From 04207fc678300a1fbab6e230a37c840ba15c5195 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 22 Aug 2024 10:01:23 +0000 Subject: [PATCH 01/26] add delete_async() to retire or destroy stack created from terraform-runner job --- lib/terraform/runner.rb | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index dd0d76a8..6623d58d 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -67,6 +67,28 @@ def parse_template_variables(template_path) template_variables(template_path) end + # Delete(destroy) the stack created by terraform-runner job, via terraform-runner api + # + # @param stack_id [String] stack_id from the terraforn-runner job + # @param template_path [String] Path to the template we will want to run + # @param input_vars [Hash] Hash with key/value pairs that will be passed as input variables to the + # terraform-runner run + # @param credentials [Array] List of Authentication objects to provide to the terraform run + # @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the + # terraform-runner run + # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner destroy action + def delete_async(stack_id, template_path, input_vars, credentials: [], env_vars: {}) + _log.debug("Run delete_aysnc stack: #{stack_id}: with template #{template_path}") + response = delete_stack_job( + stack_id, + template_path, + :input_vars => input_vars, + :credentials => credentials, + :env_vars => env_vars + ) + Terraform::Runner::ResponseAsync.new(stack_id) + end + # ================================================= # TerraformRunner Stack-API interaction methods # ================================================= @@ -150,6 +172,37 @@ def create_stack_job( Terraform::Runner::Response.parsed_response(http_response) end + # Delete(destroy) stack created by TerraformRunner Stack Job + def delete_stack_job( + stack_id, + template_path, + input_vars: [], + credentials: [], + env_vars: {} + ) + _log.info("start stack_job for template: #{template_path}") + tenant_id = stack_tenant_id + encoded_zip_file = encoded_zip_from_directory(template_path) + + # TODO: use tags,env_vars + payload = { + :stack_id => stack_id, + :cloud_providers => provider_connection_parameters(credentials), + :name => name, + :tenantId => tenant_id, + :templateZipFile => encoded_zip_file, + :parameters => ApiParams.to_cam_parameters(input_vars) + } + + http_response = terraform_runner_client.post( + "api/stack/delete", + *json_post_arguments(payload) + ) + _log.debug("==== http_response.body: \n #{http_response.body}") + _log.info("stack_job for template: #{template_path} running ...") + Terraform::Runner::Response.parsed_response(http_response) + end + # Retrieve TerraformRunner Stack Job details def retrieve_stack_job(stack_id) http_response = terraform_runner_client.post( From 106f92c8ecdbfa18edd53edf323bd71e0734e9dc Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Tue, 3 Sep 2024 20:53:14 +0530 Subject: [PATCH 02/26] Run Retrirement(delete) with run(), if :action='Retirement' & :stack_id= is passed --- lib/terraform/runner.rb | 67 +++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 33 deletions(-) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index 6623d58d..6d54dd96 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -18,23 +18,46 @@ def available? # Run a template, initiates terraform-runner job for running a template, via terraform-runner api # # @param input_vars [Hash] Hash with key/value pairs that will be passed as input variables to the - # terraform-runner run - # @param template_path [String] Path to the template we will want to run + # terraform-runner run, (execpt :action && :stack_id used for 'Retirement') + # * To run Retirement(delete stack) add :action='Retirement' & :stack_id= # @param tags [Hash] Hash with key/values pairs that will be passed as tags to the terraform-runner run # @param credentials [Array] List of Authentication objects to provide to the terraform run # @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the # terraform-runner run # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner create action def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: {}) - _log.debug("Run_aysnc template: #{template_path}") - response = create_stack_job( - template_path, - :input_vars => input_vars, - :tags => tags, - :credentials => credentials, - :env_vars => env_vars - ) - Terraform::Runner::ResponseAsync.new(response.stack_id) + action = input_vars.key?(:action) ? input_vars[:action].downcase : '' + case action + when 'retirement' + # ===== DELETE ===== + if input_vars.key?(:stack_id) && input_vars[:stack_id].present? + stack_id = input_vars[:stack_id] + input_vars.delete(:action) + input_vars.delete(:stack_id) + _log.debug("Run_aysnc/delete_tack('#{stack_id}') for template: #{template_path}") + response = delete_stack_job( + stack_id, + template_path, + :input_vars => input_vars, + :credentials => credentials, + :env_vars => env_vars + ) + Terraform::Runner::ResponseAsync.new(response.stack_id) + else + raise "'_stack_id' is required for Retirement action, was not passed" + end + else + # ===== CREATE ===== + _log.debug("Run_aysnc/create_stack for template: #{template_path}") + response = create_stack_job( + template_path, + :input_vars => input_vars, + :tags => tags, + :credentials => credentials, + :env_vars => env_vars + ) + Terraform::Runner::ResponseAsync.new(response.stack_id) + end end # To simplify clients who may just call run, we alias it to call @@ -67,28 +90,6 @@ def parse_template_variables(template_path) template_variables(template_path) end - # Delete(destroy) the stack created by terraform-runner job, via terraform-runner api - # - # @param stack_id [String] stack_id from the terraforn-runner job - # @param template_path [String] Path to the template we will want to run - # @param input_vars [Hash] Hash with key/value pairs that will be passed as input variables to the - # terraform-runner run - # @param credentials [Array] List of Authentication objects to provide to the terraform run - # @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the - # terraform-runner run - # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner destroy action - def delete_async(stack_id, template_path, input_vars, credentials: [], env_vars: {}) - _log.debug("Run delete_aysnc stack: #{stack_id}: with template #{template_path}") - response = delete_stack_job( - stack_id, - template_path, - :input_vars => input_vars, - :credentials => credentials, - :env_vars => env_vars - ) - Terraform::Runner::ResponseAsync.new(stack_id) - end - # ================================================= # TerraformRunner Stack-API interaction methods # ================================================= From c5931460116af0202aae9febcf4de4d15ce44d23 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Wed, 4 Sep 2024 07:53:15 +0530 Subject: [PATCH 03/26] use template_id & vars from provision for retirement --- app/models/service_template_terraform_template.rb | 3 +++ app/models/service_terraform_template.rb | 14 ++++++++++++-- lib/terraform/runner.rb | 3 ++- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/app/models/service_template_terraform_template.rb b/app/models/service_template_terraform_template.rb index 8bd25e44..49c35fa5 100644 --- a/app/models/service_template_terraform_template.rb +++ b/app/models/service_template_terraform_template.rb @@ -54,6 +54,9 @@ def self.validate_config_info(info) def terraform_template(action) template_id = config_info.dig(action.downcase.to_sym, :configuration_script_payload_id) + if template_id.nil? && action.downcase == 'retirement' + template_id = config_info.dig(:provision, :configuration_script_payload_id) + end return if template_id.nil? ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template.find(template_id) diff --git a/app/models/service_terraform_template.rb b/app/models/service_terraform_template.rb index 31a57e9c..8204cbf7 100644 --- a/app/models/service_terraform_template.rb +++ b/app/models/service_terraform_template.rb @@ -57,7 +57,7 @@ def check_completed(action) def launch_terraform_template(action) terraform_template = terraform_template(action) - stack = ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack.create_stack(terraform_template, get_job_options(action)) + stack = ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack.create_job(terraform_template, get_job_options(action)) add_resource!(stack, :name => action) end @@ -76,7 +76,17 @@ def check_refreshed(_action) private def get_job_options(action) - options[job_option_key(action)].deep_dup + case action.downcase + when 'retirement' + retire_job_options = options[job_option_key('retirement')].deep_dup + retire_job_options[:action] = action + prov_job_options = options[job_option_key('provision')].deep_dup + # if not already available in retirement options, (won't be available for terraform) + # copy extra_vars,execution_ttl,verbosity,credentials,etc from provision action + prov_job_options.deep_merge(retire_job_options) + else + options[job_option_key(action)].deep_dup + end end def config_options(action) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index 6d54dd96..f8071a7a 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -44,7 +44,8 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { ) Terraform::Runner::ResponseAsync.new(response.stack_id) else - raise "'_stack_id' is required for Retirement action, was not passed" + _log.error("'stack_id' is required for Retirement action, was not passed") + raise "'stack_id' is required for Retirement action, was not passed" end else # ===== CREATE ===== From f1431c3d0757c935b5b9e16f09c125765dea553e Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Wed, 4 Sep 2024 13:40:23 +0530 Subject: [PATCH 04/26] update 'terraform_stack_id' in service-instance.options.extra_vars, this will be later used for retirement action --- .../automation_manager/job.rb | 21 +++++++++++++++++++ app/models/service_terraform_template.rb | 11 ++++++---- lib/terraform/runner.rb | 12 ++++++----- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index a825f925..c0196113 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -28,6 +28,13 @@ def execute options[:terraform_stack_id] = response.stack_id save! + begin + update_service_instance_with_stack_id(extra_vars[:action], extra_vars[:service_instance_id], response.stack_id) + rescue Exception => e # rubocop:disable Lint/RescueException + _log.error("There was some error when update stack_id in service-instance") + _log.error(e) + end + queue_poll_runner end @@ -138,4 +145,18 @@ def cleanup_git_repository rescue Errno::ENOENT nil end + + def update_service_instance_with_stack_id(action, service_instance_id, stack_id) + if !action.nil? && action.downcase == 'provision' + service_instance = ServiceTerraformTemplate.find_by(:id => service_instance_id) + if !service_instance.nil? + service_instance.preprocess(action, {:extra_vars => {:terraform_stack_id => stack_id}}) + _log.debug("updated service-instance/#{service_instance.to_json} with stack_id#{stack_id}") + else + _log.info("Not found service-instance/#{service_instance_id}") + end + else + _log.debug("Not Provision action, no update to service-instance/#{service_instance_id} required") + end + end end diff --git a/app/models/service_terraform_template.rb b/app/models/service_terraform_template.rb index 8204cbf7..6f688cc6 100644 --- a/app/models/service_terraform_template.rb +++ b/app/models/service_terraform_template.rb @@ -57,7 +57,7 @@ def check_completed(action) def launch_terraform_template(action) terraform_template = terraform_template(action) - stack = ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack.create_job(terraform_template, get_job_options(action)) + stack = ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack.create_stack(terraform_template, get_job_options(action)) add_resource!(stack, :name => action) end @@ -81,12 +81,15 @@ def get_job_options(action) retire_job_options = options[job_option_key('retirement')].deep_dup retire_job_options[:action] = action prov_job_options = options[job_option_key('provision')].deep_dup - # if not already available in retirement options, (won't be available for terraform) + # if not already available in retirement options, (won't be available for terraform) # copy extra_vars,execution_ttl,verbosity,credentials,etc from provision action - prov_job_options.deep_merge(retire_job_options) + job_options = prov_job_options.deep_merge(retire_job_options) else - options[job_option_key(action)].deep_dup + job_options = options[job_option_key(action)].deep_dup end + job_options[:extra_vars][:service_instance_id] = id + job_options[:extra_vars][:action] = action + job_options end def config_options(action) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index f8071a7a..a2324d69 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -18,8 +18,8 @@ def available? # Run a template, initiates terraform-runner job for running a template, via terraform-runner api # # @param input_vars [Hash] Hash with key/value pairs that will be passed as input variables to the - # terraform-runner run, (execpt :action && :stack_id used for 'Retirement') - # * To run Retirement(delete stack) add :action='Retirement' & :stack_id= + # terraform-runner run, (execpt :action && :terraform_stack_id used for 'Retirement') + # * To run Retirement(delete stack) add :action='Retirement' & :terraform_stack_id= # @param tags [Hash] Hash with key/values pairs that will be passed as tags to the terraform-runner run # @param credentials [Array] List of Authentication objects to provide to the terraform run # @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the @@ -32,8 +32,10 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { # ===== DELETE ===== if input_vars.key?(:stack_id) && input_vars[:stack_id].present? stack_id = input_vars[:stack_id] + if input_vars.key?(:terraform_stack_id) && input_vars[:terraform_stack_id].present? + stack_id = input_vars[:terraform_stack_id] input_vars.delete(:action) - input_vars.delete(:stack_id) + input_vars.delete(:terraform_stack_id) _log.debug("Run_aysnc/delete_tack('#{stack_id}') for template: #{template_path}") response = delete_stack_job( stack_id, @@ -44,8 +46,8 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { ) Terraform::Runner::ResponseAsync.new(response.stack_id) else - _log.error("'stack_id' is required for Retirement action, was not passed") - raise "'stack_id' is required for Retirement action, was not passed" + _log.error("'terraform_stack_id' is required for Retirement action, was not passed") + raise "'terraform_stack_id' is required for Retirement action, was not passed" end else # ===== CREATE ===== From 09231e872fdebdded1f154d6430ef0d0355ac195 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Wed, 4 Sep 2024 23:34:19 +0530 Subject: [PATCH 05/26] minor - fix typo --- lib/terraform/runner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index a2324d69..ac272f38 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -36,7 +36,7 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { stack_id = input_vars[:terraform_stack_id] input_vars.delete(:action) input_vars.delete(:terraform_stack_id) - _log.debug("Run_aysnc/delete_tack('#{stack_id}') for template: #{template_path}") + _log.debug("Run_aysnc/delete_stack('#{stack_id}') for template: #{template_path}") response = delete_stack_job( stack_id, template_path, From 0f059edc5c225690cfe0bcae87f647b4ae6aaf2b Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Wed, 4 Sep 2024 23:40:31 +0530 Subject: [PATCH 06/26] update logs --- .../providers/embedded_terraform/automation_manager/job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index c0196113..764bc99e 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -31,7 +31,7 @@ def execute begin update_service_instance_with_stack_id(extra_vars[:action], extra_vars[:service_instance_id], response.stack_id) rescue Exception => e # rubocop:disable Lint/RescueException - _log.error("There was some error when update stack_id in service-instance") + _log.error("Failed updating stack_id in service-instance/#{extra_vars[:service_instance_id]}") _log.error(e) end From 9f84c7a3de51a8fa9c3d208c9df37e28444f5572 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 5 Sep 2024 08:58:36 +0530 Subject: [PATCH 07/26] add prefix 'miq_' for additional vars added in extra_vars, which is only used internally for Retirement action --- .../automation_manager/job.rb | 7 +++--- app/models/service_terraform_template.rb | 10 ++++++-- lib/terraform/runner.rb | 24 ++++++++++--------- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index 764bc99e..6e66a92d 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -28,10 +28,11 @@ def execute options[:terraform_stack_id] = response.stack_id save! + service_instance_id = extra_vars[:miq_service_instance_id] begin - update_service_instance_with_stack_id(extra_vars[:action], extra_vars[:service_instance_id], response.stack_id) + update_service_instance_with_stack_id(extra_vars[:miq_action], service_instance_id, response.stack_id) rescue Exception => e # rubocop:disable Lint/RescueException - _log.error("Failed updating stack_id in service-instance/#{extra_vars[:service_instance_id]}") + _log.error("Failed updating stack_id in service-instance/#{service_instance_id}") _log.error(e) end @@ -150,7 +151,7 @@ def update_service_instance_with_stack_id(action, service_instance_id, stack_id) if !action.nil? && action.downcase == 'provision' service_instance = ServiceTerraformTemplate.find_by(:id => service_instance_id) if !service_instance.nil? - service_instance.preprocess(action, {:extra_vars => {:terraform_stack_id => stack_id}}) + service_instance.preprocess(action, {:extra_vars => {:miq_terraform_stack_id => stack_id}}) _log.debug("updated service-instance/#{service_instance.to_json} with stack_id#{stack_id}") else _log.info("Not found service-instance/#{service_instance_id}") diff --git a/app/models/service_terraform_template.rb b/app/models/service_terraform_template.rb index 6f688cc6..e56c9729 100644 --- a/app/models/service_terraform_template.rb +++ b/app/models/service_terraform_template.rb @@ -57,6 +57,7 @@ def check_completed(action) def launch_terraform_template(action) terraform_template = terraform_template(action) + # runs provision or retirement job, based on job_options stack = ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack.create_stack(terraform_template, get_job_options(action)) add_resource!(stack, :name => action) end @@ -87,8 +88,13 @@ def get_job_options(action) else job_options = options[job_option_key(action)].deep_dup end - job_options[:extra_vars][:service_instance_id] = id - job_options[:extra_vars][:action] = action + + # Add additional vars, + # :miq_action - required for Retirement action + # :miq_service_instance_id - use in Provision action to update stack_id in Service, later needed in Retirement action. + job_options[:extra_vars][:miq_service_instance_id] = id + job_options[:extra_vars][:miq_action] = action + job_options end diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index ac272f38..0a749b50 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -18,24 +18,26 @@ def available? # Run a template, initiates terraform-runner job for running a template, via terraform-runner api # # @param input_vars [Hash] Hash with key/value pairs that will be passed as input variables to the - # terraform-runner run, (execpt :action && :terraform_stack_id used for 'Retirement') - # * To run Retirement(delete stack) add :action='Retirement' & :terraform_stack_id= + # terraform-runner run, (execpt :miq_action && :miq_terraform_stack_id used for 'Retirement') + # Note: To run Retirement(delete stack) required vars: + # - :miq_action=>'Retirement' + # - :miq_terraform_stack_id=>{{stack_id-from-terraform-runner}} # @param tags [Hash] Hash with key/values pairs that will be passed as tags to the terraform-runner run # @param credentials [Array] List of Authentication objects to provide to the terraform run # @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the # terraform-runner run # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner create action def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: {}) - action = input_vars.key?(:action) ? input_vars[:action].downcase : '' + action = input_vars.key?(:miq_action) ? input_vars[:miq_action].downcase : nil + stack_id = input_vars.key?(:miq_terraform_stack_id) ? input_vars[:miq_terraform_stack_id].downcase : nil + input_vars.delete(:miq_action) + input_vars.delete(:miq_terraform_stack_id) + input_vars.delete(:miq_service_instance_id) + case action when 'retirement' # ===== DELETE ===== - if input_vars.key?(:stack_id) && input_vars[:stack_id].present? - stack_id = input_vars[:stack_id] - if input_vars.key?(:terraform_stack_id) && input_vars[:terraform_stack_id].present? - stack_id = input_vars[:terraform_stack_id] - input_vars.delete(:action) - input_vars.delete(:terraform_stack_id) + if stack_id.present? _log.debug("Run_aysnc/delete_stack('#{stack_id}') for template: #{template_path}") response = delete_stack_job( stack_id, @@ -46,8 +48,8 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { ) Terraform::Runner::ResponseAsync.new(response.stack_id) else - _log.error("'terraform_stack_id' is required for Retirement action, was not passed") - raise "'terraform_stack_id' is required for Retirement action, was not passed" + _log.error("'miq_terraform_stack_id' is required for Retirement action, was not passed") + raise "'miq_terraform_stack_id' is required for Retirement action, was not passed" end else # ===== CREATE ===== From bda9b9ac127c80d069c1d0ee7065a4006403fb83 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 5 Sep 2024 09:21:34 +0530 Subject: [PATCH 08/26] Add test for running Retirement action in Terraform::Runner --- ...on => hello-world-create-in-progress.json} | 0 .../hello-world-delete-in-progress.json | 37 +++++++++ .../responses/hello-world-delete-success.json | 15 ++++ ... hello-world-retrieve-create-success.json} | 0 spec/lib/terraform/runner_spec.rb | 76 ++++++++++++++++--- 5 files changed, 119 insertions(+), 9 deletions(-) rename spec/lib/terraform/runner/data/responses/{hello-world-create-success.json => hello-world-create-in-progress.json} (100%) create mode 100644 spec/lib/terraform/runner/data/responses/hello-world-delete-in-progress.json create mode 100644 spec/lib/terraform/runner/data/responses/hello-world-delete-success.json rename spec/lib/terraform/runner/data/responses/{hello-world-retrieve-success.json => hello-world-retrieve-create-success.json} (100%) diff --git a/spec/lib/terraform/runner/data/responses/hello-world-create-success.json b/spec/lib/terraform/runner/data/responses/hello-world-create-in-progress.json similarity index 100% rename from spec/lib/terraform/runner/data/responses/hello-world-create-success.json rename to spec/lib/terraform/runner/data/responses/hello-world-create-in-progress.json diff --git a/spec/lib/terraform/runner/data/responses/hello-world-delete-in-progress.json b/spec/lib/terraform/runner/data/responses/hello-world-delete-in-progress.json new file mode 100644 index 00000000..1d6d8817 --- /dev/null +++ b/spec/lib/terraform/runner/data/responses/hello-world-delete-in-progress.json @@ -0,0 +1,37 @@ +{ + "stack_id": "72259c10-e84c-11ee-a474-2d77bed8e73a", + "stack_job_id": 40, + "stack_job_is_latest": true, + "status": "IN_PROGRESS", + "action": "DELETE", + "message": "Wed Sep 04 2024 08:49:09 GMT+0530 (India Standard Time) Running terraform init ... \n\n\u001b[0m\u001b[1mInitializing provider plugins...\u001b[0m\n- Finding latest version of hashicorp/null...\n", + "error_message": "", + "details": { + "resources": [ + { + "name": "null_resource.null_resource_simple", + "consolelinks": [], + "details": { + "id": "4731093289508954906", + "triggers": null + }, + "tainted": false, + "idFromProvider": "4731093289508954906", + "typeFromProvider": "null_resource", + "type": "unknown", + "provider": "unknown" + } + ], + "outputs": [ + { + "name": "greeting", + "type": "string", + "value": "Hello Mumbai" + } + ] + }, + "stack_name": "Terraform::Runner", + "created_at": "2024-09-04T03:19:09.592Z", + "metadata": null, + "stack_job_start_time": "2024-09-04T03:19:09.592Z" +} \ No newline at end of file diff --git a/spec/lib/terraform/runner/data/responses/hello-world-delete-success.json b/spec/lib/terraform/runner/data/responses/hello-world-delete-success.json new file mode 100644 index 00000000..799aad1b --- /dev/null +++ b/spec/lib/terraform/runner/data/responses/hello-world-delete-success.json @@ -0,0 +1,15 @@ +{ + "stack_id": "72259c10-e84c-11ee-a474-2d77bed8e73a", + "stack_job_id": 40, + "stack_job_is_latest": true, + "status": "SUCCESS", + "action": "DELETE", + "message": "Wed Sep 04 2024 08:49:09 GMT+0530 (India Standard Time) Running terraform init ... \n\n[0m[1mInitializing provider plugins...[0m\n- Finding latest version of hashicorp/null...\n- Installing hashicorp/null v3.2.2...\n- Installed hashicorp/null v3.2.2 (signed, key ID [0m[1m0C0AF313E5FD9F80[0m[0m)\n\nProviders are signed by their developers.\nIf you'd like to know more about provider signing, you can read about it here:\nhttps://opentofu.org/docs/cli/plugins/signing/\n\nOpenTofu has created a lock file [1m.terraform.lock.hcl[0m to record the provider\nselections it made above. Include this file in your version control repository\nso that OpenTofu can guarantee to make the same selections by default when\nyou run \"tofu init\" in the future.[0m\n\n[0m[1m[32mOpenTofu has been successfully initialized![0m[32m[0m\n[0m[32m\nYou may now begin working with OpenTofu. Try running \"tofu plan\" to see\nany changes that are required for your infrastructure. All OpenTofu commands\nshould now work.\n\nIf you ever set or change modules or backend configuration for OpenTofu,\nrerun this command to reinitialize your working directory. If you forget, other\ncommands will detect it and remind you to do so if necessary.[0m\n\n\nWed Sep 04 2024 08:49:14 GMT+0530 (India Standard Time) Running terraform destroy ... \n[0m[1mnull_resource.null_resource_simple: Refreshing state... [id=4731093289508954906][0m\n\nOpenTofu used the selected providers to generate the following execution\nplan. Resource actions are indicated with the following symbols:\n [31m-[0m destroy[0m\n\nOpenTofu will perform the following actions:\n\n[1m # null_resource.null_resource_simple[0m will be [1m[31mdestroyed[0m\n[0m [31m-[0m[0m resource \"null_resource\" \"null_resource_simple\" {\n [31m-[0m[0m id = \"4731093289508954906\" [90m-> null[0m[0m\n }\n\n[1mPlan:[0m 0 to add, 0 to change, 1 to destroy.\n[0m\nChanges to Outputs:\n [31m-[0m[0m greeting = \"Hello Mumbai\" [90m-> null[0m[0m\n[0m[1mnull_resource.null_resource_simple: Destroying... [id=4731093289508954906][0m[0m\n[0m[1mnull_resource.null_resource_simple: Destruction complete after 0s[0m\n[0m[1m[32m\nDestroy complete! Resources: 1 destroyed.\n[0m", + "error_message": "", + "details": {"resources":[], "outputs":[]}, + "stack_name": "Terraform::Runner", + "created_at": "2024-09-04T03:19:09.592Z", + "metadata": null, + "stack_job_start_time": "2024-09-04T03:19:09.592Z", + "stack_job_end_time": "2024-09-04T03:19:15.620Z" +} \ No newline at end of file diff --git a/spec/lib/terraform/runner/data/responses/hello-world-retrieve-success.json b/spec/lib/terraform/runner/data/responses/hello-world-retrieve-create-success.json similarity index 100% rename from spec/lib/terraform/runner/data/responses/hello-world-retrieve-success.json rename to spec/lib/terraform/runner/data/responses/hello-world-retrieve-create-success.json diff --git a/spec/lib/terraform/runner_spec.rb b/spec/lib/terraform/runner_spec.rb index 3a693420..1428ce00 100644 --- a/spec/lib/terraform/runner_spec.rb +++ b/spec/lib/terraform/runner_spec.rb @@ -10,8 +10,10 @@ before do stub_const("ENV", ENV.to_h.merge("TERRAFORM_RUNNER_URL" => terraform_runner_url)) - @hello_world_create_response = JSON.parse(File.read(File.join(__dir__, "runner/data/responses/hello-world-create-success.json"))) - @hello_world_retrieve_response = JSON.parse(File.read(File.join(__dir__, "runner/data/responses/hello-world-retrieve-success.json"))) + @hello_world_create_response = JSON.parse(File.read(File.join(__dir__, "runner/data/responses/hello-world-create-in-progress.json"))) + @hello_world_retrieve_create_response = JSON.parse(File.read(File.join(__dir__, "runner/data/responses/hello-world-retrieve-create-success.json"))) + @hello_world_delete_response = JSON.parse(File.read(File.join(__dir__, "runner/data/responses/hello-world-delete-in-progress.json"))) + @hello_world_retrieve_delete_response = JSON.parse(File.read(File.join(__dir__, "runner/data/responses/hello-world-delete-success.json"))) end before do @@ -51,7 +53,7 @@ def verify_req(req) ) retrieve_stub = stub_request(:post, "#{terraform_runner_url}/api/stack/retrieve") - .with(:body => hash_including({:stack_id => @hello_world_retrieve_response['stack_id']})) + .with(:body => hash_including({:stack_id => @hello_world_retrieve_create_response['stack_id']})) .to_return( :status => 200, :body => @hello_world_create_response.to_json @@ -99,11 +101,13 @@ def verify_req(req) retrieve_stub = nil before do + ENV["TERRAFORM_RUNNER_URL"] = "https://1.2.3.4:7000" + retrieve_stub = stub_request(:post, "#{terraform_runner_url}/api/stack/retrieve") - .with(:body => hash_including({:stack_id => @hello_world_retrieve_response['stack_id']})) + .with(:body => hash_including({:stack_id => @hello_world_retrieve_create_response['stack_id']})) .to_return( :status => 200, - :body => @hello_world_retrieve_response.to_json + :body => @hello_world_retrieve_create_response.to_json ) end @@ -113,9 +117,9 @@ def verify_req(req) expect(response.status).to(eq('SUCCESS'), "terraform-runner failed with:\n#{response.status}") expect(response.message).to(include('greeting = "Hello World"')) - expect(response.stack_id).to(eq(@hello_world_retrieve_response['stack_id'])) + expect(response.stack_id).to(eq(@hello_world_retrieve_create_response['stack_id'])) expect(response.action).to(eq('CREATE')) - expect(response.stack_name).to(eq(@hello_world_retrieve_response['stack_name'])) + expect(response.stack_name).to(eq(@hello_world_retrieve_create_response['stack_name'])) expect(response.details.keys).to(eq(%w[resources outputs])) expect(retrieve_stub).to(have_been_requested.times(1)) @@ -139,7 +143,7 @@ def verify_req(req) cancel_response[:status] = 'CANCELLED' retrieve_stub = stub_request(:post, "#{terraform_runner_url}/api/stack/retrieve") - .with(:body => hash_including({:stack_id => @hello_world_retrieve_response['stack_id']})) + .with(:body => hash_including({:stack_id => @hello_world_retrieve_create_response['stack_id']})) .to_return( :status => 200, :body => @hello_world_create_response.to_json @@ -151,7 +155,7 @@ def verify_req(req) :body => cancel_response.to_json ) cancel_stub = stub_request(:post, "#{terraform_runner_url}/api/stack/cancel") - .with(:body => hash_including({:stack_id => @hello_world_retrieve_response['stack_id']})) + .with(:body => hash_including({:stack_id => @hello_world_retrieve_create_response['stack_id']})) .to_return( :status => 200, :body => cancel_response.to_json @@ -191,6 +195,60 @@ def verify_req(req) expect(response.status).to(eq('CANCELLED'), "terraform-runner failed with:\n#{response.status}") end end + + describe '.run_async with Retirement action' do + delete_stub = nil + delete_retrieve_stub = nil + + def verify_req(req) + body = JSON.parse(req.body) + # expect(body["stack_id"]).to(eq(@hello_world_retrieve_delete_response['stack_id'])) + # expect(body).to(have_key('templateZipFile')) + # # expect(body["parameters"]).to(eq([{"name" => "name", "value" => "New World", "secured" => "false"}])) + # expect(body["cloud_providers"]).to(eq([])) + end + + before do + ENV["TERRAFORM_RUNNER_URL"] = "https://1.2.3.4:7000" + + delete_stub = stub_request(:post, "https://1.2.3.4:7000/api/stack/delete") + .with { |req| verify_req(req) } + .to_return( + :status => 200, + :body => @hello_world_delete_response.to_json + ) + + delete_retrieve_stub = stub_request(:post, "https://1.2.3.4:7000/api/stack/retrieve") + .with(:body => hash_including({:stack_id => @hello_world_retrieve_delete_response['stack_id']})) + .to_return( + :status => 200, + :body => @hello_world_retrieve_delete_response.to_json + ) + end + + let(:input_vars) do + { + 'name' => 'New World', + :miq_action => 'Retirement', + :miq_terraform_stack_id => @hello_world_retrieve_delete_response['stack_id'] + } + end + + it "start running retirement for hello-world terraform template stack" do + async_response = Terraform::Runner.run_async(input_vars, File.join(__dir__, "runner/data/hello-world")) + expect(delete_stub).to(have_been_requested.times(1)) + + response = async_response.response + expect(delete_retrieve_stub).to(have_been_requested.times(1)) + expect(response.stack_id).to(eq(@hello_world_delete_response['stack_id'])) + expect(response.action).to(eq('DELETE')) + expect(response.stack_name).to(eq(@hello_world_delete_response['stack_name'])) + + expect(response.status).to(eq('SUCCESS'), "terraform-runner failed with:\n#{response.status}") + expect(response.message).to(include('Destroy complete! Resources: 1 destroyed.')) + expect(response.details).to(eq({"resources" => [], "outputs" => []})) + end + end end context '.run with cloud credentials' do From ebd914c8e4ebae564684da2968573f3fe8efc9d9 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 5 Sep 2024 10:27:44 +0530 Subject: [PATCH 09/26] uncomment in verify_req from retirement test --- spec/lib/terraform/runner_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/lib/terraform/runner_spec.rb b/spec/lib/terraform/runner_spec.rb index 1428ce00..6ce044d5 100644 --- a/spec/lib/terraform/runner_spec.rb +++ b/spec/lib/terraform/runner_spec.rb @@ -202,10 +202,10 @@ def verify_req(req) def verify_req(req) body = JSON.parse(req.body) - # expect(body["stack_id"]).to(eq(@hello_world_retrieve_delete_response['stack_id'])) - # expect(body).to(have_key('templateZipFile')) - # # expect(body["parameters"]).to(eq([{"name" => "name", "value" => "New World", "secured" => "false"}])) - # expect(body["cloud_providers"]).to(eq([])) + expect(body["stack_id"]).to(eq(@hello_world_retrieve_delete_response['stack_id'])) + expect(body).to(have_key('templateZipFile')) + expect(body["parameters"]).to(eq([{"name" => "name", "value" => "New World", "secured" => "false"}])) + expect(body["cloud_providers"]).to(eq([])) end before do From 1c84917c7fa4c504253d14ab0de15b27ca569464 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 5 Sep 2024 10:48:27 +0530 Subject: [PATCH 10/26] remove typo - remove downcase fo stack_id --- lib/terraform/runner.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index 0a749b50..c066d301 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -29,7 +29,7 @@ def available? # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner create action def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: {}) action = input_vars.key?(:miq_action) ? input_vars[:miq_action].downcase : nil - stack_id = input_vars.key?(:miq_terraform_stack_id) ? input_vars[:miq_terraform_stack_id].downcase : nil + stack_id = input_vars.key?(:miq_terraform_stack_id) ? input_vars[:miq_terraform_stack_id] : nil input_vars.delete(:miq_action) input_vars.delete(:miq_terraform_stack_id) input_vars.delete(:miq_service_instance_id) From 8a91833de75f580c686c3f213b80e69e9b440ba1 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Tue, 5 Nov 2024 16:40:44 +0530 Subject: [PATCH 11/26] fix json file path stack_spec --- .../embedded_terraform/automation_manager/stack_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/manageiq/providers/embedded_terraform/automation_manager/stack_spec.rb b/spec/models/manageiq/providers/embedded_terraform/automation_manager/stack_spec.rb index 49fde038..45cbfeba 100644 --- a/spec/models/manageiq/providers/embedded_terraform/automation_manager/stack_spec.rb +++ b/spec/models/manageiq/providers/embedded_terraform/automation_manager/stack_spec.rb @@ -39,11 +39,11 @@ let(:terraform_runner_url) { "https://1.2.3.4:7000" } let(:hello_world_retrieve_response) do require 'json' - JSON.parse(File.read(File.join(__dir__, "../../../../../lib/terraform/runner/data/responses/hello-world-retrieve-success.json"))) + JSON.parse(File.read(File.join(__dir__, "../../../../../lib/terraform/runner/data/responses/hello-world-retrieve-create-success.json"))) end let(:miq_task) { FactoryBot.create(:miq_task, :job => job) } - let(:job) { FactoryBot.create(:embedded_terraform_job, :state => "finished", :options => {:terraform_stack_id => hello_world_retrieve_response['stack_id']})} + let(:job) { FactoryBot.create(:embedded_terraform_job, :state => "finished", :options => {:terraform_stack_id => hello_world_retrieve_response['stack_id']}) } let(:terraform_runner_stdout) { hello_world_retrieve_response['message'] } let(:terraform_runner_stdout_html) { TerminalToHtml.render(terraform_runner_stdout) } From 5ed867c43f5981649a7fc9a3cf680732caa8180d Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Tue, 5 Nov 2024 20:45:16 +0530 Subject: [PATCH 12/26] By default, for retirement(terraform destroy) use same config as provision config, same saved in resource-action Because retirement(terraform destroy) action is run in-reverse order with same terraform template, vars & creds --- app/models/service_template_terraform_template.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/models/service_template_terraform_template.rb b/app/models/service_template_terraform_template.rb index 49c35fa5..0754ec7c 100644 --- a/app/models/service_template_terraform_template.rb +++ b/app/models/service_template_terraform_template.rb @@ -39,18 +39,26 @@ def update_catalog_item(options, auth_user = nil) super end - def self.validate_config_info(info) + private_class_method def self.validate_config_info(info) info[:provision][:fqname] ||= default_provisioning_entry_point(SERVICE_TYPE_ATOMIC) if info.key?(:provision) + info[:reconfigure][:fqname] ||= default_reconfiguration_entry_point if info.key?(:reconfigure) - info[:retirement] ||= {} + # By default, for retirement(terraform destroy) will have same config as provision config, + # because retirement(terraform destroy) action is run in-reverse order with same terraform template. + info[:retirement] ||= retirement_default_info(info[:provision]) info[:retirement][:fqname] ||= default_retirement_entry_point raise _("Must provide a configuration_script_payload_id") if info[:provision][:configuration_script_payload_id].nil? info end - private_class_method :validate_config_info + + private_class_method def self.retirement_default_info(prov_info) + info = prov_info.deep_dup + info.delete(:fqname) + info + end def terraform_template(action) template_id = config_info.dig(action.downcase.to_sym, :configuration_script_payload_id) From ac75f0d588a002667b090df666825c6242002c58 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Wed, 6 Nov 2024 04:36:52 +0530 Subject: [PATCH 13/26] Update retirement entry-point & retirement action will now have template_id --- app/models/service_template_terraform_template.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/models/service_template_terraform_template.rb b/app/models/service_template_terraform_template.rb index 0754ec7c..37bc1152 100644 --- a/app/models/service_template_terraform_template.rb +++ b/app/models/service_template_terraform_template.rb @@ -8,7 +8,7 @@ def self.default_reconfiguration_entry_point end def self.default_retirement_entry_point - '/Service/Generic/StateMachines/GenericLifecycle/Retire_Basic_Resource' + '/Service/Generic/StateMachines/GenericLifecycle/Retire_Advanced_Resource_None' end def self.create_catalog_item(options, _auth_user) @@ -62,9 +62,6 @@ def update_catalog_item(options, auth_user = nil) def terraform_template(action) template_id = config_info.dig(action.downcase.to_sym, :configuration_script_payload_id) - if template_id.nil? && action.downcase == 'retirement' - template_id = config_info.dig(:provision, :configuration_script_payload_id) - end return if template_id.nil? ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template.find(template_id) From ff73ff0f8047bad7eba47dc354c25e5dc9baed26 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Wed, 6 Nov 2024 04:44:19 +0530 Subject: [PATCH 14/26] Fetch terraform-runner stack_id from Provision Job, no need to additionally update Service instance --- .../automation_manager/job.rb | 22 ---------- app/models/service_terraform_template.rb | 42 ++++++++++++------- lib/terraform/runner.rb | 17 ++++---- spec/lib/terraform/runner_spec.rb | 6 +-- 4 files changed, 39 insertions(+), 48 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index 6e66a92d..a825f925 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -28,14 +28,6 @@ def execute options[:terraform_stack_id] = response.stack_id save! - service_instance_id = extra_vars[:miq_service_instance_id] - begin - update_service_instance_with_stack_id(extra_vars[:miq_action], service_instance_id, response.stack_id) - rescue Exception => e # rubocop:disable Lint/RescueException - _log.error("Failed updating stack_id in service-instance/#{service_instance_id}") - _log.error(e) - end - queue_poll_runner end @@ -146,18 +138,4 @@ def cleanup_git_repository rescue Errno::ENOENT nil end - - def update_service_instance_with_stack_id(action, service_instance_id, stack_id) - if !action.nil? && action.downcase == 'provision' - service_instance = ServiceTerraformTemplate.find_by(:id => service_instance_id) - if !service_instance.nil? - service_instance.preprocess(action, {:extra_vars => {:miq_terraform_stack_id => stack_id}}) - _log.debug("updated service-instance/#{service_instance.to_json} with stack_id#{stack_id}") - else - _log.info("Not found service-instance/#{service_instance_id}") - end - else - _log.debug("Not Provision action, no update to service-instance/#{service_instance_id} required") - end - end end diff --git a/app/models/service_terraform_template.rb b/app/models/service_terraform_template.rb index e56c9729..cf56b216 100644 --- a/app/models/service_terraform_template.rb +++ b/app/models/service_terraform_template.rb @@ -76,23 +76,37 @@ def check_refreshed(_action) private + def job(action) + stack(action).miq_task.job + rescue err + _log.error("Could not fetch #{action} Job", err) + nil + end + def get_job_options(action) - case action.downcase - when 'retirement' - retire_job_options = options[job_option_key('retirement')].deep_dup - retire_job_options[:action] = action - prov_job_options = options[job_option_key('provision')].deep_dup - # if not already available in retirement options, (won't be available for terraform) - # copy extra_vars,execution_ttl,verbosity,credentials,etc from provision action - job_options = prov_job_options.deep_merge(retire_job_options) - else - job_options = options[job_option_key(action)].deep_dup - end + job_options = options[job_option_key(action)].deep_dup # Add additional vars, - # :miq_action - required for Retirement action - # :miq_service_instance_id - use in Provision action to update stack_id in Service, later needed in Retirement action. - job_options[:extra_vars][:miq_service_instance_id] = id + # :miq_action -> required for Retirement action + # :miq_terraform_runner_stack_id -> Terraform-Runner stack_id required by Retirement action. + + case action + when ResourceAction::RETIREMENT + prov_job = job(ResourceAction::PROVISION) + if prov_job.present? && prov_job.options.present? + # Copy input-vars from Provision(terraform apply) action, + # the Retirement(terraform destroy) action will use same input-vars/values. + if prov_job.options.key?(:input_vars) && prov_job.options[:input_vars].key?(:extra_vars) + prov_vars = prov_job.options[:input_vars][:extra_vars].deep_dup + job_options[:extra_vars] = prov_vars.deep_merge(job_options[:extra_vars]) + end + + # stack_id for terraform-runner + job_options[:extra_vars][:miq_terraform_runner_stack_id] = prov_job.options[:terraform_stack_id] + end + end + + # current action, required to identify Retirement action job_options[:extra_vars][:miq_action] = action job_options diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index c066d301..c4f4d026 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -18,24 +18,23 @@ def available? # Run a template, initiates terraform-runner job for running a template, via terraform-runner api # # @param input_vars [Hash] Hash with key/value pairs that will be passed as input variables to the - # terraform-runner run, (execpt :miq_action && :miq_terraform_stack_id used for 'Retirement') + # terraform-runner run, (execpt :miq_action && :miq_terraform_runner_stack_id used for 'Retirement') # Note: To run Retirement(delete stack) required vars: # - :miq_action=>'Retirement' - # - :miq_terraform_stack_id=>{{stack_id-from-terraform-runner}} + # - :miq_terraform_runner_stack_id=>{{stack_id-from-terraform-runner}} # @param tags [Hash] Hash with key/values pairs that will be passed as tags to the terraform-runner run # @param credentials [Array] List of Authentication objects to provide to the terraform run # @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the # terraform-runner run # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner create action def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: {}) - action = input_vars.key?(:miq_action) ? input_vars[:miq_action].downcase : nil - stack_id = input_vars.key?(:miq_terraform_stack_id) ? input_vars[:miq_terraform_stack_id] : nil + action = input_vars[:miq_action] + stack_id = input_vars[:miq_terraform_runner_stack_id] input_vars.delete(:miq_action) - input_vars.delete(:miq_terraform_stack_id) - input_vars.delete(:miq_service_instance_id) + input_vars.delete(:miq_terraform_runner_stack_id) case action - when 'retirement' + when ResourceAction::RETIREMENT # ===== DELETE ===== if stack_id.present? _log.debug("Run_aysnc/delete_stack('#{stack_id}') for template: #{template_path}") @@ -48,8 +47,8 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { ) Terraform::Runner::ResponseAsync.new(response.stack_id) else - _log.error("'miq_terraform_stack_id' is required for Retirement action, was not passed") - raise "'miq_terraform_stack_id' is required for Retirement action, was not passed" + _log.error("'miq_terraform_runner_stack_id' is required for Retirement action, was not passed") + raise "'miq_terraform_runner_stack_id' is required for Retirement action, was not passed" end else # ===== CREATE ===== diff --git a/spec/lib/terraform/runner_spec.rb b/spec/lib/terraform/runner_spec.rb index 6ce044d5..a6a50129 100644 --- a/spec/lib/terraform/runner_spec.rb +++ b/spec/lib/terraform/runner_spec.rb @@ -228,9 +228,9 @@ def verify_req(req) let(:input_vars) do { - 'name' => 'New World', - :miq_action => 'Retirement', - :miq_terraform_stack_id => @hello_world_retrieve_delete_response['stack_id'] + 'name' => 'New World', + :miq_action => 'Retirement', + :miq_terraform_runner_stack_id => @hello_world_retrieve_delete_response['stack_id'] } end From bb6e751ee6fa1b81eeaf3e945324bdd242a1cce7 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Wed, 6 Nov 2024 23:44:13 +0530 Subject: [PATCH 15/26] same can be done without the exception handler+logging --- app/models/service_terraform_template.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/app/models/service_terraform_template.rb b/app/models/service_terraform_template.rb index cf56b216..1188831a 100644 --- a/app/models/service_terraform_template.rb +++ b/app/models/service_terraform_template.rb @@ -77,10 +77,7 @@ def check_refreshed(_action) private def job(action) - stack(action).miq_task.job - rescue err - _log.error("Could not fetch #{action} Job", err) - nil + stack(action)&.miq_task&.job end def get_job_options(action) From 3459f891b4cac160b18cee3ed504c9cac68468a8 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 00:45:24 +0530 Subject: [PATCH 16/26] use 'if' instead of 'case', use 'dig()' --- app/models/service_terraform_template.rb | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/models/service_terraform_template.rb b/app/models/service_terraform_template.rb index 1188831a..b98cdb52 100644 --- a/app/models/service_terraform_template.rb +++ b/app/models/service_terraform_template.rb @@ -87,16 +87,13 @@ def get_job_options(action) # :miq_action -> required for Retirement action # :miq_terraform_runner_stack_id -> Terraform-Runner stack_id required by Retirement action. - case action - when ResourceAction::RETIREMENT + if action == ResourceAction::RETIREMENT prov_job = job(ResourceAction::PROVISION) if prov_job.present? && prov_job.options.present? # Copy input-vars from Provision(terraform apply) action, # the Retirement(terraform destroy) action will use same input-vars/values. - if prov_job.options.key?(:input_vars) && prov_job.options[:input_vars].key?(:extra_vars) - prov_vars = prov_job.options[:input_vars][:extra_vars].deep_dup - job_options[:extra_vars] = prov_vars.deep_merge(job_options[:extra_vars]) - end + prov_vars = prov_job.options.dig(:input_vars, :extra_vars) + job_options[:extra_vars] = prov_vars.deep_merge!(job_options[:extra_vars]) if prov_vars # stack_id for terraform-runner job_options[:extra_vars][:miq_terraform_runner_stack_id] = prov_job.options[:terraform_stack_id] From 64aae7714b6afc8c5cbbe74af70b923946b5528c Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 15:10:38 +0530 Subject: [PATCH 17/26] code improvements for terraform runner methods - to simplify add alias/methods for create_stack, delete_stack - reorder params in run_aync() method, make template_path required, input_vars optional - add optional params for action,stack_id in run_async() method - add alias for methods stack,stop_stack --- .../automation_manager/job.rb | 19 ++++-- app/models/service_terraform_template.rb | 10 +--- lib/terraform/runner.rb | 60 +++++++++++-------- lib/terraform/runner/response_async.rb | 2 +- spec/lib/terraform/runner_spec.rb | 25 ++++---- 5 files changed, 64 insertions(+), 52 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index a825f925..e4bd2012 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -21,9 +21,18 @@ def pre_execute def execute template_path = File.join(options[:git_checkout_tempdir], template_relative_path) credentials = Authentication.where(:id => options[:credentials]) - extra_vars = options.dig(:input_vars, :extra_vars) || {} - - response = Terraform::Runner.run(decrypt_extra_vars(extra_vars), template_path, :credentials => credentials, :env_vars => options[:env_vars]) + input_vars = options.dig(:input_vars, :extra_vars) || {} + action = options.dig(:input_vars, :action) || nil + terraform_stack_id = options.dig(:input_vars, :terraform_stack_id) || nil # required in case of Retirement action + + response = Terraform::Runner.run( + template_path, + :input_vars => decrypt_input_vars(input_vars), + :credentials => credentials, + :env_vars => options[:env_vars], + :action => action, + :stack_id => terraform_stack_id + ) options[:terraform_stack_id] = response.stack_id save! @@ -106,8 +115,8 @@ def stack_response @stack_response ||= Terraform::Runner::ResponseAsync.new(options[:terraform_stack_id]) end - def decrypt_extra_vars(extra_vars) - result = extra_vars.deep_dup + def decrypt_input_vars(input_vars) + result = input_vars.deep_dup result.transform_values! { |val| val.kind_of?(String) ? ManageIQ::Password.try_decrypt(val) : val } end diff --git a/app/models/service_terraform_template.rb b/app/models/service_terraform_template.rb index b98cdb52..434da185 100644 --- a/app/models/service_terraform_template.rb +++ b/app/models/service_terraform_template.rb @@ -83,10 +83,6 @@ def job(action) def get_job_options(action) job_options = options[job_option_key(action)].deep_dup - # Add additional vars, - # :miq_action -> required for Retirement action - # :miq_terraform_runner_stack_id -> Terraform-Runner stack_id required by Retirement action. - if action == ResourceAction::RETIREMENT prov_job = job(ResourceAction::PROVISION) if prov_job.present? && prov_job.options.present? @@ -95,13 +91,13 @@ def get_job_options(action) prov_vars = prov_job.options.dig(:input_vars, :extra_vars) job_options[:extra_vars] = prov_vars.deep_merge!(job_options[:extra_vars]) if prov_vars - # stack_id for terraform-runner - job_options[:extra_vars][:miq_terraform_runner_stack_id] = prov_job.options[:terraform_stack_id] + # add stack_id for terraform-runner + job_options[:terraform_stack_id] = prov_job.options[:terraform_stack_id] end end # current action, required to identify Retirement action - job_options[:extra_vars][:miq_action] = action + job_options[:action] = action job_options end diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index c4f4d026..cb6c29a9 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -15,24 +15,19 @@ def available? @available = false end - # Run a template, initiates terraform-runner job for running a template, via terraform-runner api + # Run a terraform template. Initiate terraform-runner stack job for running a template, via terraform-runner api. + # By default will run Provision action job, if no action & stack_id is passed. # - # @param input_vars [Hash] Hash with key/value pairs that will be passed as input variables to the - # terraform-runner run, (execpt :miq_action && :miq_terraform_runner_stack_id used for 'Retirement') - # Note: To run Retirement(delete stack) required vars: - # - :miq_action=>'Retirement' - # - :miq_terraform_runner_stack_id=>{{stack_id-from-terraform-runner}} - # @param tags [Hash] Hash with key/values pairs that will be passed as tags to the terraform-runner run - # @param credentials [Array] List of Authentication objects to provide to the terraform run - # @param env_vars [Hash] Hash with key/value pairs that will be passed as environment variables to the - # terraform-runner run - # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner create action - def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: {}) - action = input_vars[:miq_action] - stack_id = input_vars[:miq_terraform_runner_stack_id] - input_vars.delete(:miq_action) - input_vars.delete(:miq_terraform_runner_stack_id) - + # @param template_path [String] (required) path to the terraform template directory. + # @param input_vars [Hash] (optional) key/value pairs as input variables for the terraform-runner run job. + # @param tags [Hash] (optional) key/value pairs tags for terraform-runner Provisioned resources. + # @param credentials [Array] (optional) List of Authentication objects for the terraform run job. + # @param env_vars [Hash] (optional) key/value pairs used as environment variables, for terraform-runner run job. + # @param action [String] (optional) type of action, use ResourceAction::PROVISION or ResourceAction::RETIREMENT. + # @param stack_id [String] (optional) required, if running ResourceAction::RETIREMENT action, used by Terraform-Runner stack_delete job. + # + # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner api call + def run_async(template_path, input_vars: {}, tags: nil, credentials: [], env_vars: {}, action: ResourceAction::PROVISION, stack_id: nil) case action when ResourceAction::RETIREMENT # ===== DELETE ===== @@ -45,10 +40,9 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { :credentials => credentials, :env_vars => env_vars ) - Terraform::Runner::ResponseAsync.new(response.stack_id) else - _log.error("'miq_terraform_runner_stack_id' is required for Retirement action, was not passed") - raise "'miq_terraform_runner_stack_id' is required for Retirement action, was not passed" + _log.error("'stack_id' is required for #{ResourceAction::RETIREMENT} action") + raise "'stack_id' is required for #{ResourceAction::RETIREMENT} action" end else # ===== CREATE ===== @@ -60,15 +54,23 @@ def run_async(input_vars, template_path, tags: nil, credentials: [], env_vars: { :credentials => credentials, :env_vars => env_vars ) - Terraform::Runner::ResponseAsync.new(response.stack_id) end + Terraform::Runner::ResponseAsync.new(response.stack_id) end # To simplify clients who may just call run, we alias it to call # run_async. If we ever need run_sync, we'll need to revisit this. alias run run_async - # Stop running terraform-runner job by stack_id + # To simplify clients who want to create-stack, we alias it to call run_async + alias create_stack run_async + + # Delete(destroy) terraform-runner created stack resources. + def delete_stack(stack_id, template_path, input_vars, credentials: [], env_vars: {}) + run_async(template_path, input_vars, nil, credentials, env_vars, ResourceAction::RETIREMENT, stack_id) + end + + # Stop running terraform-runner job, by stack_id # # @param stack_id [String] stack_id from the terraforn-runner job # @@ -77,15 +79,21 @@ def stop_async(stack_id) cancel_stack_job(stack_id) end - # Fetch terraform-runner job result/status by stack_id + # To simplify clients who want to stop a running stack job, we alias it to call stop_async + alias stop_stack stop_async + + # Fetch stack object(with result/status), by stack_id from terraform-runner # - # @param stack_id [String] stack_id from the terraforn-runner job + # @param stack_id [String] stack_id for the terraforn-runner stack job # # @return [Terraform::Runner::Response] Response object with result of terraform run def fetch_result_by_stack_id(stack_id) retrieve_stack_job(stack_id) end + # To simplify clients who want to fetch stack object from terraform-runner + alias stack fetch_result_by_stack_id + # Parse Terraform Template input/output variables # @param template_path [String] Path to the template we will want to parse for input/output variables # @return Response(body) object of terraform-runner api/template/variables, @@ -149,7 +157,7 @@ def provider_connection_parameters(credentials) # Create TerraformRunner Stack Job def create_stack_job( template_path, - input_vars: [], + input_vars: {}, tags: nil, credentials: [], env_vars: {}, @@ -181,7 +189,7 @@ def create_stack_job( def delete_stack_job( stack_id, template_path, - input_vars: [], + input_vars: {}, credentials: [], env_vars: {} ) diff --git a/lib/terraform/runner/response_async.rb b/lib/terraform/runner/response_async.rb index 3f6134c1..0fcfbe9d 100644 --- a/lib/terraform/runner/response_async.rb +++ b/lib/terraform/runner/response_async.rb @@ -26,7 +26,7 @@ def running? def stop raise "No job running to stop" if !running? - Terraform::Runner.stop_async(@stack_id) + Terraform::Runner.stop_stack(@stack_id) end # Re-Fetch async job's response diff --git a/spec/lib/terraform/runner_spec.rb b/spec/lib/terraform/runner_spec.rb index a6a50129..5d532084 100644 --- a/spec/lib/terraform/runner_spec.rb +++ b/spec/lib/terraform/runner_spec.rb @@ -63,7 +63,7 @@ def verify_req(req) let(:input_vars) { {'name' => 'New World'} } it "start running hello-world terraform template" do - async_response = Terraform::Runner.run_async(input_vars, File.join(__dir__, "runner/data/hello-world")) + async_response = Terraform::Runner.run_async(File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars) expect(create_stub).to(have_been_requested.times(1)) response = async_response.response @@ -78,7 +78,7 @@ def verify_req(req) end it "handles trailing '/' in template path" do - async_response = Terraform::Runner.run_async(input_vars, File.join(__dir__, "runner/data/hello-world/")) + async_response = Terraform::Runner.run_async(File.join(__dir__, "runner/data/hello-world/"), :input_vars => input_vars) expect(create_stub).to(have_been_requested.times(1)) response = async_response.response @@ -165,7 +165,7 @@ def verify_req(req) let(:input_vars) { {} } it "start running, then stop the before it completes" do - async_response = Terraform::Runner.run_async(input_vars, File.join(__dir__, "runner/data/hello-world")) + async_response = Terraform::Runner.run_async(File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars) expect(create_stub).to(have_been_requested.times(1)) expect(retrieve_stub).to(have_been_requested.times(0)) @@ -226,16 +226,15 @@ def verify_req(req) ) end - let(:input_vars) do - { - 'name' => 'New World', - :miq_action => 'Retirement', - :miq_terraform_runner_stack_id => @hello_world_retrieve_delete_response['stack_id'] - } - end + let(:input_vars) { {'name' => 'New World'} } it "start running retirement for hello-world terraform template stack" do - async_response = Terraform::Runner.run_async(input_vars, File.join(__dir__, "runner/data/hello-world")) + async_response = Terraform::Runner.run_async( + File.join(__dir__, "runner/data/hello-world"), + :input_vars => input_vars, + :action => ResourceAction::RETIREMENT, + :stack_id => @hello_world_retrieve_delete_response['stack_id'] + ) expect(delete_stub).to(have_been_requested.times(1)) response = async_response.response @@ -311,8 +310,8 @@ def verify_req(req) it "start running terraform template with amazon credential" do Terraform::Runner.run_async( - input_vars, File.join(__dir__, "runner/data/hello-world"), + :input_vars => input_vars, :credentials => [amazon_cred] ) expect(create_stub).to(have_been_requested.times(1)) @@ -393,8 +392,8 @@ def verify_req(req) it "start running terraform template with vSphere & ibmcloud credentials" do Terraform::Runner.run_async( - input_vars, File.join(__dir__, "runner/data/hello-world"), + :input_vars => input_vars, :credentials => [vsphere_cred, ibmcloud_cred] ) expect(create_stub).to(have_been_requested.times(1)) From 245028887d7f1c75c73651e4f50bd081ed2308cb Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 15:36:52 +0530 Subject: [PATCH 18/26] Use explicit create_stack & delete_stack runner methods in Job.execute --- .../automation_manager/job.rb | 25 ++++++++----- lib/terraform/runner.rb | 9 +++-- spec/lib/terraform/runner_spec.rb | 35 +++++++++++++++++++ 3 files changed, 59 insertions(+), 10 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index e4bd2012..92b783f8 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -25,14 +25,23 @@ def execute action = options.dig(:input_vars, :action) || nil terraform_stack_id = options.dig(:input_vars, :terraform_stack_id) || nil # required in case of Retirement action - response = Terraform::Runner.run( - template_path, - :input_vars => decrypt_input_vars(input_vars), - :credentials => credentials, - :env_vars => options[:env_vars], - :action => action, - :stack_id => terraform_stack_id - ) + response = case action + when ResourceAction::RETIREMENT + Terraform::Runner.delete_stack( + terraform_stack_id, + template_path, + :input_vars => decrypt_input_vars(input_vars), + :credentials => credentials, + :env_vars => options[:env_vars] + ) + else + Terraform::Runner.create_stack( + template_path, + :input_vars => decrypt_input_vars(input_vars), + :credentials => credentials, + :env_vars => options[:env_vars] + ) + end options[:terraform_stack_id] = response.stack_id save! diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index cb6c29a9..53616f8d 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -66,8 +66,13 @@ def run_async(template_path, input_vars: {}, tags: nil, credentials: [], env_var alias create_stack run_async # Delete(destroy) terraform-runner created stack resources. - def delete_stack(stack_id, template_path, input_vars, credentials: [], env_vars: {}) - run_async(template_path, input_vars, nil, credentials, env_vars, ResourceAction::RETIREMENT, stack_id) + def delete_stack(stack_id, template_path, input_vars: {}, credentials: [], env_vars: {}) + run_async(template_path, + :input_vars => input_vars, + :credentials => credentials, + :env_vars => env_vars, + :action => ResourceAction::RETIREMENT, + :stack_id => stack_id) end # Stop running terraform-runner job, by stack_id diff --git a/spec/lib/terraform/runner_spec.rb b/spec/lib/terraform/runner_spec.rb index 5d532084..8bf369e4 100644 --- a/spec/lib/terraform/runner_spec.rb +++ b/spec/lib/terraform/runner_spec.rb @@ -77,6 +77,21 @@ def verify_req(req) expect(response.details).to(be_nil) end + it "create_stack for hello-world terraform template" do + async_response = Terraform::Runner.create_stack(File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars) + expect(create_stub).to(have_been_requested.times(1)) + + response = async_response.response + expect(retrieve_stub).to(have_been_requested.times(1)) + + expect(response.status).to(eq('IN_PROGRESS'), "terraform-runner failed with:\n#{response.status}") + expect(response.stack_id).to(eq(@hello_world_create_response['stack_id'])) + expect(response.action).to(eq('CREATE')) + expect(response.stack_name).to(eq(@hello_world_create_response['stack_name'])) + expect(response.message).to(be_nil) + expect(response.details).to(be_nil) + end + it "handles trailing '/' in template path" do async_response = Terraform::Runner.run_async(File.join(__dir__, "runner/data/hello-world/"), :input_vars => input_vars) expect(create_stub).to(have_been_requested.times(1)) @@ -247,6 +262,26 @@ def verify_req(req) expect(response.message).to(include('Destroy complete! Resources: 1 destroyed.')) expect(response.details).to(eq({"resources" => [], "outputs" => []})) end + + it "delete_stack for hello-world terraform template" do + stack_id = @hello_world_retrieve_delete_response['stack_id'] + async_response = Terraform::Runner.delete_stack( + stack_id, + File.join(__dir__, "runner/data/hello-world"), + :input_vars => input_vars + ) + expect(delete_stub).to(have_been_requested.times(1)) + + response = async_response.response + expect(delete_retrieve_stub).to(have_been_requested.times(1)) + expect(response.stack_id).to(eq(@hello_world_delete_response['stack_id'])) + expect(response.action).to(eq('DELETE')) + expect(response.stack_name).to(eq(@hello_world_delete_response['stack_name'])) + + expect(response.status).to(eq('SUCCESS'), "terraform-runner failed with:\n#{response.status}") + expect(response.message).to(include('Destroy complete! Resources: 1 destroyed.')) + expect(response.details).to(eq({"resources" => [], "outputs" => []})) + end end end From 10cf0ba827ff7141aaaf20d200d3f90f7534584b Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 20:12:02 +0530 Subject: [PATCH 19/26] Add params for create_job for Retirement & make Job.options consistment with Provision --- .../automation_manager/job.rb | 58 +++++++++---------- .../automation_manager/template.rb | 6 +- 2 files changed, 34 insertions(+), 30 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index 92b783f8..5356b3d7 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -1,11 +1,13 @@ class ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job < Job - def self.create_job(template, env_vars, input_vars, credentials, poll_interval: 1.minute) + def self.create_job(template, env_vars, input_vars, credentials, action: ResourceAction::PROVISION, terraform_stack_id: nil, poll_interval: 1.minute) super( - :template_id => template.id, - :env_vars => env_vars, - :input_vars => input_vars, - :credentials => credentials, - :poll_interval => poll_interval, + :template_id => template.id, + :env_vars => env_vars, + :input_vars => input_vars, + :credentials => credentials, + :poll_interval => poll_interval, + :action => action, + :terraform_stack_id => terraform_stack_id ) end @@ -22,29 +24,27 @@ def execute template_path = File.join(options[:git_checkout_tempdir], template_relative_path) credentials = Authentication.where(:id => options[:credentials]) input_vars = options.dig(:input_vars, :extra_vars) || {} - action = options.dig(:input_vars, :action) || nil - terraform_stack_id = options.dig(:input_vars, :terraform_stack_id) || nil # required in case of Retirement action - - response = case action - when ResourceAction::RETIREMENT - Terraform::Runner.delete_stack( - terraform_stack_id, - template_path, - :input_vars => decrypt_input_vars(input_vars), - :credentials => credentials, - :env_vars => options[:env_vars] - ) - else - Terraform::Runner.create_stack( - template_path, - :input_vars => decrypt_input_vars(input_vars), - :credentials => credentials, - :env_vars => options[:env_vars] - ) - end - - options[:terraform_stack_id] = response.stack_id - save! + action = options[:action] + + case action + when ResourceAction::RETIREMENT + Terraform::Runner.delete_stack( + options[:terraform_stack_id], + template_path, + :input_vars => decrypt_input_vars(input_vars), + :credentials => credentials, + :env_vars => options[:env_vars] + ) + else + response = Terraform::Runner.create_stack( + template_path, + :input_vars => decrypt_input_vars(input_vars), + :credentials => credentials, + :env_vars => options[:env_vars] + ) + options[:terraform_stack_id] = response.stack_id + save! + end queue_poll_runner end diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb index b7df59f0..85515567 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb @@ -4,7 +4,11 @@ class ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template < Mana def run(vars = {}, _userid = nil) env_vars = vars.delete(:env) || {} credentials = vars.delete(:credentials) + action = vars.delete(:action) + terraform_stack_id = vars.delete(:terraform_stack_id) - self.class.module_parent::Job.create_job(self, env_vars, vars, credentials).tap(&:signal_start) + self.class.module_parent::Job.create_job( + self, env_vars, vars, credentials, :action => action, :terraform_stack_id => terraform_stack_id + ).tap(&:signal_start) end end From 396db42cbf95a96aa27ef5fcfea2aa764215d156 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 20:27:25 +0530 Subject: [PATCH 20/26] fix test for changes made in Job class --- .../automation_manager/job_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb b/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb index e019a238..eb20ff58 100644 --- a/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb +++ b/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb @@ -1,6 +1,6 @@ RSpec.describe ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job do let(:template) { FactoryBot.create(:terraform_template) } - let(:job) { described_class.create_job(template, env_vars, input_vars, credentials).tap { |job| job.state = state} } + let(:job) { described_class.create_job(template, env_vars, input_vars, credentials).tap { |job| job.state = state } } let(:state) { "waiting_to_start" } let(:env_vars) { {} } let(:input_vars) { {} } @@ -11,11 +11,13 @@ expect(described_class.create_job(template, env_vars, input_vars, credentials)).to have_attributes( :type => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job", :options => { - :template_id => template.id, - :env_vars => env_vars, - :input_vars => input_vars, - :credentials => credentials, - :poll_interval => 60 + :template_id => template.id, + :env_vars => env_vars, + :input_vars => input_vars, + :credentials => credentials, + :poll_interval => 60, + :action => "Provision", + :terraform_stack_id => nil } ) end From c5def8efc197bce3fcdc5d35a42a7cf17bd01c9b Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 20:31:33 +0530 Subject: [PATCH 21/26] add test for creating job for Retirement action --- .../automation_manager/job_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb b/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb index eb20ff58..474dfda4 100644 --- a/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb +++ b/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb @@ -21,6 +21,25 @@ } ) end + + it "create a job for Retirement action" do + expect( + described_class.create_job( + template, env_vars, input_vars, credentials, :action => ResourceAction::RETIREMENT, :terraform_stack_id => '999-999-999' + ) + ).to have_attributes( + :type => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job", + :options => { + :template_id => template.id, + :env_vars => env_vars, + :input_vars => input_vars, + :credentials => credentials, + :poll_interval => 60, + :action => 'Retirement', + :terraform_stack_id => '999-999-999' + } + ) + end end describe "#signal" do From 5102827e13159824fe4f82aaacb4d5bb470b9c8f Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 21:00:08 +0530 Subject: [PATCH 22/26] default action Provision in template.run & add template.run tests for Provision & Retrirement --- .../automation_manager/template.rb | 2 +- .../automation_manager/job_spec.rb | 11 ++--- .../automation_manager/template_spec.rb | 45 +++++++++++++++++-- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb index 85515567..18b19c9c 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/template.rb @@ -4,7 +4,7 @@ class ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template < Mana def run(vars = {}, _userid = nil) env_vars = vars.delete(:env) || {} credentials = vars.delete(:credentials) - action = vars.delete(:action) + action = vars.delete(:action) || ResourceAction::PROVISION terraform_stack_id = vars.delete(:terraform_stack_id) self.class.module_parent::Job.create_job( diff --git a/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb b/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb index 474dfda4..71fdb159 100644 --- a/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb +++ b/spec/models/manageiq/providers/embedded_terraform/automation_manager/job_spec.rb @@ -3,8 +3,9 @@ let(:job) { described_class.create_job(template, env_vars, input_vars, credentials).tap { |job| job.state = state } } let(:state) { "waiting_to_start" } let(:env_vars) { {} } - let(:input_vars) { {} } + let(:input_vars) { {:extra_vars => {}} } let(:credentials) { [] } + let(:terraform_stack_id) { '999-999-999-999' } describe ".create_job" do it "create a job" do @@ -16,7 +17,7 @@ :input_vars => input_vars, :credentials => credentials, :poll_interval => 60, - :action => "Provision", + :action => ResourceAction::PROVISION, :terraform_stack_id => nil } ) @@ -25,7 +26,7 @@ it "create a job for Retirement action" do expect( described_class.create_job( - template, env_vars, input_vars, credentials, :action => ResourceAction::RETIREMENT, :terraform_stack_id => '999-999-999' + template, env_vars, input_vars, credentials, :action => ResourceAction::RETIREMENT, :terraform_stack_id => terraform_stack_id ) ).to have_attributes( :type => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job", @@ -35,8 +36,8 @@ :input_vars => input_vars, :credentials => credentials, :poll_interval => 60, - :action => 'Retirement', - :terraform_stack_id => '999-999-999' + :action => ResourceAction::RETIREMENT, + :terraform_stack_id => terraform_stack_id } ) end diff --git a/spec/models/manageiq/providers/embedded_terraform/automation_manager/template_spec.rb b/spec/models/manageiq/providers/embedded_terraform/automation_manager/template_spec.rb index fedabb8f..12499193 100644 --- a/spec/models/manageiq/providers/embedded_terraform/automation_manager/template_spec.rb +++ b/spec/models/manageiq/providers/embedded_terraform/automation_manager/template_spec.rb @@ -1,10 +1,49 @@ RSpec.describe ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template do let(:template) { FactoryBot.create(:terraform_template) } + let(:env_vars) { {} } + let(:extra_vars) { {} } + let(:credentials) { [] } + let(:terraform_stack_id) { '999-999-999-999' } + + let(:provision_options) do + {:env => env_vars, :extra_vars => extra_vars, :credentials => credentials} + end + let(:retirement_options) do + {:env => env_vars, :extra_vars => extra_vars, :credentials => credentials, :action => ResourceAction::RETIREMENT, :terraform_stack_id => terraform_stack_id} + end describe "#run" do - it "creates a Job" do - job = template.run - expect(job).to be_a(ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job) + it "run template for a provision job" do + job = template.run(provision_options) + + expect(job).to have_attributes( + :type => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job", + :options => { + :template_id => template.id, + :env_vars => env_vars, + :input_vars => {:extra_vars => extra_vars}, + :credentials => credentials, + :poll_interval => 60, + :action => ResourceAction::PROVISION, + :terraform_stack_id => nil + } + ) + end + + it "run template for a retirement job" do + job = template.run(retirement_options) + expect(job).to have_attributes( + :type => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Job", + :options => { + :template_id => template.id, + :env_vars => env_vars, + :input_vars => {:extra_vars => extra_vars}, + :credentials => credentials, + :poll_interval => 60, + :action => ResourceAction::RETIREMENT, + :terraform_stack_id => terraform_stack_id + } + ) end end end From ccc380260e60f708b848534c48bee781a1bb3d12 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Thu, 7 Nov 2024 21:07:54 +0530 Subject: [PATCH 23/26] small improvements to decrypt_vars --- .../embedded_terraform/automation_manager/job.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index 5356b3d7..bf4ee903 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -31,14 +31,14 @@ def execute Terraform::Runner.delete_stack( options[:terraform_stack_id], template_path, - :input_vars => decrypt_input_vars(input_vars), + :input_vars => decrypt_vars(input_vars), :credentials => credentials, :env_vars => options[:env_vars] ) else response = Terraform::Runner.create_stack( template_path, - :input_vars => decrypt_input_vars(input_vars), + :input_vars => decrypt_vars(input_vars), :credentials => credentials, :env_vars => options[:env_vars] ) @@ -124,9 +124,8 @@ def stack_response @stack_response ||= Terraform::Runner::ResponseAsync.new(options[:terraform_stack_id]) end - def decrypt_input_vars(input_vars) - result = input_vars.deep_dup - result.transform_values! { |val| val.kind_of?(String) ? ManageIQ::Password.try_decrypt(val) : val } + def decrypt_vars(input_vars) + input_vars.transform_values! { |val| val.kind_of?(String) ? ManageIQ::Password.try_decrypt(val) : val } end def configuration_script_source From 5d3e5739569f67a5098a53ffc8b9fc633de84ee9 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Fri, 8 Nov 2024 16:16:43 +0530 Subject: [PATCH 24/26] instead calling run_async from create_stack/delete_stack, move code into create_stack/delete_stack --- lib/terraform/runner.rb | 60 +++++++++++++++++++++-------------------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index 53616f8d..7aefcf77 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -31,22 +31,21 @@ def run_async(template_path, input_vars: {}, tags: nil, credentials: [], env_var case action when ResourceAction::RETIREMENT # ===== DELETE ===== - if stack_id.present? - _log.debug("Run_aysnc/delete_stack('#{stack_id}') for template: #{template_path}") - response = delete_stack_job( - stack_id, - template_path, - :input_vars => input_vars, - :credentials => credentials, - :env_vars => env_vars - ) - else - _log.error("'stack_id' is required for #{ResourceAction::RETIREMENT} action") - raise "'stack_id' is required for #{ResourceAction::RETIREMENT} action" - end + delete_stack(stack_id, template_path, :input_vars => input_vars, :credentials => credentials, :env_vars => env_vars) else # ===== CREATE ===== - _log.debug("Run_aysnc/create_stack for template: #{template_path}") + create_stack(template_path, :input_vars => input_vars, :tags => tags, :credentials => credentials, :env_vars => env_vars) + end + end + + # To simplify clients who may just call run, we alias it to call + # run_async. If we ever need run_sync, we'll need to revisit this. + alias run run_async + + # Provision or Create (terraform apply) stack in terraform-runner from a terraform template. + def create_stack(template_path, input_vars: {}, tags: nil, credentials: [], env_vars: {}) + _log.debug("Run_aysnc/create_stack for template: #{template_path}") + if template_path.present? response = create_stack_job( template_path, :input_vars => input_vars, @@ -54,25 +53,28 @@ def run_async(template_path, input_vars: {}, tags: nil, credentials: [], env_var :credentials => credentials, :env_vars => env_vars ) + Terraform::Runner::ResponseAsync.new(response.stack_id) + else + raise "'template_path' is required for #{ResourceAction::Provision} action" end - Terraform::Runner::ResponseAsync.new(response.stack_id) end - # To simplify clients who may just call run, we alias it to call - # run_async. If we ever need run_sync, we'll need to revisit this. - alias run run_async - - # To simplify clients who want to create-stack, we alias it to call run_async - alias create_stack run_async - - # Delete(destroy) terraform-runner created stack resources. + # Retire or Delete(terraform destroy) the terraform-runner created stack resources. def delete_stack(stack_id, template_path, input_vars: {}, credentials: [], env_vars: {}) - run_async(template_path, - :input_vars => input_vars, - :credentials => credentials, - :env_vars => env_vars, - :action => ResourceAction::RETIREMENT, - :stack_id => stack_id) + if stack_id.present? && template_path.present? + _log.debug("Run_aysnc/delete_stack('#{stack_id}') for template: #{template_path}") + response = delete_stack_job( + stack_id, + template_path, + :input_vars => input_vars, + :credentials => credentials, + :env_vars => env_vars + ) + Terraform::Runner::ResponseAsync.new(response.stack_id) + else + _log.error("'stack_id' && 'template_path' are required for #{ResourceAction::RETIREMENT} action") + raise "'stack_id' && 'template_path' are required for #{ResourceAction::RETIREMENT} action" + end end # Stop running terraform-runner job, by stack_id From 2a215b037a4ad3ef2fadd29633407194d4b2625d Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Fri, 8 Nov 2024 22:45:35 +0530 Subject: [PATCH 25/26] Remove Terraform::Runner.run_async & .run methods, as not used --- lib/terraform/runner.rb | 29 ++++-------- spec/lib/terraform/runner_spec.rb | 76 ++++++++----------------------- 2 files changed, 29 insertions(+), 76 deletions(-) diff --git a/lib/terraform/runner.rb b/lib/terraform/runner.rb index 7aefcf77..f81132f1 100644 --- a/lib/terraform/runner.rb +++ b/lib/terraform/runner.rb @@ -15,34 +15,15 @@ def available? @available = false end - # Run a terraform template. Initiate terraform-runner stack job for running a template, via terraform-runner api. - # By default will run Provision action job, if no action & stack_id is passed. + # Provision or Create (terraform apply) stack in terraform-runner from a terraform template. # # @param template_path [String] (required) path to the terraform template directory. # @param input_vars [Hash] (optional) key/value pairs as input variables for the terraform-runner run job. # @param tags [Hash] (optional) key/value pairs tags for terraform-runner Provisioned resources. # @param credentials [Array] (optional) List of Authentication objects for the terraform run job. # @param env_vars [Hash] (optional) key/value pairs used as environment variables, for terraform-runner run job. - # @param action [String] (optional) type of action, use ResourceAction::PROVISION or ResourceAction::RETIREMENT. - # @param stack_id [String] (optional) required, if running ResourceAction::RETIREMENT action, used by Terraform-Runner stack_delete job. # # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner api call - def run_async(template_path, input_vars: {}, tags: nil, credentials: [], env_vars: {}, action: ResourceAction::PROVISION, stack_id: nil) - case action - when ResourceAction::RETIREMENT - # ===== DELETE ===== - delete_stack(stack_id, template_path, :input_vars => input_vars, :credentials => credentials, :env_vars => env_vars) - else - # ===== CREATE ===== - create_stack(template_path, :input_vars => input_vars, :tags => tags, :credentials => credentials, :env_vars => env_vars) - end - end - - # To simplify clients who may just call run, we alias it to call - # run_async. If we ever need run_sync, we'll need to revisit this. - alias run run_async - - # Provision or Create (terraform apply) stack in terraform-runner from a terraform template. def create_stack(template_path, input_vars: {}, tags: nil, credentials: [], env_vars: {}) _log.debug("Run_aysnc/create_stack for template: #{template_path}") if template_path.present? @@ -60,6 +41,14 @@ def create_stack(template_path, input_vars: {}, tags: nil, credentials: [], env_ end # Retire or Delete(terraform destroy) the terraform-runner created stack resources. + # + # @param stack_id [String] (optional) required, if running ResourceAction::RETIREMENT action, used by Terraform-Runner stack_delete job. + # @param template_path [String] (required) path to the terraform template directory. + # @param input_vars [Hash] (optional) key/value pairs as input variables for the terraform-runner run job. + # @param credentials [Array] (optional) List of Authentication objects for the terraform run job. + # @param env_vars [Hash] (optional) key/value pairs used as environment variables, for terraform-runner run job. + # + # @return [Terraform::Runner::ResponseAsync] Response object of terraform-runner api call def delete_stack(stack_id, template_path, input_vars: {}, credentials: [], env_vars: {}) if stack_id.present? && template_path.present? _log.debug("Run_aysnc/delete_stack('#{stack_id}') for template: #{template_path}") diff --git a/spec/lib/terraform/runner_spec.rb b/spec/lib/terraform/runner_spec.rb index 8bf369e4..609d8309 100644 --- a/spec/lib/terraform/runner_spec.rb +++ b/spec/lib/terraform/runner_spec.rb @@ -31,8 +31,8 @@ end end - context '.run_async hello-world' do - describe '.run_async with input_var' do + context '.create_stack for hello-world' do + describe '.create_stack with input_var' do create_stub = nil retrieve_stub = nil @@ -63,21 +63,6 @@ def verify_req(req) let(:input_vars) { {'name' => 'New World'} } it "start running hello-world terraform template" do - async_response = Terraform::Runner.run_async(File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars) - expect(create_stub).to(have_been_requested.times(1)) - - response = async_response.response - expect(retrieve_stub).to(have_been_requested.times(1)) - - expect(response.status).to(eq('IN_PROGRESS'), "terraform-runner failed with:\n#{response.status}") - expect(response.stack_id).to(eq(@hello_world_create_response['stack_id'])) - expect(response.action).to(eq('CREATE')) - expect(response.stack_name).to(eq(@hello_world_create_response['stack_name'])) - expect(response.message).to(be_nil) - expect(response.details).to(be_nil) - end - - it "create_stack for hello-world terraform template" do async_response = Terraform::Runner.create_stack(File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars) expect(create_stub).to(have_been_requested.times(1)) @@ -93,7 +78,7 @@ def verify_req(req) end it "handles trailing '/' in template path" do - async_response = Terraform::Runner.run_async(File.join(__dir__, "runner/data/hello-world/"), :input_vars => input_vars) + async_response = Terraform::Runner.create_stack(File.join(__dir__, "runner/data/hello-world/"), :input_vars => input_vars) expect(create_stub).to(have_been_requested.times(1)) response = async_response.response @@ -106,10 +91,6 @@ def verify_req(req) expect(response.message).to(be_nil) expect(response.details).to(be_nil) end - - it "is aliased as run" do - expect(Terraform::Runner.method(:run)).to(eq(Terraform::Runner.method(:run_async))) - end end describe 'ResponseAsync' do @@ -141,7 +122,7 @@ def verify_req(req) end end - describe 'Stop running .run_async template job' do + describe 'Stop running .create_stack job' do create_stub = nil retrieve_stub = nil cancel_stub = nil @@ -179,8 +160,8 @@ def verify_req(req) let(:input_vars) { {} } - it "start running, then stop the before it completes" do - async_response = Terraform::Runner.run_async(File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars) + it "run .create_stack, then stop the job, before it completes" do + async_response = Terraform::Runner.create_stack(File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars) expect(create_stub).to(have_been_requested.times(1)) expect(retrieve_stub).to(have_been_requested.times(0)) @@ -209,9 +190,13 @@ def verify_req(req) expect(retrieve_stub).to(have_been_requested.times(3)) expect(response.status).to(eq('CANCELLED'), "terraform-runner failed with:\n#{response.status}") end + + it "is aliased as stop_stack" do + expect(Terraform::Runner.method(:stop_stack)).to(eq(Terraform::Runner.method(:stop_async))) + end end - describe '.run_async with Retirement action' do + describe '.delete_stack to run Retirement action' do delete_stub = nil delete_retrieve_stub = nil @@ -243,30 +228,9 @@ def verify_req(req) let(:input_vars) { {'name' => 'New World'} } - it "start running retirement for hello-world terraform template stack" do - async_response = Terraform::Runner.run_async( - File.join(__dir__, "runner/data/hello-world"), - :input_vars => input_vars, - :action => ResourceAction::RETIREMENT, - :stack_id => @hello_world_retrieve_delete_response['stack_id'] - ) - expect(delete_stub).to(have_been_requested.times(1)) - - response = async_response.response - expect(delete_retrieve_stub).to(have_been_requested.times(1)) - expect(response.stack_id).to(eq(@hello_world_delete_response['stack_id'])) - expect(response.action).to(eq('DELETE')) - expect(response.stack_name).to(eq(@hello_world_delete_response['stack_name'])) - - expect(response.status).to(eq('SUCCESS'), "terraform-runner failed with:\n#{response.status}") - expect(response.message).to(include('Destroy complete! Resources: 1 destroyed.')) - expect(response.details).to(eq({"resources" => [], "outputs" => []})) - end - - it "delete_stack for hello-world terraform template" do - stack_id = @hello_world_retrieve_delete_response['stack_id'] + it ".delete_stack to run retirement with hello-world terraform template stack" do async_response = Terraform::Runner.delete_stack( - stack_id, + @hello_world_retrieve_delete_response['stack_id'], File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars ) @@ -285,8 +249,8 @@ def verify_req(req) end end - context '.run with cloud credentials' do - describe '.run_async with amazon credential' do + context '.create_stack with cloud credentials' do + describe '.create_stack with amazon credential' do let(:amazon_cred) do params = { :userid => "manageiq-aws", @@ -343,8 +307,8 @@ def verify_req(req) let(:input_vars) { {} } - it "start running terraform template with amazon credential" do - Terraform::Runner.run_async( + it ".create_stack for terraform template with amazon credential" do + Terraform::Runner.create_stack( File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars, :credentials => [amazon_cred] @@ -353,7 +317,7 @@ def verify_req(req) end end - describe '.run_async with vSphere & ibmcloud credential' do + describe '.create_stack with vSphere & ibmcloud credential' do let(:vsphere_cred) do params = { :userid => "userid", @@ -425,8 +389,8 @@ def verify_req(req) let(:input_vars) { {} } - it "start running terraform template with vSphere & ibmcloud credentials" do - Terraform::Runner.run_async( + it ".create_stack with terraform template with vSphere & ibmcloud credentials" do + Terraform::Runner.create_stack( File.join(__dir__, "runner/data/hello-world"), :input_vars => input_vars, :credentials => [vsphere_cred, ibmcloud_cred] From 1748cd7ed39f1db3f25dc4b8a0a1d856c5cf7cd5 Mon Sep 17 00:00:00 2001 From: MANOJ PUTHRAN Date: Fri, 8 Nov 2024 22:59:20 +0530 Subject: [PATCH 26/26] don't want to modify the passed object --- .../providers/embedded_terraform/automation_manager/job.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb index bf4ee903..e90e2798 100644 --- a/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb +++ b/app/models/manageiq/providers/embedded_terraform/automation_manager/job.rb @@ -125,7 +125,7 @@ def stack_response end def decrypt_vars(input_vars) - input_vars.transform_values! { |val| val.kind_of?(String) ? ManageIQ::Password.try_decrypt(val) : val } + input_vars.transform_values { |val| val.kind_of?(String) ? ManageIQ::Password.try_decrypt(val) : val } end def configuration_script_source