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 5 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
92 changes: 92 additions & 0 deletions app/models/dialog/terraform_template_service_dialog.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
require "JSON"
putmanoj marked this conversation as resolved.
Show resolved Hide resolved

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)
if terraform_template.present?
add_template_variables_group(tab, 0, terraform_template)
end
if extra_vars.present?
add_variables_group(tab, 1, 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
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 = var_info["name"]
value = var_info["default"]
value = value.to_json if [Hash, Array].include?(value.class)
required = var_info["required"]
readonly = var_info["immutable"]
hidden = var_info["hidden"]
label = var_info["label"]
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
description = var_info["description"]
if description.blank?
description = key
end
putmanoj marked this conversation as resolved.
Show resolved Hide resolved

# TODO: use these when adding variable field
# type = var_info["type"]
# secured = var_info["secured"]

if hidden == true
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
_log.info("Not adding text-box for hidden variable: #{key}")
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
else
add_variable_field(key, value, dialog_group, index, label, description, required, readonly)
end
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|
value = value.to_json if [Hash, Array].include?(value.class)
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)
group.dialog_fields.build(
:type => "DialogFieldTextBox",
:name => "param_#{key}",
: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,11 @@ 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.update_repo - already done through checkout_git_repo
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
git_repository.with_worktree do |worktree|
worktree.ref = scm_branch

Expand All @@ -85,22 +88,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.info("Checking out git repository to #{git_checkout_tempdir}...")
checkout_git_repository(git_checkout_tempdir)
git_checkout_tempdir
end

# clean temp dir
def cleanup_git_repo(git_checkout_tempdir)
_log.info("Cleaning up git repository checked out at #{git_checkout_tempdir}...")
FileUtils.rm_rf(git_checkout_tempdir)
rescue Errno::ENOENT
nil
end
end
22 changes: 22 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].deep_merge!(dialog_ids)
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
service_template.create_resource_actions(config_info)
end
end
Expand All @@ -41,4 +44,23 @@ def terraform_template(action)

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

def create_dialogs(config_info)
# [:provision, :retirement, :reconfigure].each_with_object({}) do |action, hash|
[:provision,].each_with_object({}) do |action, hash|
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
info = config_info[action]
# next unless new_dialog_required?(info)

template_name = SecureRandom.alphanumeric # TODO: get template_name instead

new_dialog_name = info.key?(:new_dialog_name) ? info[:new_dialog_name] : "Dialog-#{template_name}"

hash[action] = {:dialog_id => create_new_dialog(new_dialog_name, terraform_template(action), info[:extra_vars]).id}
end
end

def create_new_dialog(dialog_name, terraform_template, extra_vars)
Dialog::TerraformTemplateServiceDialog.create_dialog(dialog_name, terraform_template, extra_vars)
end
private :create_new_dialog
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
end
27 changes: 27 additions & 0 deletions lib/terraform/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@ 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 [Terraform::Runner::VariablesResponse] Response object of terraform-runner api/template/variables
def parse_template_variables(template_path)
template_variables(template_path)
end

# =================================================
# TerraformRunner Stack-API interaction methods
# =================================================
Expand Down Expand Up @@ -180,6 +187,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.info("prase template: #{template_path}")
encoded_zip_file = encoded_zip_from_directory(template_path)

# TODO: use tags,env_vars
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}")
Terraform::Runner::VariablesResponse.parsed_response(http_response)
end

def jwt_token
require "jwt"

Expand Down
29 changes: 29 additions & 0 deletions lib/terraform/runner/variables_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require 'json'

module Terraform
class Runner
# Response object designed for holding full response from terraform-runner api/template/variables
class VariablesResponse
include Vmdb::Logging

attr_reader :template_input_params, :template_output_params, :terraform_version

# @return [String] Extracted attributes from the JSON response body object
def self.parsed_response(http_response)
data = JSON.parse(http_response.body)
_log.debug("data : #{data}")
Terraform::Runner::VariablesResponse.new(
:template_input_params => data['template_input_params'],
:template_output_params => data['template_output_params'],
:terraform_version => data['terraform_version']
)
putmanoj marked this conversation as resolved.
Show resolved Hide resolved
end

def initialize(template_input_params: nil, template_output_params: nil, terraform_version: nil)
@template_input_params = template_input_params
@template_output_params = template_output_params
@terraform_version = terraform_version
end
end
end
end
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"
}
44 changes: 44 additions & 0 deletions spec/lib/terraform/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,48 @@ 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))

expect(response.template_input_params.length).to(eq(1))
input_var = response.template_input_params[0]
expect(input_var["name"]).to(eq("name"))
expect(input_var["label"]).to(eq("name"))
expect(input_var["type"]).to(eq("string"))
expect(input_var["default"]).to(eq("World"))
expect(input_var["required"]).to(eq(true))
expect(input_var["secured"]).to(eq(false))
putmanoj marked this conversation as resolved.
Show resolved Hide resolved

expect(response.template_output_params.length).to(eq(1))
output_var = response.template_output_params[0]
expect(output_var["name"]).to(eq("greeting"))
expect(output_var["label"]).to(eq("greeting"))
expect(output_var["secured"]).to(eq(false))
end
end
end
end
Loading