From 63dda9ac4ee63d473ba9790c5718ce6771e931f8 Mon Sep 17 00:00:00 2001 From: Zakir Dzhamaliddinov Date: Tue, 22 Oct 2024 16:01:22 +0300 Subject: [PATCH 1/3] Put providers.tf & required_providers.tf to each app folder --- lib/command/terraform/generate.rb | 39 ++++----- lib/core/terraform_config/provider.rb | 22 ++++++ .../terraform_config/required_provider.rb | 4 + spec/command/terraform/generate_spec.rb | 79 ++++++++++--------- spec/core/terraform_config/provider_spec.rb | 26 ++++++ .../required_provider_spec.rb | 3 + 6 files changed, 119 insertions(+), 54 deletions(-) create mode 100644 lib/core/terraform_config/provider.rb create mode 100644 spec/core/terraform_config/provider_spec.rb diff --git a/lib/command/terraform/generate.rb b/lib/command/terraform/generate.rb index 901e0d81..d9589cd1 100644 --- a/lib/command/terraform/generate.rb +++ b/lib/command/terraform/generate.rb @@ -16,32 +16,20 @@ class Generate < Base WITH_INFO_HEADER = false def call - generate_common_configs - generate_app_configs - end - - private - - 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) + config.instance_variable_set(:@app, app.to_s) generate_app_config end end + private + def generate_app_config terraform_app_dir = recreate_terraform_app_dir + generate_required_providers(terraform_app_dir) + generate_providers(terraform_app_dir) + templates.each do |template| generator = TerraformConfig::Generator.new(config: config, template: template) @@ -52,6 +40,21 @@ def generate_app_config end end + def generate_required_providers(terraform_app_dir) + required_cpln_provider = TerraformConfig::RequiredProvider.new( + "cpln", + source: "controlplane-com/cpln", + version: "~> 1.0" + ) + + File.write(terraform_app_dir.join("required_providers.tf"), required_cpln_provider.to_tf) + end + + def generate_providers(terraform_app_dir) + cpln_provider = TerraformConfig::Provider.new("cpln", org: config.org) + File.write(terraform_app_dir.join("providers.tf"), cpln_provider.to_tf) + end + def recreate_terraform_app_dir full_path = terraform_dir.join(config.app) diff --git a/lib/core/terraform_config/provider.rb b/lib/core/terraform_config/provider.rb new file mode 100644 index 00000000..08625975 --- /dev/null +++ b/lib/core/terraform_config/provider.rb @@ -0,0 +1,22 @@ +# frozen_string_literal: true + +module TerraformConfig + class Provider < Base + attr_reader :name, :options + + def initialize(name, **options) + super() + + @name = name + @options = options + end + + def to_tf + block :provider, name do + options.each do |option, value| + argument option, value + end + end + end + end +end diff --git a/lib/core/terraform_config/required_provider.rb b/lib/core/terraform_config/required_provider.rb index 412c6ee7..fcff9936 100644 --- a/lib/core/terraform_config/required_provider.rb +++ b/lib/core/terraform_config/required_provider.rb @@ -13,6 +13,10 @@ def initialize(name, **options) def to_tf block :terraform do + block :cloud do + argument :organization, "PLACEHOLDER" + end + block :required_providers do argument name, options end diff --git a/spec/command/terraform/generate_spec.rb b/spec/command/terraform/generate_spec.rb index d4fa9d06..2c120590 100644 --- a/spec/command/terraform/generate_spec.rb +++ b/spec/command/terraform/generate_spec.rb @@ -24,12 +24,32 @@ FileUtils.rm_rf GENERATOR_PLAYGROUND_PATH end - it "generates terraform config files", :aggregate_failures do - config_file_paths.each { |config_file_path| expect(config_file_path).not_to exist } + shared_examples "generates terraform config files" do + specify do + all_config_paths.each { |path| expect(path).not_to exist } - expect(result[:status]).to eq(0) + expect(result[:status]).to eq(ExitCode::SUCCESS) + expect(result[:stderr]).to err_msg ? include(err_msg) : be_empty - expect(config_file_paths).to all(exist) + expect(expected_config_paths).to all(exist) + (all_config_paths - expected_config_paths).each { |path| expect(path).not_to exist } + end + end + + shared_examples "does not generate any terraform config files" do |err_msg| + it "fails with an error" do + all_config_paths.each { |path| expect(path).not_to exist } + + expect(result[:status]).to eq(ExitCode::ERROR_DEFAULT) + expect(result[:stderr]).to include(err_msg) + + all_config_paths.each { |path| expect(path).not_to exist } + end + end + + it_behaves_like "generates terraform config files" do + let(:expected_config_paths) { all_config_paths } + let(:err_msg) { nil } end context "when templates folder is empty" do @@ -39,13 +59,9 @@ allow_any_instance_of(TemplateParser).to receive(:template_dir).and_return(template_dir) # rubocop:disable RSpec/AnyInstance end - it "generates only common config files" do - config_file_paths.each { |config_file_path| expect(config_file_path).not_to exist } - - expect(result[:stderr]).to include("No templates found in #{template_dir}") - - expect(common_config_files).to all(exist) - app_config_files.each { |config_file_path| expect(config_file_path).not_to exist } + it_behaves_like "generates terraform config files" do + let(:expected_config_paths) { provider_config_paths } + let(:err_msg) { "No templates found in #{template_dir}" } end end @@ -54,25 +70,17 @@ allow_any_instance_of(TemplateParser).to receive(:parse).and_raise("error") # rubocop:disable RSpec/AnyInstance end - it "generates only common config files" do - config_file_paths.each { |config_file_path| expect(config_file_path).not_to exist } - - expect(result[:stderr]).to include("Error parsing templates: error") - - expect(common_config_files).to all(exist) - app_config_files.each { |config_file_path| expect(config_file_path).not_to exist } + it_behaves_like "generates terraform config files" do + let(:expected_config_paths) { provider_config_paths } + let(:err_msg) { "Error parsing templates: error" } end end context "when --dir option is outside of project dir" do let(:options) { ["-a", app, "--dir", GEM_TEMP_PATH.join("path-outside-of-project").to_s] } - it "aborts command execution" do - expect(result[:status]).to eq(ExitCode::ERROR_DEFAULT) - expect(result[:stderr]).to include( - "Directory to save terraform configuration files cannot be outside of current directory" - ) - end + it_behaves_like "does not generate any terraform config files", + "Directory to save terraform configuration files cannot be outside of current directory" end context "when terraform config directory creation fails" do @@ -80,23 +88,22 @@ allow(FileUtils).to receive(:mkdir_p).and_raise("error") end - it "aborts command execution" do - expect(result[:status]).to eq(ExitCode::ERROR_DEFAULT) - expect(result[:stderr]).to include("error") - end + it_behaves_like "does not generate any terraform config files", "error" end - def config_file_paths - common_config_files + app_config_files + def all_config_paths + provider_config_paths + template_config_paths end - def common_config_files - [TERRAFORM_CONFIG_DIR_PATH.join("providers.tf")] + def provider_config_paths + %w[required_providers.tf providers.tf].map { |filename| config_path(filename) } end - def app_config_files - %w[gvc.tf identities.tf secrets.tf policies.tf].map do |config_file_path| - TERRAFORM_CONFIG_DIR_PATH.join(app, config_file_path) - end + def template_config_paths + %w[gvc.tf identities.tf secrets.tf policies.tf].map { |filename| config_path(filename) } + end + + def config_path(name) + TERRAFORM_CONFIG_DIR_PATH.join(app, name) end end diff --git a/spec/core/terraform_config/provider_spec.rb b/spec/core/terraform_config/provider_spec.rb new file mode 100644 index 00000000..f12a475c --- /dev/null +++ b/spec/core/terraform_config/provider_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe TerraformConfig::Provider do + let(:config) { described_class.new(name, **options) } + + describe "#to_tf" do + subject(:generated) { config.to_tf } + + context "when provider is cpln" do + let(:name) { "cpln" } + let(:options) { { org: "test-org" } } + + it "generates correct config" do + expect(generated).to eq( + <<~EXPECTED + provider "cpln" { + org = "test-org" + } + EXPECTED + ) + end + end + end +end diff --git a/spec/core/terraform_config/required_provider_spec.rb b/spec/core/terraform_config/required_provider_spec.rb index 73e2ed64..be23f5e3 100644 --- a/spec/core/terraform_config/required_provider_spec.rb +++ b/spec/core/terraform_config/required_provider_spec.rb @@ -16,6 +16,9 @@ expect(generated).to eq( <<~EXPECTED terraform { + cloud { + organization = "PLACEHOLDER" + } required_providers { cpln = { source = "controlplane-com/cpln" From 717ce2598f3e8f402cdac81a25c578c2f65b1daa Mon Sep 17 00:00:00 2001 From: Zakir Dzhamaliddinov Date: Tue, 22 Oct 2024 16:59:04 +0300 Subject: [PATCH 2/3] coderabbitai review fixes - add exceptions handling --- lib/command/terraform/generate.rb | 12 +++++++++-- spec/command/terraform/generate_spec.rb | 27 ++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/command/terraform/generate.rb b/lib/command/terraform/generate.rb index d9589cd1..09f3f892 100644 --- a/lib/command/terraform/generate.rb +++ b/lib/command/terraform/generate.rb @@ -27,8 +27,7 @@ def call def generate_app_config terraform_app_dir = recreate_terraform_app_dir - generate_required_providers(terraform_app_dir) - generate_providers(terraform_app_dir) + generate_provider_configs(terraform_app_dir) templates.each do |template| generator = TerraformConfig::Generator.new(config: config, template: template) @@ -37,9 +36,18 @@ def generate_app_config 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 StandardError => e + Shell.warn("Failed to generate config file from '#{template['kind']}' template: #{e.message}") end end + def generate_provider_configs(terraform_app_dir) + generate_required_providers(terraform_app_dir) + generate_providers(terraform_app_dir) + rescue StandardError => e + Shell.abort("Failed to generate provider config files: #{e.message}") + end + def generate_required_providers(terraform_app_dir) required_cpln_provider = TerraformConfig::RequiredProvider.new( "cpln", diff --git a/spec/command/terraform/generate_spec.rb b/spec/command/terraform/generate_spec.rb index 2c120590..489f75a0 100644 --- a/spec/command/terraform/generate_spec.rb +++ b/spec/command/terraform/generate_spec.rb @@ -88,7 +88,32 @@ allow(FileUtils).to receive(:mkdir_p).and_raise("error") end - it_behaves_like "does not generate any terraform config files", "error" + it_behaves_like "does not generate any terraform config files", "Invalid directory: error" + end + + context "when required provider config generation fails" do + let(:required_provider_config_stub) { instance_double(TerraformConfig::RequiredProvider) } + + before do + allow(TerraformConfig::RequiredProvider).to receive(:new).and_return(required_provider_config_stub) + allow(required_provider_config_stub).to receive(:to_tf).and_raise("error") + end + + it_behaves_like "does not generate any terraform config files", "Failed to generate provider config files" + end + + context "when terraform config from template generation fails" do + let(:gvc_config_stub) { instance_double(TerraformConfig::Gvc) } + + before do + allow(TerraformConfig::Gvc).to receive(:new).and_return(gvc_config_stub) + allow(gvc_config_stub).to receive(:to_tf).and_raise("error") + end + + it_behaves_like "generates terraform config files" do + let(:expected_config_paths) { all_config_paths - [config_path("gvc.tf")] } + let(:err_msg) { "Failed to generate config file from 'gvc' template: error" } + end end def all_config_paths From 98b06828a6e0aed053f09af6ec1fc301864c48d9 Mon Sep 17 00:00:00 2001 From: Zakir Dzhamaliddinov Date: Wed, 23 Oct 2024 17:11:28 +0300 Subject: [PATCH 3/3] Review fixes --- lib/command/terraform/generate.rb | 5 +++-- lib/core/terraform_config/provider.rb | 2 +- lib/core/terraform_config/required_provider.rb | 7 ++++--- spec/core/terraform_config/provider_spec.rb | 2 +- spec/core/terraform_config/required_provider_spec.rb | 5 +++-- 5 files changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/command/terraform/generate.rb b/lib/command/terraform/generate.rb index 95689f3d..4ff52a30 100644 --- a/lib/command/terraform/generate.rb +++ b/lib/command/terraform/generate.rb @@ -48,7 +48,8 @@ def generate_provider_configs(terraform_app_dir) def generate_required_providers(terraform_app_dir) required_cpln_provider = TerraformConfig::RequiredProvider.new( - "cpln", + name: "cpln", + org: config.org, source: "controlplane-com/cpln", version: "~> 1.0" ) @@ -57,7 +58,7 @@ def generate_required_providers(terraform_app_dir) end def generate_providers(terraform_app_dir) - cpln_provider = TerraformConfig::Provider.new("cpln", org: config.org) + cpln_provider = TerraformConfig::Provider.new(name: "cpln", org: config.org) File.write(terraform_app_dir.join("providers.tf"), cpln_provider.to_tf) end diff --git a/lib/core/terraform_config/provider.rb b/lib/core/terraform_config/provider.rb index 08625975..88e2126c 100644 --- a/lib/core/terraform_config/provider.rb +++ b/lib/core/terraform_config/provider.rb @@ -4,7 +4,7 @@ module TerraformConfig class Provider < Base attr_reader :name, :options - def initialize(name, **options) + def initialize(name:, **options) super() @name = name diff --git a/lib/core/terraform_config/required_provider.rb b/lib/core/terraform_config/required_provider.rb index fcff9936..e0d20854 100644 --- a/lib/core/terraform_config/required_provider.rb +++ b/lib/core/terraform_config/required_provider.rb @@ -2,19 +2,20 @@ module TerraformConfig class RequiredProvider < Base - attr_reader :name, :options + attr_reader :name, :org, :options - def initialize(name, **options) + def initialize(name:, org:, **options) super() @name = name + @org = org @options = options end def to_tf block :terraform do block :cloud do - argument :organization, "PLACEHOLDER" + argument :organization, org end block :required_providers do diff --git a/spec/core/terraform_config/provider_spec.rb b/spec/core/terraform_config/provider_spec.rb index f12a475c..990b396c 100644 --- a/spec/core/terraform_config/provider_spec.rb +++ b/spec/core/terraform_config/provider_spec.rb @@ -3,7 +3,7 @@ require "spec_helper" describe TerraformConfig::Provider do - let(:config) { described_class.new(name, **options) } + let(:config) { described_class.new(name: name, **options) } describe "#to_tf" do subject(:generated) { config.to_tf } diff --git a/spec/core/terraform_config/required_provider_spec.rb b/spec/core/terraform_config/required_provider_spec.rb index be23f5e3..9091915b 100644 --- a/spec/core/terraform_config/required_provider_spec.rb +++ b/spec/core/terraform_config/required_provider_spec.rb @@ -3,13 +3,14 @@ require "spec_helper" describe TerraformConfig::RequiredProvider do - let(:config) { described_class.new(name, **options) } + let(:config) { described_class.new(name: name, org: org, **options) } describe "#to_tf" do subject(:generated) { config.to_tf } context "when provider is cpln" do let(:name) { "cpln" } + let(:org) { "test-org" } let(:options) { { source: "controlplane-com/cpln", version: "~> 1.0" } } it "generates correct config" do @@ -17,7 +18,7 @@ <<~EXPECTED terraform { cloud { - organization = "PLACEHOLDER" + organization = "test-org" } required_providers { cpln = {