knife-cloudformation 0.1.2 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -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