Skip to content

Commit

Permalink
Introducte Terraform config DSL
Browse files Browse the repository at this point in the history
  • Loading branch information
zzaakiirr committed Aug 6, 2024
1 parent a0e3944 commit a38efce
Show file tree
Hide file tree
Showing 12 changed files with 377 additions and 4 deletions.
2 changes: 1 addition & 1 deletion lib/command/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def copy_files
end

def self.source_root
File.expand_path("../", __dir__)
Cpflow.root_path.join("lib")
end
end

Expand Down
14 changes: 13 additions & 1 deletion lib/command/terraform/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,19 @@ class Generate < Base
VALIDATIONS = [].freeze

def call
# TODO: Implement
File.write(terraform_dir.join("providers.tf"), cpln_provider.to_tf)
end

private

def cpln_provider
::Terraform::Config::RequiredProvider.new("cpln", source: "controlplane-com/cpln", version: "~> 1.0")
end

def terraform_dir
@terraform_dir ||= Cpflow.root_path.join("terraform").tap do |path|
FileUtils.mkdir_p(path)
end
end
end
end
Expand Down
15 changes: 15 additions & 0 deletions lib/core/terraform/config/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require_relative "dsl"

module Terraform
module Config
class Base
include Dsl

def to_tf
raise NotImplementedError
end
end
end
end
84 changes: 84 additions & 0 deletions lib/core/terraform/config/dsl.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

module Terraform
module Config
module Dsl
extend Forwardable

def_delegators :current_context, :put, :output

def block(name, *labels)
switch_context do
put("#{block_declaration(name, labels)} {\n")
yield
put("}\n")
end

# There is extra indent for whole output that needs to be removed
output.unindent(2)
end

def argument(name, value, optional: false)
return if value.nil? && optional

content =
if value.is_a?(Hash)
"{\n#{value.map { |n, v| "#{n} = #{tf_value(v)}" }.join("\n").indent(2)}\n}\n"
else
"#{tf_value(value)}\n"
end

put("#{name} = #{content}", indent: 2)
end

private

def tf_value(value)
value = value.to_s if value.is_a?(Symbol)

case value
when String
expression?(value) ? value : "\"#{value}\""
else
value
end
end

def expression?(value)
value.start_with?("var.") || value.start_with?("locals.")
end

def block_declaration(name, labels)
result = name
return result unless labels.any?

result + " #{labels.map { |label| tf_value(label) }.join(' ')}"
end

class Context
attr_accessor :output

def initialize
@output = ""
end

def put(content, indent: 0)
@output += content.indent(indent)
end
end

def switch_context
old_context = current_context
@current_context = Context.new
yield
ensure
old_context.put(current_context.output, indent: 2)
@current_context = old_context
end

def current_context
@current_context ||= Context.new
end
end
end
end
24 changes: 24 additions & 0 deletions lib/core/terraform/config/required_provider.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

module Terraform
module Config
class RequiredProvider < Base
attr_reader :name, :options

def initialize(name, **options)
super()

@name = name
@options = options
end

def to_tf
block :terraform do
block :required_providers do
argument name, options
end
end
end
end
end
end
11 changes: 10 additions & 1 deletion lib/cpflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

# We need to require base before all commands, since the commands inherit from it
require_relative "command/base"
# We need to require base terraform config before all commands, since the terraform configs inherit from it
require_relative "core/terraform/config/base"

modules = Dir["#{__dir__}/**/*.rb"].reject do |file|
file == __FILE__ || file.end_with?("base.rb")
Expand Down Expand Up @@ -51,9 +53,15 @@ def print_wrapped(message, options = {})
end
end

require_relative "patches/string"

module Cpflow
class Error < StandardError; end

def self.root_path
Pathname.new(File.expand_path("../", __dir__))
end

class Cli < Thor # rubocop:disable Metrics/ClassLength
package_name "cpflow"
default_task :no_command
Expand Down Expand Up @@ -172,7 +180,8 @@ def self.process_option_params(params)

def self.klass_for(subcommand_name)
klass_name = subcommand_name.to_s.split("-").map(&:capitalize).join
return Cpflow.const_get(klass_name) if Cpflow.const_defined?(klass_name)
full_klass_name = "Cpflow::#{klass_name}"
return const_get(full_classname) if const_defined?(full_klass_name)

Cpflow.const_set(klass_name, Class.new(BaseSubCommand)).tap do |subcommand_klass|
desc(subcommand_name, "#{subcommand_name.capitalize} commands")
Expand Down
21 changes: 21 additions & 0 deletions lib/patches/string.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

# rubocop:disable Style/OptionalBooleanParameter, Lint/UnderscorePrefixedVariableName
class String
# Copied from Rails
def indent(amount, indent_string = nil, indent_empty_lines = false)
dup.tap { |_| _.indent!(amount, indent_string, indent_empty_lines) }
end

# Copied from Rails
def indent!(amount, indent_string = nil, indent_empty_lines = false)
indent_string = indent_string || self[/^[ \t]/] || " "
re = indent_empty_lines ? /^/ : /^(?!$)/
gsub!(re, indent_string * amount)
end

def unindent(amount, indent_string = " ")
lines.map { |line| line.delete_prefix(indent_string * amount) }.join
end
end
# rubocop:enable Style/OptionalBooleanParameter, Lint/UnderscorePrefixedVariableName
1 change: 0 additions & 1 deletion spec/command/generate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
GEM_TEMP_PATH = GEM_ROOT_PATH.join("tmp")
GENERATOR_PLAYGROUND_PATH = GEM_TEMP_PATH.join("sample-project")
CONTROLPLANE_CONFIG_DIR_PATH = GENERATOR_PLAYGROUND_PATH.join(".controlplane")
CPFLOW_EXECUTABLE_PATH = GEM_ROOT_PATH.join("bin", "cpflow")

def inside_dir(path)
original_working_dir = Dir.pwd
Expand Down
30 changes: 30 additions & 0 deletions spec/command/terraform/generate_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# frozen_string_literal: true

require "spec_helper"
require "pathname"

GEM_ROOT_PATH = Pathname.new(Dir.pwd)
GEM_TEMP_PATH = GEM_ROOT_PATH.join("tmp")
GENERATOR_PLAYGROUND_PATH = GEM_TEMP_PATH.join("sample-project")
TERRAFORM_CONFIG_DIR_PATH = GENERATOR_PLAYGROUND_PATH.join("terraform")

describe Command::Terraform::Generate do
before do
FileUtils.rm_r(GENERATOR_PLAYGROUND_PATH) if Dir.exist?(GENERATOR_PLAYGROUND_PATH)
FileUtils.mkdir_p GENERATOR_PLAYGROUND_PATH

allow(Cpflow).to receive(:root_path).and_return(GENERATOR_PLAYGROUND_PATH)
end

after do
FileUtils.rm_r GENERATOR_PLAYGROUND_PATH
end

it "generates terraform config files" do
providers_config_file_path = TERRAFORM_CONFIG_DIR_PATH.join("providers.tf")

expect(providers_config_file_path).not_to exist
run_cpflow_command(described_class::SUBCOMMAND_NAME, described_class::NAME)
expect(providers_config_file_path).to exist
end
end
15 changes: 15 additions & 0 deletions spec/core/terraform/config/base_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# frozen_string_literal: true

require "spec_helper"

describe Terraform::Config::Base do
let(:config) { described_class.new }

describe "#to_tf" do
subject(:to_tf) { config.to_tf }

it "raises NotImplementedError" do
expect { to_tf }.to raise_error(NotImplementedError)
end
end
end
Loading

0 comments on commit a38efce

Please sign in to comment.