lib/cfer/cli.rb in cfer-0.5.0.pre.rc1 vs lib/cfer/cli.rb in cfer-0.5.0.pre.rc2

- old
+ new

@@ -1,141 +1,150 @@ -require 'thor' +require 'cri' require 'rainbow' require 'table_print' module Cfer - class Cli < Thor - map '-v' => :version, '--version' => :version + module Cli + CFER_CLI = Cri::Command.define do + name 'cfer' + description 'Toolkit and Ruby DSL for automating infrastructure using AWS CloudFormation' + flag nil, 'verbose', 'Runs Cfer with debug output enabled' - namespace 'cfer' - class_option :verbose, type: :boolean, default: false - class_option :profile, type: :string, aliases: :p, desc: 'The AWS profile to use from your credentials file' - class_option :region, type: :string, aliases: :r, desc: 'The AWS region to use' - class_option :pretty_print, type: :boolean, default: :true, desc: 'Render JSON in a more human-friendly format' + optional :p, 'profile', 'The AWS profile to use from your credentials file' + optional :r, 'region', 'The AWS region to use' - def self.template_options - method_option :parameters, - type: :hash, - desc: 'The CloudFormation parameters to pass to the stack', - default: {} - method_option :parameter_file, - type: :string, - desc: 'A YAML or JSON file with CloudFormation parameters to pass to the stack' - method_option :parameter_environment, - type: :string, - desc: 'If parameter_file is set, will merge the subkey of this into the parameter list.' - end + optional nil, 'output-format', 'The output format to use when printing a stack [table|json]' - def self.stack_options - method_option :output_format, - type: :string, - desc: 'The output format of the stack [table|json]', - default: 'table' + optional nil, 'parameter', 'Sets a parameter to pass into the stack (format: `name:value`)', multiple: true + optional nil, 'parameter-file', 'A YAML or JSON file with CloudFormation parameters to pass to the stack' + optional nil, 'parameter-environment', 'If parameter_file is set, will merge the subkey of this into the parameter list.' + + flag :v, 'version', 'show the current version of cfer' do |value, cmd| + puts Cfer::VERSION + exit 0 + end + + flag :h, 'help', 'show help for this command' do |value, cmd| + puts cmd.help + exit 0 + end end - desc 'converge [OPTIONS] <stack-name>', 'Create or update a cloudformation stack according to the template' - #method_option :git_lock, - # type: :boolean, - # default: true, - # desc: 'When enabled, Cfer will not converge a stack in a dirty git tree' + CFER_CLI.define_command do + name 'converge' + usage 'converge [OPTIONS] <stack-name> [param=value ...]' + summary 'Create or update a cloudformation stack according to the template' - method_option :on_failure, - type: :string, - desc: 'The action to take if the stack creation fails' - method_option :follow, - aliases: :f, - type: :boolean, - default: true, - desc: 'Follow stack events on standard output while the changes are made.' - method_option :number, - type: :numeric, - default: 1, - desc: 'Prints the last (n) stack events.' - method_option :template, - aliases: :t, - type: :string, - desc: 'Override the stack filename (defaults to <stack-name>.rb)' - method_option :stack_policy, - aliases: :s, - type: :string, - desc: 'Set a new stack policy on create or update of the stack [file|url|json]' - method_option :stack_policy_during_update, - aliases: :u, - type: :string, - desc: 'Set a temporary overriding stack policy during an update [file|url|json]' - method_option :timeout, - type: :numeric, - desc: 'The timeout (in minutes) before the stack operation aborts' - method_option :s3_path, - type: :string, - desc: 'Specifies an S3 path in case the stack is created with a URL.' - method_option :force_s3, - type: :boolean, - default: false, - desc: 'Forces Cfer to upload the template to S3 and pass CloudFormation a URL.' - method_option :change, - type: :string, - desc: 'Issues updates as a Cfn change set.' - method_option :change_description, - type: :string, - desc: 'The description of this Cfn change' + optional :t, 'template', 'Override the stack filename (defaults to <stack-name>.rb)' + optional nil, 'on-failure', 'The action to take if the stack creation fails' + optional nil, 'timeout', 'The timeout (in minutes) before the stack operation aborts' + #flag nil, 'git-lock', 'When enabled, Cfer will not converge a stack in a dirty git tree' - template_options - stack_options - def converge(stack_name) - Cfer.converge! stack_name, options + optional :s, 'stack-policy', 'Set a new stack policy on create or update of the stack [file|url|json]' + optional :u, 'stack-policy-during-update', 'Set a temporary overriding stack policy during an update [file|url|json]' + + optional nil, 'change', 'Issues updates as a Cfn change set.' + optional nil, 'change-description', 'The description of this Cfn change' + + optional nil, 's3-path', 'Specifies an S3 path in case the stack is created with a URL.' + flag nil, 'force-s3', 'Forces Cfer to upload the template to S3 and pass CloudFormation a URL.' + + run do |options, args, cmd| + Cfer::Cli.fixup_options(options) + params = {} + options[:number] = 0 + options[:follow] = true + #options[:git_lock] = true if options[:git_lock].nil? + + Cfer::Cli.extract_parameters(params, args).each do |arg| + Cfer.converge! arg, options.merge(parameters: params) + end + end end - desc 'describe <stack>', 'Fetches and prints information about a CloudFormation' - stack_options - def describe(stack_name) - Cfer.describe! stack_name, options + CFER_CLI.define_command do + name 'generate' + usage 'generate [OPTIONS] <template.rb> [param=value ...]' + summary 'Generates a CloudFormation template by evaluating a Cfer template' + + flag nil, 'minified', 'Minifies the JSON when printing output.' + + run do |options, args, cmd| + Cfer::Cli.fixup_options(options) + params = {} + options[:pretty_print] = !options[:minified] + + Cfer::Cli.extract_parameters(params, args).each do |arg| + Cfer.generate! arg, options.merge(parameters: params) + end + end end - desc 'delete <stack>', 'Deletes a CloudFormation stack' - stack_options - def delete(stack_name) - Cfer.delete! stack_name, options + CFER_CLI.define_command do + name 'tail' + usage 'tail <stack>' + summary 'Follows stack events on standard output as they occur' + + flag :f, 'follow', 'Follow stack events on standard output while the changes are made.' + optional :n, 'number', 'Prints the last (n) stack events.', type: :number + + run do |options, args, cmd| + Cfer::Cli.fixup_options(options) + args.each do |arg| + Cfer.tail! arg, options + end + end end - desc 'tail <stack>', 'Follows stack events on standard output as they occur' - method_option :follow, - aliases: :f, - type: :boolean, - default: false, - desc: 'Follow stack events on standard output while the changes are made.' - method_option :number, - aliases: :n, - type: :numeric, - default: 10, - desc: 'Prints the last (n) stack events.' - stack_options - def tail(stack_name) - Cfer.tail! stack_name, options + CFER_CLI.define_command do + name 'estimate' + usage 'estimate [OPTIONS] <template.rb>' + summary 'Prints a link to the Amazon cost caculator estimating the cost of the resulting CloudFormation stack' + + run do |options, args, cmd| + Cfer::Cli.fixup_options(options) + args.each do |arg| + Cfer.estimate! arg, options + end + end end - desc 'generate [OPTIONS] <template.rb>', 'Generates a CloudFormation template by evaluating a Cfer template' - long_desc <<-LONGDESC - Generates a CloudFormation template by evaluating a Cfer template. - LONGDESC - template_options - def generate(tmpl) - Cfer.generate! tmpl, options + CFER_CLI.define_command do + name 'describe' + usage 'describe <stack>' + summary 'Fetches and prints information about a CloudFormation' + + run do |options, args, cmd| + Cfer::Cli.fixup_options(options) + options[:pretty_print] ||= true + args.each do |arg| + Cfer.describe! arg, options + end + end end - desc 'estimate [OPTIONS] <template.rb>', 'Prints a link to the Amazon cost caculator estimating the cost of the resulting CloudFormation stack' - long_desc <<-LONGDESC - LONGDESC - template_options - def estimate(tmpl) - Cfer.estimate! tmpl, options + CFER_CLI.define_command do + name 'delete' + usage 'delete <stack>' + summary 'Deletes a CloudFormation stack' + + run do |options, args, cmd| + Cfer::Cli.fixup_options(options) + options[:number] = 0 + options[:follow] = true + args.each do |arg| + Cfer.delete! arg, options + end + end end + CFER_CLI.add_command Cri::Command.new_basic_help + def self.main(args) Cfer::LOGGER.debug "Cfer version #{Cfer::VERSION}" begin - Cli.start(args) + CFER_CLI.run(args) rescue Aws::Errors::NoSuchProfileError => e Cfer::LOGGER.error "#{e.message}. Specify a valid profile with the --profile option." exit 1 rescue Aws::Errors::MissingRegionError => e Cfer::LOGGER.error "Missing region. Specify a valid AWS region with the --region option, or use the AWS_REGION environment variable." @@ -160,19 +169,34 @@ end exit 1 end end - desc 'version', 'Prints the current version of Cfer' - def version - puts Cfer::VERSION + PARAM_REGEX=/(?<name>.+?)=(?<value>.+)/ + def self.extract_parameters(params, args) + args.reject do |arg| + if match = PARAM_REGEX.match(arg) + name = match[:name] + value = match[:value] + Cfer::LOGGER.debug "Extracting parameter #{name}: #{value}" + params[name] = value + end + end end - private - - def cfn(opts = {}) - @cfn ||= opts + # Convert options of the form `:'some-option'` into `:some_option`. + # Cfer internally uses the latter format, while Cri options must be specified as the former. + # This approach is better than changing the names of all the options in the CLI. + def self.fixup_options(opts) + opts.keys.map(&:to_s).each do |k| + old_k = k.to_sym + new_k = k.gsub('-', '_').to_sym + val = opts[old_k] + opts[new_k] = (Integer(val) rescue Float(val) rescue val) + opts.delete(old_k) if old_k != new_k + end end + private def self.format_backtrace(bt) "Backtrace: #{bt.join("\n from ")}" end def self.exit_on_failure?