Skip to content

Commit

Permalink
Merge branch 'terraform-feature' of github.com:shakacode/control-plan…
Browse files Browse the repository at this point in the history
…e-flow into fix-terraform-provider-configs
  • Loading branch information
zzaakiirr committed Oct 22, 2024
2 parents 717ce25 + 442554b commit ef05b0c
Show file tree
Hide file tree
Showing 10 changed files with 504 additions and 63 deletions.
6 changes: 2 additions & 4 deletions lib/command/terraform/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,9 @@ def generate_app_config

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 secret policy].include?(template["kind"])

File.write(terraform_app_dir.join(generator.filename), generator.tf_config.to_tf, mode: "a+")
rescue TerraformConfig::Generator::InvalidTemplateError => e
Shell.warn(e.message)
rescue StandardError => e
Shell.warn("Failed to generate config file from '#{template['kind']}' template: #{e.message}")
end
Expand Down
117 changes: 64 additions & 53 deletions lib/core/terraform_config/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,85 @@

module TerraformConfig
class Generator
SUPPORTED_TEMPLATE_KINDS = %w[gvc secret identity policy volumeset].freeze

class InvalidTemplateError < ArgumentError; end

attr_reader :config, :template

def initialize(config:, template:)
@config = config
@template = template.deep_underscore_keys.deep_symbolize_keys
validate_template_kind!
end

def filename # rubocop:disable Metrics/MethodLength
case kind
when "gvc"
"gvc.tf"
when "secret"
"secrets.tf"
when "identity"
"identities.tf"
when "policy"
"policies.tf"
else
raise "Unsupported template kind - #{kind}"
end
def filename
return "gvc.tf" if kind == "gvc"

"#{kind.pluralize}.tf"
end

def tf_config
method_name = :"#{kind}_config"
raise "Unsupported template kind - #{kind}" unless self.class.private_method_defined?(method_name)

send(method_name)
config_class.new(**config_params)
end

private

def kind
@kind ||= template[:kind]
def validate_template_kind!
return if SUPPORTED_TEMPLATE_KINDS.include?(kind)

raise InvalidTemplateError, "Unsupported template kind: #{kind}"
end

def config_class
if kind == "volumeset"
TerraformConfig::VolumeSet
else
TerraformConfig.const_get(kind.capitalize)
end
end

def config_params
send("#{kind}_config_params")
end

def gvc_config # rubocop:disable Metrics/MethodLength
TerraformConfig::Gvc.new(
**template
.slice(:name, :description, :tags)
.merge(
env: gvc_env,
pull_secrets: gvc_pull_secrets,
locations: gvc_locations,
domain: template.dig(:spec, :domain),
load_balancer: template.dig(:spec, :load_balancer)
)
)
def gvc_config_params
template
.slice(:name, :description, :tags)
.merge(
env: gvc_env,
pull_secrets: gvc_pull_secrets,
locations: gvc_locations,
domain: template.dig(:spec, :domain),
load_balancer: template.dig(:spec, :load_balancer)
)
end

def identity_config
TerraformConfig::Identity.new(**template.slice(:name, :description, :tags).merge(gvc: gvc))
def identity_config_params
template.slice(:name, :description, :tags).merge(gvc: gvc)
end

def secret_config
TerraformConfig::Secret.new(**template.slice(:name, :description, :type, :data, :tags))
def secret_config_params
template.slice(:name, :description, :type, :data, :tags)
end

def policy_config
TerraformConfig::Policy.new(
**template
.slice(:name, :description, :tags, :target, :target_kind, :target_query)
.merge(gvc: gvc, target_links: policy_target_links, bindings: policy_bindings)
)
def policy_config_params
template
.slice(:name, :description, :tags, :target, :target_kind, :target_query)
.merge(gvc: gvc, target_links: policy_target_links, bindings: policy_bindings)
end

def volumeset_config_params
specs = %i[
initial_capacity
performance_class
file_system_type
storage_class_suffix
snapshots
autoscaling
].to_h { |key| [key, template.dig(:spec, key)] }

template.slice(:name, :description, :tags).merge(gvc: gvc).merge(specs)
end

# GVC name matches application name
Expand All @@ -73,35 +89,30 @@ def gvc
end

def gvc_pull_secrets
template.dig(:spec, :pull_secret_links)&.map do |secret_link|
secret_name = secret_link.split("/").last
"cpln_secret.#{secret_name}.name"
end
template.dig(:spec, :pull_secret_links)&.map { |secret_link| "cpln_secret.#{secret_link.split('/').last}.name" }
end

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

def gvc_locations
template.dig(:spec, :static_placement, :location_links)&.map do |location_link|
location_link.split("/").last
end
template.dig(:spec, :static_placement, :location_links)&.map { |location_link| location_link.split("/").last }
end

# //secret/secret-name -> secret-name
def policy_target_links
template[:target_links]&.map do |target_link|
target_link.split("/").last
end
template[:target_links]&.map { |target_link| target_link.split("/").last }
end

# //group/viewers -> group/viewers
def policy_bindings
template[:bindings]&.map do |data|
principal_links = data.delete(:principal_links)&.map { |link| link.delete_prefix("//") }
data.merge(principal_links: principal_links)
end
end

def kind
@kind ||= template[:kind]
end
end
end
147 changes: 147 additions & 0 deletions lib/core/terraform_config/volume_set.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
# frozen_string_literal: true

module TerraformConfig
class VolumeSet < Base # rubocop:disable Metrics/ClassLength
PERFORMANCE_CLASSES = %w[general-purpose-ssd high-throughput-ssd].freeze
FILE_SYSTEM_TYPES = %w[xfs ext4].freeze
MIN_CAPACITY = 10
MIN_SCALING_FACTOR = 1.1

attr_reader :gvc, :name, :initial_capacity, :performance_class, :file_system_type,
:storage_class_suffix, :description, :tags, :snapshots, :autoscaling

def initialize( # rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
gvc:,
name:,
initial_capacity:,
performance_class:,
file_system_type:,
storage_class_suffix: nil,
description: nil,
tags: nil,
snapshots: nil,
autoscaling: nil
)
super()

@gvc = gvc
@name = name
@initial_capacity = initial_capacity
@performance_class = performance_class
@file_system_type = file_system_type
@storage_class_suffix = storage_class_suffix
@description = description
@tags = tags
@snapshots = snapshots
@autoscaling = autoscaling

validate_attributes!
end

def to_tf
block :resource, :cpln_volume_set, name do
base_arguments_tf
snapshots_tf
autoscaling_tf
end
end

private

def validate_attributes!
validate_initial_capacity!
validate_performance_class!
validate_file_system_type!
validate_autoscaling! if autoscaling
end

def validate_initial_capacity!
raise ArgumentError, "Initial capacity must be numeric" unless initial_capacity.is_a?(Numeric)
return if initial_capacity >= MIN_CAPACITY

raise ArgumentError, "Initial capacity should be >= #{MIN_CAPACITY}"
end

def validate_performance_class!
return if PERFORMANCE_CLASSES.include?(performance_class.to_s)

raise ArgumentError,
"Invalid performance class: #{performance_class}. Choose from #{PERFORMANCE_CLASSES.join(', ')}"
end

def validate_file_system_type!
return if FILE_SYSTEM_TYPES.include?(file_system_type.to_s)

raise ArgumentError, "Invalid file system type: #{file_system_type}. Choose from #{FILE_SYSTEM_TYPES.join(', ')}"
end

def validate_autoscaling!
validate_max_capacity!
validate_min_free_percentage!
validate_scaling_factor!
end

def validate_max_capacity!
max_capacity = autoscaling.fetch(:max_capacity, nil)
return if max_capacity.nil?

raise ArgumentError, "autoscaling.max_capacity must be numeric" unless max_capacity.is_a?(Numeric)
return if max_capacity >= MIN_CAPACITY

raise ArgumentError, "autoscaling.max_capacity should be >= #{MIN_CAPACITY}"
end

def validate_min_free_percentage!
min_free_percentage = autoscaling.fetch(:min_free_percentage, nil)
return if min_free_percentage.nil?

raise ArgumentError, "autoscaling.min_free_percentage must be numeric" unless min_free_percentage.is_a?(Numeric)
return if min_free_percentage.between?(1, 100)

raise ArgumentError, "autoscaling.min_free_percentage should be between 1 and 100"
end

def validate_scaling_factor!
scaling_factor = autoscaling.fetch(:scaling_factor, nil)
return if scaling_factor.nil?

raise ArgumentError, "autoscaling.scaling_factor must be numeric" unless scaling_factor.is_a?(Numeric)
return if scaling_factor >= MIN_SCALING_FACTOR

raise ArgumentError, "autoscaling.scaling_factor should be >= #{MIN_SCALING_FACTOR}"
end

def base_arguments_tf
argument :gvc, gvc

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

argument :initial_capacity, initial_capacity
argument :performance_class, performance_class
argument :storage_class_suffix, storage_class_suffix, optional: true
argument :file_system_type, file_system_type
end

def snapshots_tf
return if snapshots.nil?

block :snapshots do
%i[create_final_snapshot retention_duration schedule].each do |arg_name|
argument arg_name, snapshots.fetch(arg_name, nil), optional: true
end
end
end

def autoscaling_tf
return if autoscaling.nil?

block :autoscaling do
%i[max_capacity min_free_percentage scaling_factor].each do |arg_name|
argument arg_name, autoscaling.fetch(arg_name, nil), optional: true
end
end
end
end
end
8 changes: 8 additions & 0 deletions lib/patches/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,13 @@ def unindent
def underscore
gsub("::", "/").gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z\d])([A-Z])/, '\1_\2').tr("-", "_").downcase
end

# Covers only simple cases and used for pluralizing controlplane template kinds (`gvc`, `secret`, `policy`, etc.)
def pluralize
return self if empty?
return "#{self[...-1]}ies" if end_with?("y")

"#{self}s"
end
end
# rubocop:enable Style/OptionalBooleanParameter, Lint/UnderscorePrefixedVariableName
17 changes: 15 additions & 2 deletions spec/command/terraform/generate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@

it_behaves_like "generates terraform config files" do
let(:expected_config_paths) { all_config_paths }
let(:err_msg) { nil }
let(:err_msg) { "Unsupported template kind: workload" }
end

context "when templates folder is empty" do
Expand Down Expand Up @@ -116,6 +116,19 @@
end
end

context "when InvalidTemplateError is raised" do
before do
allow_any_instance_of(TerraformConfig::Generator).to receive(:tf_config).and_raise( # rubocop:disable RSpec/AnyInstance
TerraformConfig::Generator::InvalidTemplateError, "Invalid template: error message"
)
end

it_behaves_like "generates terraform config files" do
let(:expected_config_paths) { provider_config_paths }
let(:err_msg) { "Invalid template: error message" }
end
end

def all_config_paths
provider_config_paths + template_config_paths
end
Expand All @@ -125,7 +138,7 @@ def provider_config_paths
end

def template_config_paths
%w[gvc.tf identities.tf secrets.tf policies.tf].map { |filename| config_path(filename) }
%w[gvc.tf identities.tf secrets.tf policies.tf volumesets.tf].map { |filename| config_path(filename) }
end

def config_path(name)
Expand Down
Loading

0 comments on commit ef05b0c

Please sign in to comment.