knife-cloudformation 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,17 +2,24 @@ require 'chef/knife/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
5
- class CloudformationDescribe < CloudformationBase
6
- include CloudformationDefault
5
+ class CloudformationDescribe < Knife
6
+
7
+ include KnifeCloudformation::KnifeBase
7
8
 
8
9
  banner 'knife cloudformation describe NAME'
9
-
10
+
10
11
  option(:resources,
11
12
  :short => '-r',
12
13
  :long => '--resources',
13
- :description => 'Display resources for stack(s)'
14
+ :description => 'Display resources for stack'
15
+ )
16
+
17
+ option(:outputs,
18
+ :short => '-o',
19
+ :long => '--outputs',
20
+ :description => 'Display output for stack'
14
21
  )
15
-
22
+
16
23
  option(:attribute,
17
24
  :short => '-a ATTR',
18
25
  :long => '--attribute ATTR',
@@ -27,11 +34,22 @@ class Chef
27
34
  :long => '--all-attributes',
28
35
  :description => 'Print all attributes'
29
36
  )
30
-
37
+
31
38
  def run
32
- name_args.each do |stack|
33
- things_output(stack,
34
- *(config[:resources] ? [get_resources(stack), :resources] : [get_description(stack), :description])
39
+ stack_name = name_args.last
40
+ if(config[:outputs])
41
+ ui.info "Outputs for stack: #{ui.color(stack_name, :bold)}:"
42
+ unless(stack(stack_name).outputs.empty?)
43
+ stack(stack_name).outputs.each do |key, value|
44
+ key = snake(key).to_s.split('_').map(&:capitalize).join(' ')
45
+ ui.info [' ', ui.color("#{key}:", :bold), value].join(' ')
46
+ end
47
+ else
48
+ ui.info " #{ui.color('No outputs found')}"
49
+ end
50
+ else
51
+ things_output(stack_name,
52
+ *(config[:resources] ? [get_resources(stack_name), :resources] : [get_description(stack_name), :description])
35
53
  )
36
54
  end
37
55
  end
@@ -40,18 +58,18 @@ class Chef
40
58
  config[:resources] ? %w(Timestamp ResourceType ResourceStatus) : %w(StackName CreationTime StackStatus DisableRollback)
41
59
  end
42
60
 
43
- def get_description(stack)
44
- get_things(stack) do
45
- [aws_con.describe_stacks('StackName' => stack).body['Stacks'].first]
61
+ def get_description(name)
62
+ get_things(name) do
63
+ [stack(name).raw_stack]
46
64
  end
47
65
  end
48
-
49
- def get_resources(stack)
50
- get_things(stack) do
51
- aws_con.describe_stack_resources('StackName' => stack).body['StackResources']
66
+
67
+ def get_resources(name)
68
+ get_things(name) do
69
+ stack(name).resources
52
70
  end
53
71
  end
54
-
72
+
55
73
  end
56
74
  end
57
75
  end
@@ -2,27 +2,27 @@ require 'chef/knife/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
5
- class CloudformationDestroy < CloudformationBase
5
+ class CloudformationDestroy < Knife
6
6
 
7
- include CloudformationDefault
8
-
9
- banner 'knife cloudformation destroy NAME[ NAME ...]'
7
+ include KnifeCloudformation::KnifeBase
8
+
9
+ banner 'knife cloudformation destroy NAME'
10
10
 
11
11
  def run
12
- name_args.each do |stack_name|
13
- ui.warn "Destroying Cloud Formation: #{ui.color(stack_name, :bold)}"
14
- ui.confirm 'Destroy this formation'
15
- destroy_formation!(stack_name)
16
- ui.info " -> Destroyed Cloud Formation: #{ui.color(stack_name, :bold, :red)}"
17
- end
12
+ stack_name = name_args.last
13
+ ui.warn "Destroying Cloud Formation: #{ui.color(stack_name, :bold)}"
14
+ ui.confirm 'Destroy this formation'
15
+ destroy_formation!(stack_name)
16
+ poll_stack(stack_name)
17
+ ui.info " -> Destroyed Cloud Formation: #{ui.color(stack_name, :bold, :red)}"
18
18
  end
19
19
 
20
20
  def destroy_formation!(stack_name)
21
21
  get_things(stack_name, 'Failed to perform destruction') do
22
- aws_con.delete_stack(stack_name)
22
+ stack(stack_name).destroy
23
23
  end
24
24
  end
25
-
25
+
26
26
  end
27
27
  end
28
28
  end
@@ -2,17 +2,17 @@ require 'chef/knife/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
5
- class CloudformationEvents < CloudformationBase
5
+ class CloudformationEvents < Knife
6
6
 
7
7
  banner 'knife cloudformation events NAME'
8
8
 
9
- include CloudformationDefault
10
-
11
- option(:poll,
9
+ include KnifeCloudformation::KnifeBase
10
+
11
+ option(:polling,
12
12
  :short => '-p',
13
- :long => '--poll',
13
+ :long => '--[no-]poll',
14
14
  :description => 'Poll events while stack status is "in progress"',
15
- :proc => lambda {|v| Chef::Config[:knife][:cloudformation][:poll] = true }
15
+ :proc => lambda {|v| Chef::Config[:knife][:cloudformation][:polling] = v }
16
16
  )
17
17
 
18
18
  option(:attribute,
@@ -25,6 +25,15 @@ class Chef
25
25
  }
26
26
  )
27
27
 
28
+ option(:poll_delay,
29
+ :short => '-D secs',
30
+ :long => '--poll-delay secs',
31
+ :description => 'Number of seconds to pause between event poll',
32
+ :proc => lambda {|val|
33
+ Chef::Config[:knife][:cloudformation][:poll_delay] = val.to_i
34
+ }
35
+ )
36
+
28
37
  option(:all_attributes,
29
38
  :long => '--all-attributes',
30
39
  :description => 'Print all attributes'
@@ -33,50 +42,23 @@ class Chef
33
42
  def run
34
43
  name = name_args.first
35
44
  ui.info "Cloud Formation Events for Stack: #{ui.color(name, :bold)}\n"
36
- events = stack_events(name)
37
- output = get_titles(events, :format)
38
- output += process(events)
39
- ui.info ui.list(output.flatten, :uneven_columns_across, allowed_attributes.size)
45
+ things_output(name, get_events(name), 'events')
40
46
  if(Chef::Config[:knife][:cloudformation][:poll])
41
- poll_stack(name)
42
- end
43
- end
44
-
45
- def stack_events(name, continue_from_last=true)
46
- get_things(name) do
47
- @_stack_events ||= Mash.new
48
- if(@_stack_events[name])
49
- options = {'NextToken' => @_stack_events[name]}
50
- else
51
- options = {}
47
+ while(stack(name).in_progress?)
48
+ sleep(Chef::Config[:knife][:cloudformation][:poll_delay] || 15)
49
+ things_output(nil, get_events(name), 'events', :no_title, :ignore_empty_output)
52
50
  end
53
- res = aws_con.describe_stack_events(name, options)
54
- @_stack_events[name] = res.body['StackToken']
55
- res.body['StackEvents']
51
+ # Extra to see completion
52
+ things_output(nil, get_events(name), 'events', :no_title, :ignore_empty_output)
56
53
  end
57
54
  end
58
55
 
59
- def poll_stack(name)
60
- while(stack_in_progress?(name))
61
- events = stack_events(name)
62
- output = process(events)
63
- unless(output.empty?)
64
- ui.info ui.list(output, :uneven_columns_across, allowed_attributes.size)
65
- end
66
- sleep((ENV['CLOUDFORMATION_POLL'] || 15).to_i)
67
- end
68
- # One more to see completion
69
- events = stack_events(name)
70
- output = process(events)
71
- unless(output.empty?)
72
- ui.info ui.list(output, :uneven_columns_across, allowed_attributes.size)
56
+ def get_events(name)
57
+ get_things do
58
+ stack(name).events
73
59
  end
74
60
  end
75
61
 
76
- def stack_in_progress?(name)
77
- stack_status(name).downcase.include?('in_progress')
78
- end
79
-
80
62
  def default_attributes
81
63
  %w(Timestamp LogicalResourceId ResourceType ResourceStatus ResourceStatusReason)
82
64
  end
@@ -0,0 +1,84 @@
1
+ require 'chef/knife/cloudformation_base'
2
+ require 'knife-cloudformation/utils'
3
+
4
+ class Chef
5
+ class Knife
6
+ class CloudformationInspect < Knife
7
+
8
+ include KnifeCloudformation::KnifeBase
9
+ include KnifeCloudformation::Utils::Ssher
10
+
11
+ banner 'knife cloudformation inspect NAME'
12
+
13
+ option(:instance_failure,
14
+ :short => '-I',
15
+ :long => '--instance-failure',
16
+ :boolean => true,
17
+ :description => 'Display log from failed instance'
18
+ )
19
+
20
+ option(:identity_file,
21
+ :short => '-i IDENTITY_FILE',
22
+ :long => '--identity-file IDENTITY_FILE',
23
+ :description => 'The SSH identity file used for authentication',
24
+ :proc => lambda {|val|
25
+ Chef::Config[:knife][:cloudformation][:identity_file] = val
26
+ }
27
+ )
28
+
29
+ option(:ssh_user,
30
+ :short => '-x SSH_USER',
31
+ :long => '--ssh-user SSH_USER',
32
+ :description => 'The ssh username',
33
+ :proc => lambda {|val|
34
+ Chef::Config[:knife][:cloudformation][:ssh_user] = val
35
+ }
36
+ )
37
+
38
+ def run
39
+ stack_name = name_args.last
40
+ if(config[:instance_failure])
41
+ do_instance_failure(stack_name)
42
+ end
43
+ end
44
+
45
+ def do_instance_failure(stack_name)
46
+ event = stack(stack_name).events.detect do |e|
47
+ e['ResourceType'] == 'AWS::CloudFormation::WaitCondition' &&
48
+ e['ResourceStatus'] == 'CREATE_FAILED' &&
49
+ e['ResourceStatusReason'].include?('uniqueId')
50
+ end
51
+ if(event)
52
+ process_instance_failure(stack_name, event)
53
+ else
54
+ ui.error "Failed to discover failed node within stack: #{stack_name}"
55
+ exit 1
56
+ end
57
+ end
58
+
59
+ def process_instance_failure(stack_name, event)
60
+ inst_id = event['ResourceStatusReason'].split(' ').last.strip
61
+ inst_addr = aws.aws(:ec2).servers.get(inst_id).public_ip_address
62
+ ui.info "Displaying stack #{ui.color(stack_name, :bold)} failure on instance #{ui.color(inst_id, :bold)}"
63
+ opts = ssh_key ? {:keys => [ssh_key]} : {}
64
+ remote_path = '/var/log/cfn-init.log'
65
+ content = remote_file_contents(inst_addr, ssh_user, remote_path, opts)
66
+ ui.info " content of #{remote_path}:"
67
+ ui.info ""
68
+ ui.info content
69
+ end
70
+
71
+ def ssh_user
72
+ Chef::Config[:knife][:cloudformation][:ssh_user] ||
73
+ Chef::Config[:knife][:ssh_user] ||
74
+ ENV['USER']
75
+ end
76
+
77
+ def ssh_key
78
+ Chef::Config[:knife][:cloudformation][:identity_file] ||
79
+ Chef::Config[:knife][:identity_file]
80
+ end
81
+
82
+ end
83
+ end
84
+ end
@@ -2,11 +2,11 @@ require 'chef/knife/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
5
- class CloudformationList < CloudformationBase
6
- include CloudformationDefault
5
+ class CloudformationList < Knife
6
+ include KnifeCloudformation::KnifeBase
7
7
 
8
8
  banner 'knife cloudformation list NAME'
9
-
9
+
10
10
  option(:attribute,
11
11
  :short => '-a ATTR',
12
12
  :long => '--attribute ATTR',
@@ -31,14 +31,14 @@ class Chef
31
31
  Chef::Config[:knife][:cloudformation][:status].push(val).uniq!
32
32
  }
33
33
  )
34
-
34
+
35
35
  def run
36
36
  things_output(nil, get_list, nil)
37
37
  end
38
38
 
39
39
  def get_list
40
40
  get_things do
41
- aws_con.list_stacks(aws_filter_hash).body['StackSummaries']
41
+ aws.stacks
42
42
  end
43
43
  end
44
44
 
@@ -46,18 +46,6 @@ class Chef
46
46
  %w(StackName CreationTime StackStatus TemplateDescription)
47
47
  end
48
48
 
49
- def status_filter
50
- val = Chef::Config[:knife][:cloudformation][:status] || %w(CREATE_COMPLETE CREATE_IN_PROGRESS UPDATE_IN_PROGRESS UPDATE_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_COMPLETE)
51
- val.map(&:downcase).include?('none') ? [] : val.map(&:upcase)
52
- end
53
-
54
- def aws_filter_hash
55
- hash = Mash.new
56
- status_filter.each_with_index do |filter, i|
57
- hash["StackStatusFilter.member.#{i+1}"] = filter
58
- end
59
- hash
60
- end
61
49
  end
62
50
  end
63
51
  end
@@ -2,12 +2,12 @@ require 'chef/knife/cloudformation_create'
2
2
 
3
3
  class Chef
4
4
  class Knife
5
- class CloudformationUpdate < CloudformationCreate
5
+ class CloudformationUpdate < Knife
6
6
  banner 'knife cloudformation update NAME'
7
7
 
8
- include CloudformationDefault
8
+ include KnifeCloudformation::KnifeBase
9
9
  include CloudformationCreate::Options
10
-
10
+
11
11
  def create_stack(name, stack)
12
12
  begin
13
13
  res = aws_con.update_stack(name, stack)
@@ -0,0 +1,118 @@
1
+ require 'fog'
2
+ require 'knife-cloudformation/utils'
3
+
4
+ Dir.glob(File.join(File.dirname(__FILE__), 'aws_commons/*.rb')).each do |item|
5
+ require "knife-cloudformation/aws_commons/#{File.basename(item).sub('.rb', '')}"
6
+ end
7
+
8
+ module KnifeCloudformation
9
+ class AwsCommons
10
+
11
+ FOG_MAP = {
12
+ :ec2 => :compute
13
+ }
14
+
15
+ def initialize(args={})
16
+ @ui = args[:ui]
17
+ @creds = args[:fog]
18
+ @connections = {}
19
+ @memo = {
20
+ :stacks => {},
21
+ :event_ids => [],
22
+ :stack_list => {}
23
+ }
24
+ end
25
+
26
+ def build_connection(type)
27
+ type = type.to_sym
28
+ type = FOG_MAP[type] if FOG_MAP[type]
29
+ unless(@connections[type])
30
+ if(type == :compute)
31
+ @connections[:compute] = Fog::Compute::AWS.new(@creds)
32
+ else
33
+ Fog.credentials = Fog.symbolize_credentials(@creds)
34
+ @connections[type] = Fog::AWS[type]
35
+ Fog.credentials = {}
36
+ end
37
+ end
38
+ @connections[type]
39
+ end
40
+ alias_method :aws, :build_connection
41
+
42
+ DEFAULT_STACK_STATUS = %w(
43
+ CREATE_IN_PROGRESS CREATE_COMPLETE CREATE_FAILED
44
+ ROLLBACK_IN_PROGRESS ROLLBACK_COMPLETE ROLLBACK_FAILED
45
+ UPDATE_IN_PROGRESS UPDATE_COMPLETE UPDATE_COMPLETE_CLEANUP_IN_PROGRESS
46
+ UPDATE_ROLLBACK_IN_PROGRESS UPDATE_ROLLBACK_FAILED
47
+ UPDATE_ROLLBACK_COMPLETE_CLEANUP_IN_PROGRESS UPDATE_ROLLBACK_COMPLETE
48
+ DELETE_IN_PROGRESS DELETE_FAILED
49
+ )
50
+
51
+ def stacks(args={})
52
+ status = args[:status] || DEFAULT_STACK_STATUS
53
+ key = status.hash
54
+ @memo[:stack_list].delete(key) if args[:force_refresh]
55
+ count = 0
56
+ if(status.map(&:downcase).include?('none'))
57
+ filter = {}
58
+ else
59
+ filter = Hash[*(
60
+ status.map do |n|
61
+ count += 1
62
+ ["StackStatusFilter.member.#{count}", n]
63
+ end.flatten
64
+ )]
65
+ end
66
+ unless(@memo[:stack_list][key])
67
+ @memo[:stack_list][key] = aws(:cloud_formation).list_stacks(filter).body['StackSummaries']
68
+ end
69
+ @memo[:stack_list][key]
70
+ end
71
+
72
+ def stack(name)
73
+ unless(@memo[:stacks][name])
74
+ @memo[:stacks][name] = Stack.new(name, self)
75
+ end
76
+ @memo[:stacks][name]
77
+ end
78
+
79
+ def create_stack(name, definition)
80
+ Stack.create(name, definition, self)
81
+ end
82
+
83
+ # Output Helpers
84
+
85
+ def process(things, args={})
86
+ @event_ids ||= []
87
+ processed = things.reverse.map do |thing|
88
+ next if @memo[:event_ids].include?(thing['EventId'])
89
+ @event_ids.push(thing['EventId']).compact!
90
+ if(args[:attributes])
91
+ args[:attributes].map do |key|
92
+ thing[key].to_s
93
+ end
94
+ else
95
+ thing.values
96
+ end
97
+ end
98
+ args[:flat] ? processed.flatten : processed
99
+ end
100
+
101
+ def get_titles(thing, args={})
102
+ attrs = args[:attributes] || []
103
+ if(attrs.empty?)
104
+ hash = thing.is_a?(Array) ? thing.first : thing
105
+ hash ||= {}
106
+ attrs = hash.keys
107
+ end
108
+ titles = attrs.map do |key|
109
+ key.gsub(/([a-z])([A-Z])/, '\1 \2')
110
+ end.compact
111
+ if(args[:format])
112
+ titles.map{|s| @ui.color(s, :bold)}
113
+ else
114
+ titles
115
+ end
116
+ end
117
+ end
118
+ end