lib/aws/cfn/dsl/template.rb in aws-cfn-dsl-0.6.0 vs lib/aws/cfn/dsl/template.rb in aws-cfn-dsl-0.7.0
- old
+ new
@@ -89,173 +89,9 @@
else
"#{line}\n"
end
end
- def exec!(argv=ARGV)
- @opts = Slop.parse(help: true) do
- banner "usage: #{$PROGRAM_NAME} <expand|diff|validate|create|update|delete>"
- on :o, :output=, 'The template file to save this DSL expansion to', as: String
- end
-
- action = argv[0] || 'expand'
- unless %w(expand diff validate create update delete).include? action
- $stderr.puts "usage: #{$PROGRAM_NAME} <expand|diff|validate|create|update|delete>"
- exit(2)
- end
- unless (argv & %w(--template-file --template-url)).empty?
- $stderr.puts "#{File.basename($PROGRAM_NAME)}: The --template-file and --template-url command-line options are not allowed. (You are running the template itself right now ... !)"
- exit(2)
- end
-
- # Find parameters where extension attribute :Immutable is true then remove it from the
- # cfn template since we can't pass it to CloudFormation.
- immutable_parameters = excise_parameter_attribute!(:Immutable)
-
- # Tag CloudFormation stacks based on :Tags defined in the template
- cfn_tags = excise_tags!
- # The command line string looks like: --tag "Key=key; Value=value" --tag "Key2=key2; Value2=value"
- cfn_tags_options = cfn_tags.sort.map { |tag| ["--tag", "Key=%s; Value=%s" % tag.split('=')] }.flatten
-
- # example: <template.rb> cfn-create-stack my-stack-name --parameters "Env=prod" --region eu-west-1
- # Execute the AWS CLI cfn-cmd command to validate/create/update a CloudFormation stack.
- if action == 'diff' or (action == 'expand' and not nopretty)
- template_string = JSON.pretty_generate(self)
- else
- template_string = JSON.generate(self)
- end
-
- if action == 'expand'
- # Write the pretty-printed JSON template to stdout and exit. [--nopretty] option writes output with minimal whitespace
- # example: <template.rb> expand --parameters "Env=prod" --region eu-west-1 --nopretty
- if @opts[:output]
- dest = @opts[:output]
- if File.directory? dest
- file = File.basename $PROGRAM_NAME
- file.gsub!(%r'\.rb', '.json')
- dest = File.join dest, file
- end
- IO.write(dest, template_string)
- else
- puts template_string
- end
- exit(true)
- end
-
- temp_file = File.absolute_path("#{$PROGRAM_NAME}.expanded.json")
- File.write(temp_file, template_string)
-
- cmdline = ['cfn-cmd'] + argv + ['--template-file', temp_file] + cfn_tags_options
-
- case action
- when 'diff'
- # example: <template.rb> diff my-stack-name --parameters "Env=prod" --region eu-west-1
- # Diff the current template for an existing stack with the expansion of this template.
-
- # The --parameters and --tag options were used to expand the template but we don't need them anymore. Discard.
- _, cfn_options = extract_options(argv[1..-1], %w(), %w(--parameters --tag))
-
- # Separate the remaining command-line options into options for 'cfn-cmd' and options for 'diff'.
- cfn_options, diff_options = extract_options(cfn_options, %w(),
- %w(--stack-name --region --parameters --connection-timeout -I --access-key-id -S --secret-key -K --ec2-private-key-file-path -U --url))
-
- # If the first argument is a stack name then shift it from diff_options over to cfn_options.
- if diff_options[0] && !(/^-/ =~ diff_options[0])
- cfn_options.unshift(diff_options.shift)
- end
-
- # Run CloudFormation commands to describe the existing stack
- cfn_options_string = cfn_options.map { |arg| "'#{arg}'" }.join(' ')
- old_template_raw = exec_capture_stdout("cfn-cmd cfn-get-template #{cfn_options_string}")
- # ec2 template output is not valid json: TEMPLATE "<json>\n"\n
- old_template_object = JSON.parse(old_template_raw[11..-3])
- old_template_string = JSON.pretty_generate(old_template_object)
- old_stack_attributes = exec_describe_stack(cfn_options_string)
- old_tags_string = old_stack_attributes["TAGS"]
- old_parameters_string = old_stack_attributes["PARAMETERS"]
-
- # Sort the tag strings alphabetically to make them easily comparable
- old_tags_string = (old_tags_string || '').split(';').sort.map { |tag| %Q(TAG "#{tag}"\n) }.join
- tags_string = cfn_tags.sort.map { |tag| "TAG \"#{tag}\"\n" }.join
-
- # Sort the parameter strings alphabetically to make them easily comparable
- old_parameters_string = (old_parameters_string || '').split(';').sort.map { |param| %Q(PARAMETER "#{param}"\n) }.join
- parameters_string = parameters.sort.map { |key, value| "PARAMETER \"#{key}=#{value}\"\n" }.join
-
- # Diff the expanded template with the template from CloudFormation.
- old_temp_file = File.absolute_path("#{$PROGRAM_NAME}.current.json")
- new_temp_file = File.absolute_path("#{$PROGRAM_NAME}.expanded.json")
- File.write(old_temp_file, old_tags_string + old_parameters_string + old_template_string)
- File.write(new_temp_file, tags_string + parameters_string + template_string)
-
- # Compare templates
- system(*["diff"] + diff_options + [old_temp_file, new_temp_file])
-
- File.delete(old_temp_file)
- File.delete(new_temp_file)
-
- exit(true)
-
- when 'cfn-validate-template'
- # The cfn-validate-template command doesn't support --parameters so remove it if it was provided for template expansion.
- _, cmdline = extract_options(cmdline, %w(), %w(--parameters --tag))
-
- when 'cfn-update-stack'
- # Pick out the subset of cfn-update-stack options that apply to cfn-describe-stacks.
- cfn_options, other_options = extract_options(argv[1..-1], %w(),
- %w(--stack-name --region --connection-timeout -I --access-key-id -S --secret-key -K --ec2-private-key-file-path -U --url))
-
- # If the first argument is a stack name then shift it over to cfn_options.
- if other_options[0] && !(/^-/ =~ other_options[0])
- cfn_options.unshift(other_options.shift)
- end
-
- # Run CloudFormation command to describe the existing stack
- cfn_options_string = cfn_options.map { |arg| "'#{arg}'" }.join(' ')
- old_stack_attributes = exec_describe_stack(cfn_options_string)
-
- # If updating a stack and some parameters are marked as immutable, fail if the new parameters don't match the old ones.
- if not immutable_parameters.empty?
- old_parameters_string = old_stack_attributes["PARAMETERS"]
- old_parameters = Hash[(old_parameters_string || '').split(';').map { |pair| pair.split('=', 2) }]
- new_parameters = parameters
-
- immutable_parameters.sort.each do |param|
- if old_parameters[param].to_s != new_parameters[param].to_s
- $stderr.puts "Error: cfn-update-stack may not update immutable parameter " +
- "'#{param}=#{old_parameters[param]}' to '#{param}=#{new_parameters[param]}'."
- exit(false)
- end
- end
- end
-
- # Tags are immutable in CloudFormation. The cfn-update-stack command doesn't support --tag options, so remove
- # the argument (if it exists) and validate against the existing stack to ensure tags haven't changed.
- # Compare the sorted arrays for an exact match
- old_cfn_tags = old_stack_attributes['TAGS'].split(';').sort rescue [] # Use empty Array if .split fails
- if cfn_tags != old_cfn_tags
- $stderr.puts "CloudFormation stack tags do not match and cannot be updated. You must either use the same tags or create a new stack." +
- "\n" + (old_cfn_tags - cfn_tags).map {|tag| "< #{tag}" }.join("\n") +
- "\n" + "---" +
- "\n" + (cfn_tags - old_cfn_tags).map {|tag| "> #{tag}"}.join("\n")
- exit(false)
- end
- _, cmdline = extract_options(cmdline, %w(), %w(--tag))
- end
-
- # Execute command cmdline
- unless system(*cmdline)
- $stderr.puts "\nExecution of 'cfn-cmd' failed. To facilitate debugging, the generated JSON template " +
- "file was not deleted. You may delete the file manually if it isn't needed: #{temp_file}"
- exit(false)
- end
-
- File.delete(temp_file)
-
- exit(true)
- end
-
-
end
end
end
end