Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Parse terraform template & create new service dialog with parsed input vars #75

Merged
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
4b6a0eb
add support for creating new diaglogs with extra-vars for terraform t…
putmanoj Sep 20, 2024
067f4f4
add parse_template_variables method in Terraform::Runner
putmanoj Sep 20, 2024
53909c9
add test for parse_template_variables
putmanoj Sep 22, 2024
e377dbe
parse terraform template and store input/output vars info
putmanoj Sep 23, 2024
1573aac
add terraform template input variables into new dialog
putmanoj Sep 23, 2024
30a006b
update spec - match hash with include, instead match to each key value
putmanoj Sep 24, 2024
398081a
update spec - match with be_kind_of(Hash)
putmanoj Sep 24, 2024
d13a115
review comments - move require into method & other changes
putmanoj Sep 24, 2024
f934749
improve position value for dialog groups
putmanoj Sep 24, 2024
2a983a1
review comment - code improvements
putmanoj Sep 24, 2024
6020047
minor - remove commented code & _log.info to _log.debug
putmanoj Sep 24, 2024
32c8387
fix error log message
putmanoj Sep 25, 2024
e444122
minor - private method defination
putmanoj Sep 25, 2024
72d7c7e
use same dialog from :provision action with retirement & reconfigure …
putmanoj Sep 25, 2024
fca9d3d
remove 'param_' prefix for dialog var names
putmanoj Sep 25, 2024
ea7945d
code-review-fix: no need new VariablesResponse class, no translation …
putmanoj Sep 25, 2024
6431953
minor - code formating & remove unused method
putmanoj Sep 26, 2024
a24a5f6
fix typo
putmanoj Sep 26, 2024
5444610
code review : code improvement
putmanoj Sep 26, 2024
4fe3adf
add stub_request for terraform_runner:api/template/variables
putmanoj Sep 26, 2024
f965829
code review fix: don't need instance variables
putmanoj Sep 26, 2024
180af2c
no rm dir requried, if git_checkout_tempdir is nil
putmanoj Sep 27, 2024
82b6095
minor - just raise is enough
putmanoj Sep 27, 2024
9d0748c
stub ENV var TERRAFORM_RUNNER_URL in spec
putmanoj Sep 27, 2024
8945d55
fix for review comments
putmanoj Sep 27, 2024
aacd630
add support to update catalog item
putmanoj Sep 30, 2024
9719425
instead of global constant use let defination within test
putmanoj Oct 1, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions app/models/dialog/terraform_template_service_dialog.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
class Dialog
class TerraformTemplateServiceDialog
def self.create_dialog(label, terraform_template, extra_vars)
new.create_dialog(label, terraform_template, extra_vars)
end

# This dialog is to be used by a terraform template service item
def create_dialog(label, terraform_template, extra_vars)
Dialog.new(:label => label, :buttons => "submit,cancel").tap do |dialog|
tab = dialog.dialog_tabs.build(:display => "edit", :label => "Basic Information", :position => 0)
position = 0
if terraform_template.present?
add_template_variables_group(tab, position, terraform_template)
position += 1
end
if extra_vars.present?
add_variables_group(tab, position, extra_vars)
end
dialog.save!
end
end

private

def add_template_variables_group(tab, position, terraform_template)
agrare marked this conversation as resolved.
Show resolved Hide resolved
require "json"
template_info = JSON.parse(terraform_template.payload)
input_vars = template_info["input_vars"]

return if input_vars.nil?

tab.dialog_groups.build(
:display => "edit",
:label => "Terraform Template Variables",
:position => position
).tap do |dialog_group|
input_vars.each_with_index do |(var_info), index|
key, value, required, readonly, hidden, label, description = var_info.values_at(
"name", "default", "required", "immutable", "hidden", "label", "description"
)
# TODO: use these when adding variable field
# type, secured = var_info.values_at("type", "secured")

next if hidden

add_variable_field(
key, value, dialog_group, index, label, description, required, readonly
)
end
end
end

def add_variables_group(tab, position, extra_vars)
tab.dialog_groups.build(
:display => "edit",
:label => "Extra Variables",
:position => position
).tap do |dialog_group|
extra_vars.transform_values { |val| val[:default] }.each_with_index do |(key, value), index|
add_variable_field(key, value, dialog_group, index, key, key, false, false)
end
end
end

def add_variable_field(key, value, group, position, label, description, required, read_only)
value = value.to_json if [Hash, Array].include?(value.class)
description = key if description.blank?

group.dialog_fields.build(
:type => "DialogFieldTextBox",
:name => key.to_s,
:data_type => "string",
:display => "edit",
:required => required,
:default_value => value,
:label => label,
:description => description,
:reconfigurable => true,
:position => position,
:dialog_group => group,
:read_only => read_only
)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,10 @@ def self.template_name_from_git_repo_url(git_repo_url, relative_path)
def find_templates_in_git_repo
template_dirs = {}

# checkout repo, for sending files to terraform-runner to parse for input/ouput vars.
git_checkout_tempdir = checkout_git_repo

# traverse through files in git-worktree
git_repository.update_repo
git_repository.with_worktree do |worktree|
worktree.ref = scm_branch

Expand All @@ -85,22 +87,51 @@ def find_templates_in_git_repo
.group_by { |file| File.dirname(file) }
.select { |_dir, files| files.any? { |f| f.end_with?(".tf", ".tf.json") } }
.transform_values! { |files| files.map { |f| File.basename(f) } }
.each do |parent_dir, files|
name = self.class.template_name_from_git_repo_url(git_repository.url, parent_dir)
.each do |relative_path, files|
name = self.class.template_name_from_git_repo_url(git_repository.url, relative_path)

template_full_path = File.join(git_checkout_tempdir, relative_path)
agrare marked this conversation as resolved.
Show resolved Hide resolved

# TODO: add parsing for input/output vars
input_vars = nil
output_vars = nil
input_vars, output_vars, terraform_version = parse_vars_in_template(template_full_path)

template_dirs[name] = {
:relative_path => parent_dir,
:files => files,
:input_vars => input_vars,
:output_vars => output_vars
:relative_path => relative_path,
:files => files,
:input_vars => input_vars,
:output_vars => output_vars,
:terraform_version => terraform_version,
}
end
end

template_dirs
rescue => error
_log.error("Failing scaning for terraform templates in the git repo: #{error}")
raise error
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
ensure
cleanup_git_repo(git_checkout_tempdir)
end

# Parse template and return input-vars, output-vars & terraform-version
def parse_vars_in_template(template_path)
response = Terraform::Runner.parse_template_variables(template_path)
return response['template_input_params'], response['template_output_params'], response['terraform_version']
end

# checkout git repo to temp dir
def checkout_git_repo
git_checkout_tempdir = Dir.mktmpdir("embedded-terraform-runner-git")

_log.debug("Checking out git repository to #{git_checkout_tempdir}...")
checkout_git_repository(git_checkout_tempdir)
git_checkout_tempdir
end
Comment on lines +122 to +128
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised there isn't a way to do this in the base EmbeddedAutomationManager::ConfigurationScript class, ideally we could have a with_git_repository which took a block and automatically handled the tempdir creation and cleanup.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, let me do this another PR, rather than is PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised as well - thought we had this functionality, because all git repositories will need this (since we use bare repos)


# clean temp dir
def cleanup_git_repo(git_checkout_tempdir)
_log.debug("Cleaning up git repository checked out at #{git_checkout_tempdir}...")
FileUtils.rm_rf(git_checkout_tempdir)
rescue Errno::ENOENT
nil
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ class ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template < Mana
has_many :stacks, :class_name => "ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Stack", :foreign_key => :configuration_script_base_id, :inverse_of => :configuration_script_payload, :dependent => :nullify

def run(vars = {}, _userid = nil)
env_vars = vars.delete(:env) || {}
env_vars = vars.delete(:env) || {}
credentials = vars.delete(:credentials)

self.class.module_parent::Job.create_job(self, env_vars, vars, credentials).tap(&:signal_start)
Expand Down
31 changes: 31 additions & 0 deletions app/models/service_template_terraform_template.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def self.create_catalog_item(options, _auth_user)

transaction do
create_from_options(options).tap do |service_template|
dialog_ids = service_template.send(:create_dialogs, config_info)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use send here? This method doesn't appear to be private and the method name isn't dynamic so I think you can just

Suggested change
dialog_ids = service_template.send(:create_dialogs, config_info)
dialog_ids = service_template.create_dialogs(config_info)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This came from ansible implementation, myself was puzzled why 'send' ?
simply did not fix what was not broken

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 That's because their create_dialogs instance method is private, that isn't a good reason to use send but that is why it is needed there and not here.

config_info.deep_merge!(dialog_ids)
service_template.options[:config_info] = config_info
service_template.create_resource_actions(config_info)
end
end
Expand All @@ -41,4 +44,32 @@ def terraform_template(action)

ManageIQ::Providers::EmbeddedTerraform::AutomationManager::Template.find(template_id)
end

def create_dialogs(config_info)
dialog_hash = {}

info = config_info[:provision]
if info
# create new dialog, if required for :provision action
if info.key?(:new_dialog_name) && !info.key?(:dialog_id)
provision_dialog_id = create_new_dialog(info[:new_dialog_name], terraform_template(:provision), info[:extra_vars]).id
dialog_hash[:provision] = {:dialog_id => provision_dialog_id}
else
provision_dialog_id = info[:dialog_id]
end

# For :retirement & :reconfigure, we use the same dialog as in :provision action
dialog_hash = [:retirement, :reconfigure].each_with_object(dialog_hash) do |action, hash|
hash[action] = {:dialog_id => provision_dialog_id}
end
end

dialog_hash
end

private

def create_new_dialog(dialog_name, terraform_template, extra_vars)
Dialog::TerraformTemplateServiceDialog.create_dialog(dialog_name, terraform_template, extra_vars)
end
end
28 changes: 28 additions & 0 deletions lib/terraform/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,14 @@ def fetch_result_by_stack_id(stack_id)
retrieve_stack_job(stack_id)
end

# 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,
# - the response object had template_input_params, template_output_params and terraform_version
def parse_template_variables(template_path)
template_variables(template_path)
end

# =================================================
# TerraformRunner Stack-API interaction methods
# =================================================
Expand Down Expand Up @@ -180,6 +188,26 @@ def encoded_zip_from_directory(template_path)
end
end

# Parse Variables in Terraform Template
def template_variables(
template_path
)
Comment on lines +192 to +194
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a very long argument list :) can we one-line this?

Suggested change
def template_variables(
template_path
)
def template_variables(template_path)

_log.debug("prase template: #{template_path}")
encoded_zip_file = encoded_zip_from_directory(template_path)

payload = {
:templateZipFile => encoded_zip_file,
}

http_response = terraform_runner_client.post(
"api/template/variables",
*json_post_arguments(payload)
)
agrare marked this conversation as resolved.
Show resolved Hide resolved

_log.debug("==== http_response.body: \n #{http_response.body}")
JSON.parse(http_response.body)
end

def jwt_token
require "jwt"

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"template_input_params": [
{
"name": "name",
"label": "name",
"type": "string",
"description": "",
"required": true,
"secured": false,
"hidden": false,
"immutable": false,
"default": "World"
}
],
"template_output_params": [
{
"name": "greeting",
"label": "greeting",
"description": "",
"secured": false,
"hidden": false
}
],
"terraform_version": ">= 1.1.0"
}
56 changes: 56 additions & 0 deletions spec/lib/terraform/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,60 @@ def verify_req(req)
end
end
end

context '.parse_template_variables hello-world' do
describe '.parse_template_variables input/output vars' do
template_variables_stub = nil

def verify_req(req)
body = JSON.parse(req.body)
expect(body).to(have_key('templateZipFile'))
end

before do
ENV["TERRAFORM_RUNNER_URL"] = "https://1.2.3.4:7000"
putmanoj marked this conversation as resolved.
Show resolved Hide resolved

@hello_world_variables_response = JSON.parse(File.read(File.join(__dir__, "runner/data/responses/hello-world-variables-success.json")))
putmanoj marked this conversation as resolved.
Show resolved Hide resolved

template_variables_stub = stub_request(:post, "https://1.2.3.4:7000/api/template/variables")
.with { |req| verify_req(req) }
.to_return(
:status => 200,
:body => @hello_world_variables_response.to_json
)
end

it "parse input/output params from hello-world terraform template" do
response = Terraform::Runner.parse_template_variables(File.join(__dir__, "runner/data/hello-world"))
expect(template_variables_stub).to(have_been_requested.times(1))

template_input_params = response['template_input_params']
expect(template_input_params.length).to(eq(1))
expect(template_input_params.first).to be_kind_of(Hash).and include(
"name" => "name",
"label" => "name",
"type" => "string",
"description" => "",
"required" => true,
"secured" => false,
"hidden" => false,
"immutable" => false,
"default" => "World"
)

template_output_params = response['template_output_params']
expect(template_output_params.length).to(eq(1))
expect(template_output_params.first).to be_kind_of(Hash).and include(
"name" => "greeting",
"label" => "greeting",
"description" => "",
"secured" => false,
"hidden" => false
)

terraform_version = response['terraform_version']
expect(terraform_version).to eq('>= 1.1.0')
end
end
end
end
Loading
Loading