Skip to content

Commit

Permalink
Create GVC and identity terraform configs from templates (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
zzaakiirr authored Oct 21, 2024
1 parent 3382a79 commit b4bf309
Show file tree
Hide file tree
Showing 12 changed files with 509 additions and 18 deletions.
13 changes: 13 additions & 0 deletions lib/command/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,19 @@ def self.add_app_identity_option(required: false)
}
}
end

def self.dir_option(required: false)
{
name: :dir,
params: {
banner: "DIR",
desc: "Output directory",
type: :string,
required: required
}
}
end

# rubocop:enable Metrics/MethodLength

def self.all_options
Expand Down
75 changes: 69 additions & 6 deletions lib/command/terraform/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,89 @@ module Terraform
class Generate < Base
SUBCOMMAND_NAME = "terraform"
NAME = "generate"
OPTIONS = [
app_option,
dir_option
].freeze
DESCRIPTION = "Generates terraform configuration files"
LONG_DESCRIPTION = <<~DESC
- Generates terraform configuration files based on `controlplane.yml` and `templates/` config
DESC
WITH_INFO_HEADER = false
VALIDATIONS = [].freeze

def call
File.write(terraform_dir.join("providers.tf"), cpln_provider.to_tf)
generate_common_configs
generate_app_configs
end

private

def cpln_provider
TerraformConfig::RequiredProvider.new("cpln", source: "controlplane-com/cpln", version: "~> 1.0")
def generate_common_configs
cpln_provider = TerraformConfig::RequiredProvider.new(
"cpln",
source: "controlplane-com/cpln",
version: "~> 1.0"
)

File.write(terraform_dir.join("providers.tf"), cpln_provider.to_tf)
end

def generate_app_configs
Array(config.app || config.apps.keys).each do |app|
config.instance_variable_set(:@app, app)
generate_app_config
end
end

def generate_app_config
terraform_app_dir = recreate_terraform_app_dir

templates.each do |template|
generator = TerraformConfig::Generator.new(config: config, template: template)

# TODO: Delete line below after all template kinds are supported
next unless %w[gvc identity].include?(template["kind"])

File.write(terraform_app_dir.join(generator.filename), generator.tf_config.to_tf, mode: "a+")
end
end

def recreate_terraform_app_dir
full_path = terraform_dir.join(config.app)

unless File.expand_path(full_path).include?(Cpflow.root_path.to_s)
Shell.abort("Directory to save terraform configuration files cannot be outside of current directory")
end

FileUtils.rm_rf(full_path)
FileUtils.mkdir_p(full_path)

full_path
end

def templates
parser = TemplateParser.new(self)
template_files = Dir["#{parser.template_dir}/*.yml"]

if template_files.empty?
Shell.warn("No templates found in #{parser.template_dir}")
return []
end

parser.parse(template_files)
rescue StandardError => e
Shell.warn("Error parsing templates: #{e.message}")
[]
end

def terraform_dir
@terraform_dir ||= Cpflow.root_path.join("terraform").tap do |path|
FileUtils.mkdir_p(path)
@terraform_dir ||= begin
full_path = config.options.fetch(:dir, Cpflow.root_path.join("terraform"))
Pathname.new(full_path).tap do |path|
FileUtils.mkdir_p(path)
rescue StandardError => e
Shell.abort("Invalid directory: #{e.message}")
end
end
end
end
Expand Down
6 changes: 4 additions & 2 deletions lib/core/terraform_config/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ module TerraformConfig
module Dsl
extend Forwardable

REFERENCE_PATTERN = /^(var|locals|cpln_\w+)\./.freeze

def_delegators :current_context, :put, :output

def block(name, *labels)
Expand Down Expand Up @@ -44,7 +46,7 @@ def tf_value(value)
end

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

def block_declaration(name, labels)
Expand All @@ -62,7 +64,7 @@ def initialize
end

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

Expand Down
75 changes: 75 additions & 0 deletions lib/core/terraform_config/generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

module TerraformConfig
class Generator
attr_reader :config, :template

def initialize(config:, template:)
@config = config
@template = template
end

def filename
case template["kind"]
when "gvc"
"gvc.tf"
when "identity"
"identities.tf"
else
raise "Unsupported template kind - #{template['kind']}"
end
end

def tf_config
case template["kind"]
when "gvc"
gvc_config
when "identity"
identity_config
else
raise "Unsupported template kind - #{template['kind']}"
end
end

private

def gvc_config # rubocop:disable Metrics/MethodLength
pull_secrets = template.dig("spec", "pullSecretLinks")&.map do |secret_link|
secret_name = secret_link.split("/").last
"cpln_secret.#{secret_name}.name"
end

load_balancer = template.dig("spec", "loadBalancer")

TerraformConfig::Gvc.new(
name: template["name"],
description: template["description"],
tags: template["tags"],
domain: template.dig("spec", "domain"),
env: env,
pull_secrets: pull_secrets,
locations: locations,
load_balancer: load_balancer
)
end

def identity_config
TerraformConfig::Identity.new(
gvc: "cpln_gvc.#{config.app}.name", # GVC name matches application name
name: template["name"],
description: template["description"],
tags: template["tags"]
)
end

def env
template.dig("spec", "env").to_h { |env_var| [env_var["name"], env_var["value"]] }
end

def locations
template.dig("spec", "staticPlacement", "locationLinks")&.map do |location_link|
location_link.split("/").last
end
end
end
end
55 changes: 55 additions & 0 deletions lib/core/terraform_config/gvc.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# frozen_string_literal: true

module TerraformConfig
class Gvc < Base
attr_reader :name, :description, :tags, :domain, :locations, :pull_secrets, :env, :load_balancer

def initialize( # rubocop:disable Metrics/ParameterLists
name:,
description: nil,
tags: nil,
domain: nil,
locations: nil,
pull_secrets: nil,
env: nil,
load_balancer: nil
)
super()

@name = name
@description = description
@tags = tags
@domain = domain
@locations = locations
@pull_secrets = pull_secrets
@env = env
@load_balancer = load_balancer&.transform_keys { |k| k.to_s.underscore.to_sym }
end

def to_tf
block :resource, :cpln_gvc, name do
argument :name, name
argument :description, description, optional: true
argument :tags, tags, optional: true

argument :domain, domain, optional: true
argument :locations, locations, optional: true
argument :pull_secrets, pull_secrets, optional: true
argument :env, env, optional: true

load_balancer_tf
end
end

private

def load_balancer_tf
return if load_balancer.nil?

block :load_balancer do
argument :dedicated, load_balancer.fetch(:dedicated)
argument :trusted_proxies, load_balancer.fetch(:trusted_proxies, nil), optional: true
end
end
end
end
27 changes: 27 additions & 0 deletions lib/core/terraform_config/identity.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# frozen_string_literal: true

module TerraformConfig
class Identity < Base
attr_reader :gvc, :name, :description, :tags

def initialize(gvc:, name:, description: nil, tags: nil)
super()

@gvc = gvc
@name = name
@description = description
@tags = tags
end

def to_tf
block :resource, :cpln_identity, name do
argument :gvc, gvc

argument :name, name
argument :description, description, optional: true

argument :tags, tags, optional: true
end
end
end
end
2 changes: 1 addition & 1 deletion lib/cpflow.rb
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ def self.klass_for(subcommand_name)
long_desc(long_description)

command_options.each do |option|
params = process_option_params(option[:params])
params = Cpflow::Cli.process_option_params(option[:params])
method_option(option[:name], **params)
end
end
Expand Down
4 changes: 4 additions & 0 deletions lib/patches/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ def indent!(amount, indent_string = nil, indent_empty_lines = false)
def unindent
gsub(/^#{scan(/^[ \t]+(?=\S)/).min}/, "")
end

def underscore
gsub(/(.)([A-Z])/, '\1_\2').downcase
end
end
# rubocop:enable Style/OptionalBooleanParameter, Lint/UnderscorePrefixedVariableName
Loading

0 comments on commit b4bf309

Please sign in to comment.