Skip to content

Commit

Permalink
Generate Terraform config from volumeset templates
Browse files Browse the repository at this point in the history
  • Loading branch information
zzaakiirr committed Oct 17, 2024
1 parent 080f3d6 commit 7b952dd
Show file tree
Hide file tree
Showing 10 changed files with 529 additions and 67 deletions.
7 changes: 3 additions & 4 deletions lib/command/terraform/generate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ def generate_app_config(app)
terraform_app_dir = recreate_terraform_app_dir(app)

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"])
# TODO: Raise error i/o ignoring invalid template kind after all template kinds are supported
next unless TerraformConfig::Generator::SUPPORTED_TEMPLATE_KINDS.include?(template["kind"])

generator = TerraformConfig::Generator.new(config: config, template: template)
File.write(terraform_app_dir.join(generator.filename), generator.tf_config.to_tf, mode: "a+")
end
end
Expand Down
118 changes: 61 additions & 57 deletions lib/core/terraform_config/generator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,110 +2,114 @@

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

attr_reader :config, :template

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

# rubocop:disable Metrics/MethodLength
def filename
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
"#{kind.pluralize}.tf"
end
# rubocop:enable Metrics/MethodLength

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 ArgumentError, "Unsupported template kind: #{kind}"
end

# rubocop:disable Metrics/MethodLength
def gvc_config
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 config_class
return TerraformConfig.const_get(kind.capitalize) if TerraformConfig.const_defined?(kind.capitalize)
return TerraformConfig::VolumeSet if kind == "volumeset"

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

def config_params
send("#{kind}_config_params")
end
# rubocop:enable Metrics/MethodLength

def identity_config
TerraformConfig::Identity.new(**template.slice(:name, :description, :tags).merge(gvc: gvc))
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 secret_config
TerraformConfig::Secret.new(**template.slice(:name, :description, :type, :data, :tags))
def identity_config_params
template.slice(:name, :description, :tags).merge(gvc: gvc)
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 secret_config_params
template.slice(:name, :description, :type, :data, :tags)
end

# GVC name matches application name
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

# rubocop:disable Metrics/MethodLength
def volumeset_config_params
template
.slice(:name, :description, :tags)
.merge(
gvc: gvc,
initial_capacity: template.dig(:spec, :initial_capacity),
performance_class: template.dig(:spec, :performance_class),
file_system_type: template.dig(:spec, :file_system_type),
storage_class_suffix: template.dig(:spec, :storage_class_suffix),
snapshots: template.dig(:spec, :snapshots),
autoscaling: template.dig(:spec, :autoscaling)
)
end
# rubocop:enable Metrics/MethodLength

def gvc
"cpln_gvc.#{config.app}.name"
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
133 changes: 133 additions & 0 deletions lib/core/terraform_config/volume_set.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# frozen_string_literal: true

module TerraformConfig
class VolumeSet < Base
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

# rubocop:disable Metrics/ParameterLists, Metrics/MethodLength
def initialize(
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
# rubocop:enable Metrics/ParameterLists, Metrics/MethodLength

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!
return unless initial_capacity < MIN_CAPACITY

raise ArgumentError,
"Initial capacity should be greater than or equal to #{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)

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? || 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? || 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? || 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
14 changes: 14 additions & 0 deletions lib/patches/string.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,19 @@ 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

def pluralize
return self if empty?

if end_with?("ies")
self
elsif end_with?("s", "x", "z", "ch", "sh")
end_with?("es") ? self : "#{self}es"
elsif end_with?("y")
"#{self[...-1]}ies"
else
end_with?("s") ? self : "#{self}s"
end
end
end
# rubocop:enable Style/OptionalBooleanParameter, Lint/UnderscorePrefixedVariableName
2 changes: 1 addition & 1 deletion spec/command/terraform/generate_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ def common_config_files
end

def app_config_files
%w[gvc.tf identities.tf secrets.tf policies.tf].map do |config_file_path|
%w[gvcs.tf identities.tf secrets.tf policies.tf volumesets.tf].map do |config_file_path|
TERRAFORM_CONFIG_DIR_PATH.join(app, config_file_path)
end
end
Expand Down
Loading

0 comments on commit 7b952dd

Please sign in to comment.