From f7f23387bcab301814178335874153f1181f5b58 Mon Sep 17 00:00:00 2001 From: neillturner Date: Mon, 30 Nov 2015 10:11:59 +0000 Subject: [PATCH] credentials file --- .gitignore | 2 +- README.rdoc => README.md | 93 +++++++++++++++++-------- knife-cfn.gemspec | 4 +- lib/chef/knife/cfn_base.rb | 117 +++++++++++++++++++++++++++----- lib/chef/knife/cfn_create.rb | 52 +++++++------- lib/chef/knife/cfn_delete.rb | 82 ++++++++++++++++------ lib/chef/knife/cfn_describe.rb | 40 +++++++++++ lib/chef/knife/cfn_events.rb | 66 ++++++++++++++---- lib/chef/knife/cfn_outputs.rb | 40 +++++++++++ lib/chef/knife/cfn_resources.rb | 40 +++++++++++ lib/chef/knife/cfn_update.rb | 92 ++++++++++++++++++------- lib/chef/knife/cfn_validate.rb | 71 ++++++++++++++----- 12 files changed, 550 insertions(+), 149 deletions(-) rename README.rdoc => README.md (52%) diff --git a/.gitignore b/.gitignore index 86339a0..93225d8 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -*.gem \ No newline at end of file +# *.gem \ No newline at end of file diff --git a/README.rdoc b/README.md similarity index 52% rename from README.rdoc rename to README.md index 5f7855d..2f4ef45 100644 --- a/README.rdoc +++ b/README.md @@ -1,10 +1,10 @@ -= Knife CFN - -= DESCRIPTION: +Knife CFN +========= This is a Knife plugin for AWS Cloud Formation. This plugin gives knife the ability to validate, create, describe, and delete stacks. -= INSTALLATION: +Installation +------------ Be sure you are running the latest version Chef. Versions earlier than 0.10.0 don't support plugins: @@ -16,64 +16,101 @@ This plugin is distributed as a Ruby Gem. To install it, run: Depending on your system's configuration, you may need to run this command with root privileges. -= CONFIGURATION: +Configuration +------------- + +In order to communicate with the Amazon's CloudFormation API you will have to tell Knife about your AWS Access Key and Secret Access Key. The easiest way to accomplish this is to create some entries in your `knife.rb` file: + +```ruby +knife[:aws_access_key_id] = "Your AWS Access Key ID" +knife[:aws_secret_access_key] = "Your AWS Secret Access Key" +``` + +If your `knife.rb` file will be checked into a SCM system (ie readable by others) you may want to read the values from environment variables: + +```ruby +knife[:aws_access_key_id] = ENV['AWS_ACCESS_KEY_ID'] +knife[:aws_secret_access_key] = ENV['AWS_SECRET_ACCESS_KEY'] +# Optional if you're using Amazon's STS +knife[:aws_session_token] = ENV['AWS_SESSION_TOKEN'] +``` + +You also have the option of passing your AWS API Key/Secret into the individual knife subcommands using the `-A` (or `--aws-access-key-id`) `-K` (or `--aws-secret-access-key`) command options + +```bash +# provision a new stack +$ knife cfn create test -f test.stack +``` + +If you are working with Amazon's command line tools, there is a good chance +you already have a file with these keys somewhere in this format: + + AWSAccessKeyId=Your AWS Access Key ID + AWSSecretKey=Your AWS Secret Access Key -In order to communicate with the Amazon's CloudFormation API you will have to tell Knife about your AWS Access Key and Secret Access Key. The easiest way to accomplish this is to create some entries in your knife.rb file: - knife[:aws_access_key_id] = "Your AWS Access Key ID" - knife[:aws_secret_access_key] = "Your AWS Secret Access Key" +The new config file format used by Amazon's command line tools is also supported: -If your knife.rb file will be checked into a SCM system (ie readable by others) you may want to read the values from environment variables: + [default] + aws_access_key_id = Your AWS Access Key ID + aws_secret_access_key = Your AWS Secret Access Key - knife[:aws_access_key_id] = "#{ENV['AWS_ACCESS_KEY_ID']}" - knife[:aws_secret_access_key] = "#{ENV['AWS_SECRET_ACCESS_KEY']}" +In this case, you can point the aws_credential_file option to +this file in your knife.rb file, like so: -You also have the option of passing your AWS API Key/Secret into the individual knife subcommands using the -A (or --aws-access-key-id) -K (or --aws-secret-access-key) command options +```ruby +knife[:aws_credential_file] = "/path/to/credentials/file/in/above/format" +``` - # provision a new stack - knife cfn create test -f test.stack +If you have multiple profiles in your credentials file you can define which +profile to use. The `default` profile will be used if not supplied, +```ruby +knife[:aws_profile] = "personal" +``` -= SUBCOMMANDS: +Subcommands +----------- This plugin provides the following Knife subcommands. Specific command options can be found by invoking the subcommand with a --help flag -== knife cfn validate +#### `knife cfn validate` -Validates a template file of template URL. +Validates a template file of template URL. -== knife cfn create +#### `knife cfn create` -Create a cloud formation stack from a template file or Template URL. +Create a cloud formation stack from a template file or Template URL. -== knife cfn update +#### `knife cfn update` -Update a cloud formation stack from a template file or Template URL. +Update a cloud formation stack from a template file or Template URL. -== knife cfn delete +#### `knife cfn delete` -Deletes a running cloud formation stack. +Deletes a running cloud formation stack. -== knife cfn describe [-l / --long ] [stack name] +#### `knife cfn describe [-l / --long ] [stack name]` Outputs the name, status, creation time and rollback status of a stack, or all stacks if stack name is omitted. The --long (-l) parameter shows stack IDs (ARN) instead of friendly names. -== knife cfn events [ stack name ] +#### `knife cfn events [ stack name ]` Outputs a list of events for a stack name. -== knife cfn resources [ stack name ] [ logical resource id ] +#### `knife cfn resources [ stack name ] [ logical resource id ]` Outputs the logical resource ID, physical resource ID, resource type and status for all resources of a stack. If logical resource id is specified, then only the details of that resource is shown. A logical resource ID is reference given to a resource in the cloudformation template, under the "Resources" section. -== knife cfn outputs [ -o ] [ stack name ] +#### `knife cfn outputs [ -o ] [ stack name ]` Outputs a list of outputs for a stack name. If -o option is specified, then output will be formatted in the same syntax as parameters for cfn create / update -= LICENSE: +License +------- Author:: Neill Turner (neillwturner@gmail.com) Copyright:: Copyright (c) 2012 EC2Dream. diff --git a/knife-cfn.gemspec b/knife-cfn.gemspec index bbcbc78..3eeb066 100644 --- a/knife-cfn.gemspec +++ b/knife-cfn.gemspec @@ -2,7 +2,7 @@ Gem::Specification.new do |s| s.name = "knife-cfn" - s.version = "0.1.10" + s.version = "0.1.11" s.summary = "CloudFormation Support for Knife" s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.author = "Neill Turner" @@ -11,6 +11,6 @@ Gem::Specification.new do |s| s.homepage = 'https://github.com/neillturner/knife-cfn' s.files = Dir["lib/**/*"] s.rubygems_version = "1.6.2" - s.add_dependency "fog", ">= 1.3" + s.add_dependency 'fog', '~> 1.18' s.add_dependency "chef", ">= 11" end diff --git a/lib/chef/knife/cfn_base.rb b/lib/chef/knife/cfn_base.rb index 75eefcc..012e4e5 100644 --- a/lib/chef/knife/cfn_base.rb +++ b/lib/chef/knife/cfn_base.rb @@ -1,4 +1,8 @@ # +# Author:: Seth Chisamore () +# Copyright:: Copyright (c) 2011-2015 Chef Software, Inc. +# License:: Apache License, Version 2.0 +# # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at @@ -30,6 +34,17 @@ def self.included(includer) require 'chef/json_compat' end + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + option :aws_access_key_id, :short => "-A ID", :long => "--aws-access-key-id KEY", @@ -42,27 +57,45 @@ def self.included(includer) :description => "Your AWS API Secret Access Key", :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + option :region, :long => "--region REGION", :description => "Your AWS region", - :default => "us-east-1", :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } end end def connection + connection_settings = { +# :provider => 'AWS', + :region => locate_config_value(:region) + } + if locate_config_value(:use_iam_profile) + connection_settings[:use_iam_profile] = true + else + connection_settings[:aws_access_key_id] = locate_config_value(:aws_access_key_id) + connection_settings[:aws_secret_access_key] = locate_config_value(:aws_secret_access_key) + connection_settings[:aws_session_token] = locate_config_value(:aws_session_token) + end @connection ||= begin - connection = Fog::AWS::CloudFormation.new( - :aws_access_key_id => Chef::Config[:knife][:aws_access_key_id], - :aws_secret_access_key => Chef::Config[:knife][:aws_secret_access_key], - :region => locate_config_value(:region) - ) + connection = Fog::AWS::CloudFormation.new(connection_settings) end end def locate_config_value(key) key = key.to_sym - Chef::Config[:knife][key] || config[key] + config[key] || Chef::Config[:knife][key] end def msg_pair(label, value, color=:cyan) @@ -71,23 +104,75 @@ def msg_pair(label, value, color=:cyan) end end + def is_image_windows? + image_info = connection.images.get(@server.image_id) + return image_info.platform == 'windows' + end + def validate!(keys=[:aws_access_key_id, :aws_secret_access_key]) errors = [] - keys.each do |k| - pretty_key = k.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(ssh)|(aws)/i) ? w.upcase : w.capitalize } - if Chef::Config[:knife][k].nil? - errors << "You did not provide a valid '#{pretty_key}' value." + unless locate_config_value(:use_iam_profile) + unless Chef::Config[:knife][:aws_credential_file].nil? + unless (Chef::Config[:knife].keys & [:aws_access_key_id, :aws_secret_access_key]).empty? + errors << "Either provide a credentials file or the access key and secret keys but not both." + end + # File format: + # AWSAccessKeyId=somethingsomethingdarkside + # AWSSecretKey=somethingsomethingcomplete + # OR + # [default] + # aws_access_key_id = somethingsomethingdarkside + # aws_secret_access_key = somethingsomethingdarkside + + aws_creds = ini_parse(File.read(Chef::Config[:knife][:aws_credential_file])) + profile = Chef::Config[:knife][:aws_profile] || 'default' + entries = aws_creds.values.first.has_key?("AWSAccessKeyId") ? aws_creds.values.first : aws_creds[profile] + + Chef::Config[:knife][:aws_access_key_id] = entries['AWSAccessKeyId'] || entries['aws_access_key_id'] + Chef::Config[:knife][:aws_secret_access_key] = entries['AWSSecretKey'] || entries['aws_secret_access_key'] + end + + keys.each do |k| + pretty_key = k.to_s.gsub(/_/, ' ').gsub(/\w+/){ |w| (w =~ /(ssh)|(aws)/i) ? w.upcase : w.capitalize } + if Chef::Config[:knife][k].nil? + errors << "You did not provide a valid '#{pretty_key}' value." + end end - end - if errors.each{|e| ui.error(e)}.any? - exit 1 + if errors.each{|e| ui.error(e)}.any? + exit 1 + end end end end - end -end + def iam_name_from_profile(profile) + # The IAM profile object only contains the name as part of the arn + if profile && profile.key?('arn') + name = profile['arn'].split('/')[-1] + end + name ||= '' + end + def ini_parse(file) + current_section = {} + map = {} + file.each_line do |line| + line = line.split(/^|\s;/).first # remove comments + section = line.match(/^\s*\[([^\[\]]+)\]\s*$/) unless line.nil? + if section + current_section = section[1] + elsif current_section + item = line.match(/^\s*(.+?)\s*=\s*(.+?)\s*$/) unless line.nil? + if item + map[current_section] ||= {} + map[current_section][item[1]] = item[2] + end + end + end + map + end + end +end diff --git a/lib/chef/knife/cfn_create.rb b/lib/chef/knife/cfn_create.rb index 425fd99..a20a92a 100644 --- a/lib/chef/knife/cfn_create.rb +++ b/lib/chef/knife/cfn_create.rb @@ -26,15 +26,15 @@ class CfnCreate < Chef::Knife::CfnBase require 'chef/knife/bootstrap' Chef::Knife::Bootstrap.load_deps end - + banner "knife cfn create (options)" - + option :capabilities, :short => "-C CAPABILITY..", :long => "--capabilities CAPABILITY1,CAPABILITY2,CAPABILITY3..", :description => "The explicitly approved capabilities that may be used during this stack creation", - :proc => Proc.new { |capabilities| capabilities.split(',') } - + :proc => Proc.new { |capabilities| capabilities.split(',') } + option :disable_rollback, :short => "-d", :long => "--disable-rollback", @@ -46,44 +46,44 @@ class CfnCreate < Chef::Knife::CfnBase :long => "--template-file TEMPLATE_FILE", :description => "Path to the file that contains the template", :proc => Proc.new { |f| Chef::Config[:knife][:template_file] = f } - + option :notification_arns, :short => "-n NOTIFICATION_ARN1,NOTIFICATION_ARN2,NOTIFICATION_ARN3..", :long => "--notification-arns VALUE1,VALUE2,VALUE3..", :description => "SNS ARNs to receive notification about the stack", :proc => Proc.new { |notification_arns| notification_arns.split(',') } - + option :parameters, :short => "-p 'key1=value1;key2=value2...'", :long => "--parameters 'key1=value1;key2=value2...'", :description => "Parameter values used to create the stack", :proc => Proc.new { |parameters| parameters.split(';') } - + option :timeout, :short => "-t TIMEOUT_VALUE", :long => "--timeout TIMEOUT_VALUE", :description => " Stack creation timeout in minutes", - :proc => Proc.new { |t| Chef::Config[:knife][:timeout] = t } - + :proc => Proc.new { |t| Chef::Config[:knife][:timeout] = t } + option :template_url, :short => "-u TEMPLATE_URL", :long => "--template-file TEMPLATE_URL", :description => "Path of the URL that contains the template. This must be a reference to a template in an S3 bucket in the same region that the stack will be created in", :proc => Proc.new { |u| Chef::Config[:knife][:template_url] = u } - + def run $stdout.sync = true validate! - + stack_name = @name_args[0] - - if stack_name.nil? - show_usage - ui.error("You must specify a stack name") + + if stack_name.nil? + show_usage + ui.error("You must specify a stack name") exit 1 - end - + end + begin response = connection.create_stack(stack_name, create_create_def) rescue Excon::Errors::BadRequest => e @@ -99,18 +99,18 @@ def run message = "Stack #{stack_name} creation started" print "\n#{ui.color(message, :green)}\n" end - end - end - + end + end + def create_create_def - create_def = {} + create_def = {} template_file = locate_config_value(:template_file) if template_file != nil and template_file != "" doc = File.open(template_file, 'rb') { |file| file.read } - create_def['TemplateBody'] = doc - end - create_def['TemplateURL'] = locate_config_value(:template_url) - create_def['Capabilities'] = locate_config_value(:capabilities) + create_def['TemplateBody'] = doc + end + create_def['TemplateURL'] = locate_config_value(:template_url) + create_def['Capabilities'] = locate_config_value(:capabilities) create_def['DisableRollback'] = locate_config_value(:disable_rollback) create_def['NotificationARNs'] = locate_config_value(:notification_arns) hashed_parameters={} @@ -120,6 +120,6 @@ def create_create_def create_def['TimeoutInMinutes'] = locate_config_value(:timeout) create_def end - + end end diff --git a/lib/chef/knife/cfn_delete.rb b/lib/chef/knife/cfn_delete.rb index 135425a..db1f1e3 100644 --- a/lib/chef/knife/cfn_delete.rb +++ b/lib/chef/knife/cfn_delete.rb @@ -26,28 +26,68 @@ class CfnDelete < Chef::Knife::CfnBase require 'chef/knife/bootstrap' Chef::Knife::Bootstrap.load_deps end - + banner "knife cfn delete " - + + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + + option :aws_access_key_id, + :short => "-A ID", + :long => "--aws-access-key-id KEY", + :description => "Your AWS Access Key ID", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key } + + option :aws_secret_access_key, + :short => "-K SECRET", + :long => "--aws-secret-access-key SECRET", + :description => "Your AWS API Secret Access Key", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + + option :region, + :long => "--region REGION", + :description => "Your AWS region", + :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } + def run $stdout.sync = true validate! - + stack_name = @name_args[0] - - if stack_name.nil? - show_usage - ui.error("You must specify a stack name") - exit 1 - end - + + if stack_name.nil? + show_usage + ui.error("You must specify a stack name") + exit 1 + end + options = {} options['StackName'] = stack_name begin response = connection.describe_stacks(options) - rescue Excon::Errors::BadRequest => e - i= e.response.body.index("") + rescue Excon::Errors::BadRequest => e + i= e.response.body.index("") j = e.response.body.index("") if !i.nil? and !j.nil? ui.error(e.response.body[i+9,j-i-9]) @@ -55,15 +95,15 @@ def run print "\n#{e.response.body}" end exit 1 - end - - puts "\n" - confirm("Do you really want to delete stack #{stack_name}") - + end + + puts "\n" + confirm("Do you really want to delete stack #{stack_name}") + begin response = connection.delete_stack(stack_name) rescue Excon::Errors::BadRequest => e - i= e.response.body.index("") + i= e.response.body.index("") j = e.response.body.index("") if !i.nil? and !j.nil? ui.error(e.response.body[i+9,j-i-9]) @@ -74,8 +114,8 @@ def run else message = "Stack #{stack_name} delete started" print "\n#{ui.color(message, :green)}" - end - end - end + end + end + end end end diff --git a/lib/chef/knife/cfn_describe.rb b/lib/chef/knife/cfn_describe.rb index 4ebde25..4e75a65 100644 --- a/lib/chef/knife/cfn_describe.rb +++ b/lib/chef/knife/cfn_describe.rb @@ -34,6 +34,46 @@ class CfnDescribe < Chef::Knife::CfnBase :long => "--long", :description => "Use long stack names (ARN) instead of friendly names" + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + + option :aws_access_key_id, + :short => "-A ID", + :long => "--aws-access-key-id KEY", + :description => "Your AWS Access Key ID", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key } + + option :aws_secret_access_key, + :short => "-K SECRET", + :long => "--aws-secret-access-key SECRET", + :description => "Your AWS API Secret Access Key", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + + option :region, + :long => "--region REGION", + :description => "Your AWS region", + :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } + def run $stdout.sync = true diff --git a/lib/chef/knife/cfn_events.rb b/lib/chef/knife/cfn_events.rb index ae2b9b8..f6882bd 100644 --- a/lib/chef/knife/cfn_events.rb +++ b/lib/chef/knife/cfn_events.rb @@ -18,29 +18,69 @@ class Chef class Knife class CfnEvents < Chef::Knife::CfnBase - + deps do require 'fog' require 'readline' require 'chef/json_compat' require 'chef/knife/bootstrap' Chef::Knife::Bootstrap.load_deps - end + end banner "knife cfn events " + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + + option :aws_access_key_id, + :short => "-A ID", + :long => "--aws-access-key-id KEY", + :description => "Your AWS Access Key ID", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key } + + option :aws_secret_access_key, + :short => "-K SECRET", + :long => "--aws-secret-access-key SECRET", + :description => "Your AWS API Secret Access Key", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + + option :region, + :long => "--region REGION", + :description => "Your AWS region", + :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } + def run $stdout.sync = true validate! - + stack_name = @name_args[0] - - if stack_name.nil? - show_usage - ui.error("You must specify a stack name") - exit 1 - end + + if stack_name.nil? + show_usage + ui.error("You must specify a stack name") + exit 1 + end events_list = [ ui.color('Event ID', :bold), @@ -53,12 +93,12 @@ def run ui.color('Resource Status Reason', :bold) ] @name_args.each do |stack_name| - data = Array.new - begin + data = Array.new + begin response = connection.describe_stack_events(stack_name) data = response.body['StackEvents'] rescue Excon::Errors::BadRequest => e - i= e.response.body.index("") + i= e.response.body.index("") j = e.response.body.index("") if !i.nil? and !j.nil? ui.error(e.response.body[i+9,j-i-9]) @@ -67,7 +107,7 @@ def run end exit 1 else - data.each do |event| + data.each do |event| events_list << event['EventId'] events_list << event['StackId'] events_list << event['LogicalResourceId'] diff --git a/lib/chef/knife/cfn_outputs.rb b/lib/chef/knife/cfn_outputs.rb index 3825702..19e5bc1 100644 --- a/lib/chef/knife/cfn_outputs.rb +++ b/lib/chef/knife/cfn_outputs.rb @@ -35,6 +35,46 @@ class CfnOutputs < Chef::Knife::CfnBase :description => "Stack output is formatted in a suitable syntax to use for parameters for another stack", :proc => Proc.new{ |o| Chef::Config[:knife][:output_format] = "parameter" } + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + + option :aws_access_key_id, + :short => "-A ID", + :long => "--aws-access-key-id KEY", + :description => "Your AWS Access Key ID", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key } + + option :aws_secret_access_key, + :short => "-K SECRET", + :long => "--aws-secret-access-key SECRET", + :description => "Your AWS API Secret Access Key", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + + option :region, + :long => "--region REGION", + :description => "Your AWS region", + :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } + def run $stdout.sync = true diff --git a/lib/chef/knife/cfn_resources.rb b/lib/chef/knife/cfn_resources.rb index 151408c..f4b91e2 100644 --- a/lib/chef/knife/cfn_resources.rb +++ b/lib/chef/knife/cfn_resources.rb @@ -29,6 +29,46 @@ class CfnResources < Chef::Knife::CfnBase banner "knife cfn resources [logical resource id]" + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + + option :aws_access_key_id, + :short => "-A ID", + :long => "--aws-access-key-id KEY", + :description => "Your AWS Access Key ID", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key } + + option :aws_secret_access_key, + :short => "-K SECRET", + :long => "--aws-secret-access-key SECRET", + :description => "Your AWS API Secret Access Key", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + + option :region, + :long => "--region REGION", + :description => "Your AWS region", + :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } + def run $stdout.sync = true diff --git a/lib/chef/knife/cfn_update.rb b/lib/chef/knife/cfn_update.rb index 7df1e29..ee6200f 100644 --- a/lib/chef/knife/cfn_update.rb +++ b/lib/chef/knife/cfn_update.rb @@ -26,15 +26,15 @@ class CfnUpdate < Chef::Knife::CfnBase require 'chef/knife/bootstrap' Chef::Knife::Bootstrap.load_deps end - + banner "knife cfn update (options)" - + option :capabilities, :short => "-c CAPABILITY..", :long => "--capabilities CAPABILITY1,CAPABILITY2,CAPABILITY3..", :description => "The explicitly approved capabilities that may be used during this stack creation", - :proc => Proc.new { |capabilities| capabilities.split(',') } - + :proc => Proc.new { |capabilities| capabilities.split(',') } + option :disable_rollback, :short => "-d", :long => "--disable-rollback", @@ -46,44 +46,84 @@ class CfnUpdate < Chef::Knife::CfnBase :long => "--template-file TEMPLATE_FILE", :description => "Path to the file that contains the template", :proc => Proc.new { |f| Chef::Config[:knife][:template_file] = f } - + option :notification_arns, :short => "-n NOTIFICATION_ARN1,NOTIFICATION_ARN2,NOTIFICATION_ARN3..", :long => "--notification-arns VALUE1,VALUE2,VALUE3..", :description => "SNS ARNs to receive notification about the stack", :proc => Proc.new { |notification_arns| notification_arns.split(',') } - + option :parameters, :short => "-p 'key1=value1;key2=value2...'", :long => "--parameters 'key1=value1;key2=value2...'", :description => "Parameter values used to update the stack", :proc => Proc.new { |parameters| parameters.split(';') } - + option :timeout, :short => "-t TIMEOUT_VALUE", :long => "--timeout TIMEOUT_VALUE", :description => " Stack update timeout in minutes", - :proc => Proc.new { |t| Chef::Config[:knife][:timeout] = t } - + :proc => Proc.new { |t| Chef::Config[:knife][:timeout] = t } + option :template_url, :short => "-u TEMPLATE_URL", :long => "--template-file TEMPLATE_URL", :description => "Path of the URL that contains the template. This must be a reference to a template in an S3 bucket in the same region that the stack was created in", :proc => Proc.new { |u| Chef::Config[:knife][:template_url] = u } - + + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + + option :aws_access_key_id, + :short => "-A ID", + :long => "--aws-access-key-id KEY", + :description => "Your AWS Access Key ID", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key } + + option :aws_secret_access_key, + :short => "-K SECRET", + :long => "--aws-secret-access-key SECRET", + :description => "Your AWS API Secret Access Key", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + + option :region, + :long => "--region REGION", + :description => "Your AWS region", + :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } + def run $stdout.sync = true validate! - + stack_name = @name_args[0] - - if stack_name.nil? - show_usage - ui.error("You must specify a stack name") + + if stack_name.nil? + show_usage + ui.error("You must specify a stack name") exit 1 - end - + end + begin response = connection.update_stack(stack_name, create_update_def) rescue Excon::Errors::BadRequest => e @@ -99,18 +139,18 @@ def run message = "Stack #{stack_name} update started" print "\n#{ui.color(message, :green)}\n" end - end - end - + end + end + def create_update_def - create_def = {} + create_def = {} template_file = locate_config_value(:template_file) if template_file != nil and template_file != "" doc = File.open(template_file, 'rb') { |file| file.read } - create_def['TemplateBody'] = doc - end - create_def['TemplateURL'] = locate_config_value(:template_url) - create_def['Capabilities'] = locate_config_value(:capabilities) + create_def['TemplateBody'] = doc + end + create_def['TemplateURL'] = locate_config_value(:template_url) + create_def['Capabilities'] = locate_config_value(:capabilities) create_def['DisableRollback'] = locate_config_value(:disable_rollback) create_def['NotificationARNs'] = locate_config_value(:notification_arns) hashed_parameters={} @@ -120,6 +160,6 @@ def create_update_def create_def['TimeoutInMinutes'] = locate_config_value(:timeout) create_def end - + end end diff --git a/lib/chef/knife/cfn_validate.rb b/lib/chef/knife/cfn_validate.rb index 84ebaed..d88d238 100644 --- a/lib/chef/knife/cfn_validate.rb +++ b/lib/chef/knife/cfn_validate.rb @@ -18,7 +18,7 @@ class Chef class Knife class CfnValidate < Chef::Knife::CfnBase - + deps do require 'fog' require 'readline' @@ -26,7 +26,7 @@ class CfnValidate < Chef::Knife::CfnBase require 'chef/knife/bootstrap' Chef::Knife::Bootstrap.load_deps end - + banner "knife cfn validate (options)" option :template_file, @@ -34,23 +34,62 @@ class CfnValidate < Chef::Knife::CfnBase :long => "--template-file TEMPLATE_FILE", :description => "Path to the file that contains the template", :proc => Proc.new { |f| Chef::Config[:knife][:template_file] = f } - + option :template_url, :short => "-u TEMPLATE_URL", :long => "--template-file TEMPLATE_URL", :description => "Path to the URL that contains the template", :proc => Proc.new { |u| Chef::Config[:knife][:template_url] = u } - + + option :aws_credential_file, + :long => "--aws-credential-file FILE", + :description => "File containing AWS credentials as used by aws cmdline tools", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_credential_file] = key } + + option :aws_profile, + :long => "--aws-profile PROFILE", + :description => "AWS profile, from credential file, to use", + :default => 'default', + :proc => Proc.new { |key| Chef::Config[:knife][:aws_profile] = key } + + option :aws_access_key_id, + :short => "-A ID", + :long => "--aws-access-key-id KEY", + :description => "Your AWS Access Key ID", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_access_key_id] = key } + + option :aws_secret_access_key, + :short => "-K SECRET", + :long => "--aws-secret-access-key SECRET", + :description => "Your AWS API Secret Access Key", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_secret_access_key] = key } + + option :aws_session_token, + :long => "--aws-session-token TOKEN", + :description => "Your AWS Session Token, for use with AWS STS Federation or Session Tokens", + :proc => Proc.new { |key| Chef::Config[:knife][:aws_session_token] = key } + + option :region, + :long => "--region REGION", + :description => "Your AWS region", + :proc => Proc.new { |key| Chef::Config[:knife][:region] = key } + + option :use_iam_profile, + :long => "--use-iam-profile", + :description => "Use IAM profile assigned to current machine", + :boolean => true, + :default => false, + :proc => Proc.new { |key| Chef::Config[:knife][:use_iam_profile] = key } def run $stdout.sync = true validate! - - begin + + begin response = connection.validate_template(create_validate_def) rescue Excon::Errors::BadRequest => e - i= e.response.body.index("") + i= e.response.body.index("") j = e.response.body.index("") if !i.nil? and !j.nil? ui.error(e.response.body[i+9,j-i-9]) @@ -61,20 +100,20 @@ def run else print "\n#{ui.color("Template validated successfully", :green)}" end - end - + end + def create_validate_def - validate_def = {} + validate_def = {} template_file = locate_config_value(:template_file) if template_file != nil and template_file != "" doc = File.open(template_file, 'rb') { |file| file.read } - validate_def['TemplateBody'] = doc - else - validate_def['TemplateURL'] = locate_config_value(:template_url) - end - validate_def + validate_def['TemplateBody'] = doc + else + validate_def['TemplateURL'] = locate_config_value(:template_url) + end + validate_def end - + end end end