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.
- data/CHANGELOG.md +6 -0
- data/lib/chef/knife/cloudformation_base.rb +109 -143
- data/lib/chef/knife/cloudformation_create.rb +66 -103
- data/lib/chef/knife/cloudformation_describe.rb +35 -17
- data/lib/chef/knife/cloudformation_destroy.rb +12 -12
- data/lib/chef/knife/cloudformation_events.rb +24 -42
- data/lib/chef/knife/cloudformation_inspect.rb +84 -0
- data/lib/chef/knife/cloudformation_list.rb +5 -17
- data/lib/chef/knife/cloudformation_update.rb +3 -3
- data/lib/knife-cloudformation/aws_commons.rb +118 -0
- data/lib/knife-cloudformation/aws_commons/stack.rb +221 -0
- data/lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb +78 -0
- data/lib/knife-cloudformation/sparkle_formation.rb +38 -7
- data/lib/knife-cloudformation/utils.rb +69 -0
- data/lib/knife-cloudformation/version.rb +1 -1
- metadata +7 -2
@@ -2,17 +2,24 @@ require 'chef/knife/cloudformation_base'
|
|
2
2
|
|
3
3
|
class Chef
|
4
4
|
class Knife
|
5
|
-
class CloudformationDescribe <
|
6
|
-
|
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
|
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.
|
33
|
-
|
34
|
-
|
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(
|
44
|
-
get_things(
|
45
|
-
[
|
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(
|
50
|
-
get_things(
|
51
|
-
|
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 <
|
5
|
+
class CloudformationDestroy < Knife
|
6
6
|
|
7
|
-
include
|
8
|
-
|
9
|
-
banner 'knife cloudformation destroy NAME
|
7
|
+
include KnifeCloudformation::KnifeBase
|
8
|
+
|
9
|
+
banner 'knife cloudformation destroy NAME'
|
10
10
|
|
11
11
|
def run
|
12
|
-
name_args.
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
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
|
-
|
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 <
|
5
|
+
class CloudformationEvents < Knife
|
6
6
|
|
7
7
|
banner 'knife cloudformation events NAME'
|
8
8
|
|
9
|
-
include
|
10
|
-
|
11
|
-
option(:
|
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][:
|
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
|
-
|
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
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
54
|
-
|
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
|
60
|
-
|
61
|
-
|
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 <
|
6
|
-
include
|
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
|
-
|
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 <
|
5
|
+
class CloudformationUpdate < Knife
|
6
6
|
banner 'knife cloudformation update NAME'
|
7
7
|
|
8
|
-
include
|
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
|