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.
- 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
|