knife-cloudformation 0.1.4 → 0.1.6
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/knife-cloudformation.gemspec +2 -1
- data/lib/chef/knife/cloudformation_base.rb +2 -2
- data/lib/chef/knife/cloudformation_create.rb +3 -3
- data/lib/chef/knife/cloudformation_destroy.rb +23 -7
- data/lib/chef/knife/cloudformation_events.rb +3 -1
- data/lib/chef/knife/cloudformation_inspect.rb +18 -0
- data/lib/knife-cloudformation/aws_commons/stack.rb +90 -26
- data/lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb +1 -0
- data/lib/knife-cloudformation/aws_commons.rb +51 -5
- data/lib/knife-cloudformation/version.rb +1 -1
- metadata +20 -4
data/CHANGELOG.md
CHANGED
@@ -10,7 +10,8 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.description = 'Knife tooling for Cloud Formation'
|
11
11
|
s.require_path = 'lib'
|
12
12
|
s.add_dependency 'chef'
|
13
|
-
s.add_dependency 'fog', '~> 1.
|
13
|
+
s.add_dependency 'fog', '~> 1.15'
|
14
|
+
s.add_dependency 'net-sftp'
|
14
15
|
s.add_dependency 'attribute_struct', '~> 0.1.6'
|
15
16
|
s.files = Dir['**/*']
|
16
17
|
end
|
@@ -53,8 +53,8 @@ module KnifeCloudformation
|
|
53
53
|
columns = allowed_attributes.size
|
54
54
|
output += aws.process(things, :flat => true, :attributes => allowed_attributes)
|
55
55
|
output.compact.flatten
|
56
|
-
if(output.empty?
|
57
|
-
ui.warn 'No information found'
|
56
|
+
if(output.empty?)
|
57
|
+
ui.warn 'No information found' unless args.include?(:ignore_empty_output)
|
58
58
|
else
|
59
59
|
ui.info "#{what.to_s.capitalize} for stack: #{ui.color(stack, :bold)}" if stack
|
60
60
|
ui.info "#{ui.list(output, :uneven_columns_across, columns)}"
|
@@ -59,11 +59,11 @@ class Chef
|
|
59
59
|
:proc => lambda {|val| Chef::Config[:knife][:cloudformation][:processing] = val }
|
60
60
|
)
|
61
61
|
option(:polling,
|
62
|
-
:long => '--[no-]
|
62
|
+
:long => '--[no-]poll',
|
63
63
|
:description => 'Enable stack event polling.',
|
64
64
|
:boolean => true,
|
65
65
|
:default => true,
|
66
|
-
:proc => lambda {|val| Chef::Config[:knife][:cloudformation][:
|
66
|
+
:proc => lambda {|val| Chef::Config[:knife][:cloudformation][:poll] = val }
|
67
67
|
)
|
68
68
|
option(:notifications,
|
69
69
|
:long => '--notification ARN',
|
@@ -158,7 +158,7 @@ class Chef
|
|
158
158
|
valid = false
|
159
159
|
until(valid)
|
160
160
|
default = Chef::Config[:knife][:cloudformation][:options][:parameters][k] || v['Default']
|
161
|
-
answer = ui.ask_question("#{k.split(/([A-Z]+[^A-Z]*)/).find_all{|s|!s.empty?}.join(' ')} ", :default => default)
|
161
|
+
answer = ui.ask_question("#{k.split(/([A-Z]+[^A-Z]*)/).find_all{|s|!s.empty?}.join(' ')}: ", :default => default)
|
162
162
|
validation = KnifeCloudformation::AwsCommons::Stack::ParameterValidator.validate(answer, v)
|
163
163
|
if(validation == true)
|
164
164
|
Chef::Config[:knife][:cloudformation][:options][:parameters][k] = answer
|
@@ -6,15 +6,31 @@ class Chef
|
|
6
6
|
|
7
7
|
include KnifeCloudformation::KnifeBase
|
8
8
|
|
9
|
-
banner 'knife cloudformation destroy NAME'
|
9
|
+
banner 'knife cloudformation destroy NAME [NAME]'
|
10
|
+
|
11
|
+
option(:polling,
|
12
|
+
:long => '--[no-]poll',
|
13
|
+
:description => 'Enable stack event polling.',
|
14
|
+
:boolean => true,
|
15
|
+
:default => true,
|
16
|
+
:proc => lambda {|val| Chef::Config[:knife][:cloudformation][:poll] = val }
|
17
|
+
)
|
10
18
|
|
11
19
|
def run
|
12
|
-
|
13
|
-
|
14
|
-
ui.
|
15
|
-
|
16
|
-
|
17
|
-
|
20
|
+
stacks = name_args.sort
|
21
|
+
plural = 's' if stacks.size > 1
|
22
|
+
ui.warn "Destroying Cloud Formation#{plural}: #{ui.color(stacks.join(', '), :bold)}"
|
23
|
+
ui.confirm "Destroy formation#{plural}"
|
24
|
+
stacks.each do |stack_name|
|
25
|
+
destroy_formation!(stack_name)
|
26
|
+
ui.info "Destroy request sent for stack: #{ui.color(stack_name, :bold)}"
|
27
|
+
end
|
28
|
+
if(config[:polling])
|
29
|
+
stacks.each do |stack_name|
|
30
|
+
poll_stack(stack_name)
|
31
|
+
end
|
32
|
+
ui.info " -> Destroyed Cloud Formation#{plural}: #{ui.color(stacks.join(', '), :bold, :red)}"
|
33
|
+
end
|
18
34
|
end
|
19
35
|
|
20
36
|
def destroy_formation!(stack_name)
|
@@ -11,8 +11,10 @@ class Chef
|
|
11
11
|
option(:polling,
|
12
12
|
:short => '-p',
|
13
13
|
:long => '--[no-]poll',
|
14
|
+
:boolean => true,
|
15
|
+
:default => false,
|
14
16
|
:description => 'Poll events while stack status is "in progress"',
|
15
|
-
:proc => lambda {|v| Chef::Config[:knife][:cloudformation][:
|
17
|
+
:proc => lambda {|v| Chef::Config[:knife][:cloudformation][:poll] = v }
|
16
18
|
)
|
17
19
|
|
18
20
|
option(:attribute,
|
@@ -26,6 +26,13 @@ class Chef
|
|
26
26
|
}
|
27
27
|
)
|
28
28
|
|
29
|
+
option(:nodes,
|
30
|
+
:short => '-N',
|
31
|
+
:long => '--nodes',
|
32
|
+
:boolean => true,
|
33
|
+
:description => 'Display ec2 nodes of stack'
|
34
|
+
)
|
35
|
+
|
29
36
|
option(:ssh_user,
|
30
37
|
:short => '-x SSH_USER',
|
31
38
|
:long => '--ssh-user SSH_USER',
|
@@ -40,6 +47,17 @@ class Chef
|
|
40
47
|
if(config[:instance_failure])
|
41
48
|
do_instance_failure(stack_name)
|
42
49
|
end
|
50
|
+
if(config[:nodes])
|
51
|
+
do_node_list(stack_name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def do_node_list(stack_name)
|
56
|
+
nodes = stack(stack_name).nodes.map do |n|
|
57
|
+
[n.id, n.public_ip_address]
|
58
|
+
end.flatten
|
59
|
+
ui.info "Nodes for stack: #{ui.color(stack_name, :bold)}"
|
60
|
+
ui.info "#{ui.list(nodes, :uneven_columns_across, 2)}"
|
43
61
|
end
|
44
62
|
|
45
63
|
def do_instance_failure(stack_name)
|
@@ -5,11 +5,18 @@ module KnifeCloudformation
|
|
5
5
|
class Stack
|
6
6
|
|
7
7
|
include KnifeCloudformation::Utils::JSON
|
8
|
+
include KnifeCloudformation::Utils::AnimalStrings
|
8
9
|
|
9
10
|
attr_reader :name, :raw_stack, :raw_resources, :common
|
10
11
|
|
11
12
|
class << self
|
12
13
|
|
14
|
+
ALLOWED_PARAMETER_ATTRIBUTES = %w(
|
15
|
+
Type Default NoEcho AllowedValues AllowedPattern
|
16
|
+
MaxLength MinLength MaxValue MinValue Description
|
17
|
+
ConstraintDescription
|
18
|
+
)
|
19
|
+
|
13
20
|
include KnifeCloudformation::Utils::JSON
|
14
21
|
|
15
22
|
def create(name, definition, aws_common)
|
@@ -26,6 +33,7 @@ module KnifeCloudformation
|
|
26
33
|
stack[format_key] = value
|
27
34
|
end
|
28
35
|
enable_capabilities!(stack, template)
|
36
|
+
clean_parameters!(template)
|
29
37
|
stack['TemplateBody'] = _to_json(template)
|
30
38
|
stack
|
31
39
|
end
|
@@ -40,13 +48,25 @@ module KnifeCloudformation
|
|
40
48
|
nil
|
41
49
|
end
|
42
50
|
|
51
|
+
def clean_parameters!(template)
|
52
|
+
template['Parameters'].each do |name, options|
|
53
|
+
options.delete_if do |attribute, value|
|
54
|
+
!ALLOWED_PARAMETER_ATTRIBUTES.include?(attribute)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
43
59
|
end
|
44
60
|
|
45
|
-
def initialize(name, common)
|
61
|
+
def initialize(name, common, raw_stack=nil)
|
46
62
|
@name = name
|
47
63
|
@common = common
|
48
64
|
@memo = {}
|
49
|
-
|
65
|
+
if(raw_stack)
|
66
|
+
@raw_stack = raw_stack
|
67
|
+
else
|
68
|
+
load_stack
|
69
|
+
end
|
50
70
|
@force_refresh = false
|
51
71
|
@force_refresh = in_progress?
|
52
72
|
end
|
@@ -54,11 +74,25 @@ module KnifeCloudformation
|
|
54
74
|
## Actions ##
|
55
75
|
|
56
76
|
def update(definition)
|
77
|
+
if(definition.keys.detect{|k|k.is_a?(Symbol)})
|
78
|
+
definition = format_definition(definition)
|
79
|
+
end
|
57
80
|
res = common.aws(:cloud_formation).update_stack(name, definition)
|
58
81
|
reload!
|
59
82
|
res
|
60
83
|
end
|
61
84
|
|
85
|
+
def format_definition(def_hash)
|
86
|
+
new_hash = {}
|
87
|
+
def_hash.each do |k,v|
|
88
|
+
new_hash[camel(k)] = v
|
89
|
+
end
|
90
|
+
if(new_hash['TemplateBody'].is_a?(Hash))
|
91
|
+
new_hash['TemplateBody'] = _to_json(new_hash['TemplateBody'])
|
92
|
+
end
|
93
|
+
new_hash
|
94
|
+
end
|
95
|
+
|
62
96
|
def destroy
|
63
97
|
res = common.aws(:cloud_formation).delete_stack(name)
|
64
98
|
reload!
|
@@ -66,14 +100,18 @@ module KnifeCloudformation
|
|
66
100
|
end
|
67
101
|
|
68
102
|
def load_stack
|
69
|
-
@raw_stack = common.aws(:cloud_formation)
|
103
|
+
@raw_stack = common.aws(:cloud_formation)
|
104
|
+
.describe_stacks('StackName' => name)
|
105
|
+
.body['Stacks'].first
|
70
106
|
end
|
71
107
|
|
72
108
|
def load_resources
|
73
|
-
@raw_resources = common.aws(:cloud_formation)
|
109
|
+
@raw_resources = common.aws(:cloud_formation)
|
110
|
+
.describe_stack_resources('StackName' => name)
|
111
|
+
.body['StackResources']
|
74
112
|
end
|
75
113
|
|
76
|
-
def refresh?(bool)
|
114
|
+
def refresh?(bool=false)
|
77
115
|
bool || (bool.nil? && @force_refresh)
|
78
116
|
end
|
79
117
|
|
@@ -93,7 +131,7 @@ module KnifeCloudformation
|
|
93
131
|
|
94
132
|
def to_hash(extra_data={})
|
95
133
|
{
|
96
|
-
:
|
134
|
+
:template_body => template,
|
97
135
|
:parameters => parameters,
|
98
136
|
:capabilities => capabilities,
|
99
137
|
:disable_rollback => disable_rollback,
|
@@ -112,15 +150,20 @@ module KnifeCloudformation
|
|
112
150
|
@memo[:template]
|
113
151
|
end
|
114
152
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
153
|
+
## Stack metadata ##
|
154
|
+
def parameters(raw=false)
|
155
|
+
if(raw)
|
156
|
+
@raw_stack['Parameters']
|
157
|
+
else
|
158
|
+
unless(@memo[:parameters])
|
159
|
+
@memo[:parameters] = Hash[*(
|
160
|
+
@raw_stack['Parameters'].map do |ary|
|
161
|
+
[ary['ParameterKey'], ary['ParameterValue']]
|
162
|
+
end.flatten
|
163
|
+
)]
|
164
|
+
end
|
165
|
+
@memo[:parameters]
|
122
166
|
end
|
123
|
-
@memo[:parameters]
|
124
167
|
end
|
125
168
|
|
126
169
|
def capabilities
|
@@ -135,6 +178,21 @@ module KnifeCloudformation
|
|
135
178
|
@raw_stack['NotificationARNs']
|
136
179
|
end
|
137
180
|
|
181
|
+
def timeout_in_minutes
|
182
|
+
@raw_stack['TimeoutInMinutes']
|
183
|
+
end
|
184
|
+
alias_method :timeout_in_minutes, :timeout
|
185
|
+
|
186
|
+
def stack_id
|
187
|
+
@raw_stack['StackId']
|
188
|
+
end
|
189
|
+
alias_method :id, :stack_id
|
190
|
+
|
191
|
+
def creation_time
|
192
|
+
@raw_stack['CreationTime']
|
193
|
+
end
|
194
|
+
alias_method :created_at, :creation_time
|
195
|
+
|
138
196
|
def status(force_refresh=nil)
|
139
197
|
load_stack if refresh?(force_refresh)
|
140
198
|
@raw_stack['StackStatus']
|
@@ -148,10 +206,11 @@ module KnifeCloudformation
|
|
148
206
|
def events(all=false)
|
149
207
|
res = common.aws(:cloud_formation).describe_stack_events(name).body['StackEvents']
|
150
208
|
@memo[:events] ||= []
|
151
|
-
|
152
|
-
|
209
|
+
current = @memo[:events].map{|e| e['EventId']}
|
210
|
+
res.delete_if{|e| current.include?(e['EventId'])}
|
211
|
+
@memo[:events] += res
|
153
212
|
@memo[:events].uniq!
|
154
|
-
res
|
213
|
+
all ? @memo[:events] : res
|
155
214
|
end
|
156
215
|
|
157
216
|
def outputs(style=:unformatted)
|
@@ -201,19 +260,24 @@ module KnifeCloudformation
|
|
201
260
|
|
202
261
|
def expand_resource(resource)
|
203
262
|
kind = resource['ResourceType'].split('::')[1]
|
204
|
-
kind_snake =
|
263
|
+
kind_snake = snake(kind)
|
205
264
|
aws = common.aws(kind_snake)
|
206
|
-
aws.send("#{
|
265
|
+
aws.send("#{snake(resource['ResourceType'].split('::').last).to_s.split('_').last}s").get(resource['PhysicalResourceId'])
|
207
266
|
end
|
208
267
|
|
209
268
|
def nodes
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
269
|
+
reload! if refresh?
|
270
|
+
unless(@memo[:nodes])
|
271
|
+
as_resources = resources.find_all{|r|r['ResourceType'] == 'AWS::AutoScaling::AutoScalingGroup'}
|
272
|
+
@memo[:nodes] =
|
273
|
+
as_resources.map do |as_resource|
|
274
|
+
as_group = expand_resource(as_resource)
|
275
|
+
as_group.instances.map do |inst|
|
276
|
+
common.aws(:ec2).servers.get(inst.id)
|
277
|
+
end
|
278
|
+
end.flatten
|
279
|
+
end
|
280
|
+
@memo[:nodes]
|
217
281
|
end
|
218
282
|
|
219
283
|
end
|
@@ -11,6 +11,7 @@ module KnifeCloudformation
|
|
11
11
|
include KnifeCloudformation::Utils::AnimalStrings
|
12
12
|
|
13
13
|
def validate(value, parameter_definition)
|
14
|
+
return [[:blank, 'Value cannot be blank']] if value.to_s.strip.empty?
|
14
15
|
result = %w(AllowedValues AllowedPattern MaxLength MinLength MaxValue MinValue).map do |key|
|
15
16
|
if(parameter_definition[key])
|
16
17
|
res = self.send(snake(key), value, parameter_definition)
|
@@ -23,12 +23,25 @@ module KnifeCloudformation
|
|
23
23
|
}
|
24
24
|
end
|
25
25
|
|
26
|
+
def clear_cache(*types)
|
27
|
+
keys = types.empty? ? @memo.keys : types.map(&:to_sym)
|
28
|
+
keys.each do |key|
|
29
|
+
@memo[key].clear if @memo[key]
|
30
|
+
end
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
26
34
|
def build_connection(type)
|
27
35
|
type = type.to_sym
|
28
36
|
type = FOG_MAP[type] if FOG_MAP[type]
|
29
37
|
unless(@connections[type])
|
30
|
-
|
38
|
+
case type
|
39
|
+
when :compute
|
31
40
|
@connections[:compute] = Fog::Compute::AWS.new(@creds)
|
41
|
+
when :dns
|
42
|
+
dns_creds = @creds.dup
|
43
|
+
dns_creds.delete(:region) || dns_creds.delete('region')
|
44
|
+
@connections[:dns] = Fog::DNS::AWS.new(dns_creds)
|
32
45
|
else
|
33
46
|
Fog.credentials = Fog.symbolize_credentials(@creds)
|
34
47
|
@connections[type] = Fog::AWS[type]
|
@@ -69,11 +82,44 @@ module KnifeCloudformation
|
|
69
82
|
@memo[:stack_list][key]
|
70
83
|
end
|
71
84
|
|
72
|
-
def
|
73
|
-
|
74
|
-
|
85
|
+
def name_from_stack_id(name)
|
86
|
+
found = stacks.detect do |s|
|
87
|
+
s['StackId'] == name
|
88
|
+
end
|
89
|
+
if(found)
|
90
|
+
s['StackName']
|
91
|
+
else
|
92
|
+
raise "Failed to locate stack with ID: #{name}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def stack(*names)
|
97
|
+
names = names.map do |name|
|
98
|
+
if(name.start_with?('arn:'))
|
99
|
+
name_from_stack_id(name)
|
100
|
+
else
|
101
|
+
name
|
102
|
+
end
|
103
|
+
end
|
104
|
+
if(names.size == 1)
|
105
|
+
name = names.first
|
106
|
+
unless(@memo[:stacks][name])
|
107
|
+
@memo[:stacks][name] = Stack.new(name, self)
|
108
|
+
end
|
109
|
+
@memo[:stacks][name]
|
110
|
+
else
|
111
|
+
to_fetch = names - @memo[:stacks].keys
|
112
|
+
slim_stacks = {}
|
113
|
+
unless(to_fetch.empty?)
|
114
|
+
to_fetch.each do |name|
|
115
|
+
slim_stacks[name] = Stack.new(name, self, stacks.detect{|s| s['StackName'] == name})
|
116
|
+
end
|
117
|
+
end
|
118
|
+
result = names.map do |n|
|
119
|
+
@memo[:stacks][n] || slim_stacks[n]
|
120
|
+
end
|
121
|
+
result
|
75
122
|
end
|
76
|
-
@memo[:stacks][name]
|
77
123
|
end
|
78
124
|
|
79
125
|
def create_stack(name, definition)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-cloudformation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-09-
|
12
|
+
date: 2013-09-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: chef
|
@@ -34,7 +34,7 @@ dependencies:
|
|
34
34
|
requirements:
|
35
35
|
- - ~>
|
36
36
|
- !ruby/object:Gem::Version
|
37
|
-
version: 1.
|
37
|
+
version: '1.15'
|
38
38
|
type: :runtime
|
39
39
|
prerelease: false
|
40
40
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -42,7 +42,23 @@ dependencies:
|
|
42
42
|
requirements:
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
|
-
version: 1.
|
45
|
+
version: '1.15'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: net-sftp
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
46
62
|
- !ruby/object:Gem::Dependency
|
47
63
|
name: attribute_struct
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|