knife-cloudformation 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +2 -0
- data/README.md +3 -0
- data/knife-cloudformation.gemspec +16 -0
- data/lib/chef/knife/cloudformation_base.rb +177 -0
- data/lib/chef/knife/cloudformation_create.rb +178 -0
- data/lib/chef/knife/cloudformation_describe.rb +57 -0
- data/lib/chef/knife/cloudformation_destroy.rb +28 -0
- data/lib/chef/knife/cloudformation_events.rb +85 -0
- data/lib/chef/knife/cloudformation_list.rb +63 -0
- data/lib/knife-cloudformation.rb +1 -0
- data/lib/knife-cloudformation/sparkle_formation.rb +70 -0
- data/lib/knife-cloudformation/version.rb +6 -0
- metadata +105 -0
data/CHANGELOG.md
ADDED
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) + '/lib/'
|
2
|
+
require 'knife-cloudformation/version'
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'knife-cloudformation'
|
5
|
+
s.version = KnifeCloudformation::VERSION.version
|
6
|
+
s.summary = 'Knife tooling for Cloud Formation'
|
7
|
+
s.author = 'Chris Roberts'
|
8
|
+
s.email = 'chrisroberts.code@gmail.com'
|
9
|
+
s.homepage = 'http://github.com/heavywater/knife-cloudformation'
|
10
|
+
s.description = 'Knife tooling for Cloud Formation'
|
11
|
+
s.require_path = 'lib'
|
12
|
+
s.add_dependency 'chef'
|
13
|
+
s.add_dependency 'fog', '~> 1.12.1'
|
14
|
+
s.add_dependency 'attribute_struct', '~> 0.1.2'
|
15
|
+
s.files = Dir['**/*']
|
16
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'chef/knife'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
module CloudformationDefault
|
6
|
+
class << self
|
7
|
+
def included(klass)
|
8
|
+
klass.instance_eval do
|
9
|
+
deps do
|
10
|
+
require 'fog'
|
11
|
+
Chef::Config[:knife][:cloudformation] ||= Mash.new
|
12
|
+
Chef::Config[:knife][:cloudformation][:credentials] ||= Mash.new
|
13
|
+
Chef::Config[:knife][:cloudformation][:options] ||= Mash.new
|
14
|
+
end
|
15
|
+
|
16
|
+
option(:key,
|
17
|
+
:short => '-K KEY',
|
18
|
+
:long => '--key KEY',
|
19
|
+
:description => 'AWS access key id',
|
20
|
+
:proc => lambda {|val|
|
21
|
+
Chef::Config[:knife][:cloudformation][:credentials][:key] = val
|
22
|
+
}
|
23
|
+
)
|
24
|
+
option(:secret,
|
25
|
+
:short => '-S SECRET',
|
26
|
+
:long => '--secret SECRET',
|
27
|
+
:description => 'AWS secret access key',
|
28
|
+
:proc => lambda {|val|
|
29
|
+
Chef::Config[:knife][:cloudformation][:credentials][:secret] = val
|
30
|
+
}
|
31
|
+
)
|
32
|
+
option(:region,
|
33
|
+
:short => '-r REGION',
|
34
|
+
:long => '--region REGION',
|
35
|
+
:description => 'AWS region',
|
36
|
+
:proc => lambda {|val|
|
37
|
+
Chef::Config[:knife][:cloudformation][:credentials][:region] = val
|
38
|
+
}
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
class CloudformationBase < Knife
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def aws_con
|
49
|
+
@connection ||= Fog::AWS::CloudFormation.new(
|
50
|
+
:aws_access_key_id => _key,
|
51
|
+
:aws_secret_access_key => _secret,
|
52
|
+
:region => _region
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
def _key
|
57
|
+
Chef::Config[:knife][:cloudformation][:credentials][:key] ||
|
58
|
+
Chef::Config[:knife][:aws_access_key_id]
|
59
|
+
end
|
60
|
+
|
61
|
+
def _secret
|
62
|
+
Chef::Config[:knife][:cloudformation][:credentials][:secret] ||
|
63
|
+
Chef::Config[:knife][:aws_secret_access_key]
|
64
|
+
end
|
65
|
+
|
66
|
+
def _region
|
67
|
+
Chef::Config[:knife][:cloudformation][:credentials][:region] ||
|
68
|
+
Chef::Config[:knife][:region]
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def _debug(e, *args)
|
74
|
+
if(ENV['DEBUG'])
|
75
|
+
ui.fatal "Exception information: #{e.class}: #{e}\n#{e.backtrace.join("\n")}\n"
|
76
|
+
args.each do |string|
|
77
|
+
ui.fatal string
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def aws_con
|
83
|
+
self.class.aws_con
|
84
|
+
end
|
85
|
+
|
86
|
+
def stack_status(name)
|
87
|
+
aws_con.describe_stacks('StackName' => name).body['Stacks'].first['StackStatus']
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_titles(thing, format=false)
|
91
|
+
unless(@titles)
|
92
|
+
hash = thing.is_a?(Array) ? thing.first : thing
|
93
|
+
hash ||= {}
|
94
|
+
@titles = hash.keys.map do |key|
|
95
|
+
next unless attribute_allowed?(key)
|
96
|
+
key.gsub(/([a-z])([A-Z])/, '\1 \2')
|
97
|
+
end.compact
|
98
|
+
end
|
99
|
+
if(format)
|
100
|
+
@titles.map{|s| ui.color(s, :bold)}
|
101
|
+
else
|
102
|
+
@titles
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def process(things)
|
107
|
+
@event_ids ||= []
|
108
|
+
things.reverse.map do |thing|
|
109
|
+
next if @event_ids.include?(thing['EventId'])
|
110
|
+
@event_ids.push(thing['EventId']).compact!
|
111
|
+
get_titles(thing).map do |key|
|
112
|
+
thing[key.gsub(' ', '')].to_s
|
113
|
+
end
|
114
|
+
end.flatten.compact
|
115
|
+
end
|
116
|
+
|
117
|
+
def allowed_attributes
|
118
|
+
Chef::Config[:knife][:cloudformation][:attributes] || default_attributes
|
119
|
+
end
|
120
|
+
|
121
|
+
def default_attributes
|
122
|
+
%w(Timestamp StackName StackId)
|
123
|
+
end
|
124
|
+
|
125
|
+
def attribute_allowed?(attr)
|
126
|
+
config[:all_attributes] || allowed_attributes.include?(attr)
|
127
|
+
end
|
128
|
+
|
129
|
+
def things_output(stack, things, what)
|
130
|
+
output = get_titles(things, :format)
|
131
|
+
output += process(things)
|
132
|
+
output.compact.flatten
|
133
|
+
if(output.empty?)
|
134
|
+
ui.warn 'No information found'
|
135
|
+
else
|
136
|
+
ui.info "#{what.to_s.capitalize} for stack: #{ui.color(stack, :bold)}" if stack
|
137
|
+
ui.info "#{ui.list(output, :uneven_columns_across, get_titles(things).size)}\n"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_things(stack=nil, message=nil)
|
142
|
+
begin
|
143
|
+
yield
|
144
|
+
rescue => e
|
145
|
+
ui.fatal "#{message || 'Failed to retrieve information'}#{" for requested stack: #{stack}" if stack}"
|
146
|
+
ui.fatal "Reason: #{e}"
|
147
|
+
_debug(e)
|
148
|
+
exit 1
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def try_json_compat
|
153
|
+
begin
|
154
|
+
require 'chef/json_compat'
|
155
|
+
rescue
|
156
|
+
end
|
157
|
+
defined?(Chef::JSONCompat)
|
158
|
+
end
|
159
|
+
|
160
|
+
def _to_json(thing)
|
161
|
+
if(try_json_compat)
|
162
|
+
Chef::JSONCompat.to_json(thing)
|
163
|
+
else
|
164
|
+
JSON.dump(thing)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def _from_json(thing)
|
169
|
+
if(try_json_compat)
|
170
|
+
Chef::JSONCompat.from_json(thing)
|
171
|
+
else
|
172
|
+
JSON.read(thing)
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
require 'knife-cloudformation/sparkle_formation'
|
2
|
+
require 'chef/knife/cloudformation_base'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
class Knife
|
6
|
+
class CloudformationCreate < CloudformationBase
|
7
|
+
|
8
|
+
banner 'knife cloudformation create NAME'
|
9
|
+
|
10
|
+
include CloudformationDefault
|
11
|
+
|
12
|
+
option(:parameter,
|
13
|
+
:short => '-p KEY:VALUE',
|
14
|
+
:long => '--parameter KEY:VALUE',
|
15
|
+
:description => 'Set parameter. Can be used multiple times.',
|
16
|
+
:proc => lambda {|val|
|
17
|
+
key,value = val.split(':')
|
18
|
+
Chef::Config[:knife][:cloudformation][:options][:parameters] ||= Mash.new
|
19
|
+
Chef::Config[:knife][:cloudformation][:options][:parameters][key] = value
|
20
|
+
}
|
21
|
+
)
|
22
|
+
option(:timeout,
|
23
|
+
:short => '-t MIN',
|
24
|
+
:long => '--timeout MIN',
|
25
|
+
:description => 'Set timeout for stack creation',
|
26
|
+
:proc => lambda {|val|
|
27
|
+
Chef::Config[:knife][:cloudformation][:options][:timeout_in_minutes] = val
|
28
|
+
}
|
29
|
+
)
|
30
|
+
option(:disable_rollback,
|
31
|
+
:short => '-R',
|
32
|
+
:long => '--disable-rollback',
|
33
|
+
:description => 'Disable rollback on stack creation failure',
|
34
|
+
:proc => lambda { Chef::Config[:knife][:cloudformation][:options][:disable_rollback] = true }
|
35
|
+
)
|
36
|
+
option(:capability,
|
37
|
+
:short => '-C CAPABILITY',
|
38
|
+
:long => '--capability CAPABILITY',
|
39
|
+
:description => 'Specify allowed capabilities. Can be used multiple times.',
|
40
|
+
:proc => lambda {|val|
|
41
|
+
Chef::Config[:knife][:cloudformation][:options][:capabilities] ||= []
|
42
|
+
Chef::Config[:knife][:cloudformation][:options][:capabilities].push(val).uniq!
|
43
|
+
}
|
44
|
+
)
|
45
|
+
option(:enable_processing,
|
46
|
+
:long => '--enable-processing',
|
47
|
+
:description => 'Call the unicorns.',
|
48
|
+
:proc => lambda { Chef::Config[:knife][:cloudformation][:options][:enable_processing] = true }
|
49
|
+
)
|
50
|
+
option(:disable_polling,
|
51
|
+
:long => '--disable-polling',
|
52
|
+
:description => 'Disable stack even polling.',
|
53
|
+
:proc => lambda { Chef::Config[:knife][:cloudformation][:options][:disable_polling] = true }
|
54
|
+
)
|
55
|
+
option(:notifications,
|
56
|
+
:long => '--notification ARN',
|
57
|
+
:description => 'Add notification ARN. Can be used multiple times.',
|
58
|
+
:proc => lambda {|val|
|
59
|
+
Chef::Config[:knife][:cloudformation][:options][:notification_ARNs] ||= []
|
60
|
+
Chef::Config[:knife][:cloudformation][:options][:notification_ARNs].push(val).uniq!
|
61
|
+
}
|
62
|
+
)
|
63
|
+
option(:file,
|
64
|
+
:short => '-F PATH',
|
65
|
+
:long => '--file PATH',
|
66
|
+
:description => 'Path to Cloud Formation to process',
|
67
|
+
:proc => lambda {|val|
|
68
|
+
Chef::Config[:knife][:cloudformation][:file] = val
|
69
|
+
}
|
70
|
+
)
|
71
|
+
option(:disable_interactive_parameters,
|
72
|
+
:long => '--no-parameter-prompts',
|
73
|
+
:description => 'Do not prompt for input on dynamic parameters'
|
74
|
+
)
|
75
|
+
|
76
|
+
def run
|
77
|
+
unless(File.exists?(Chef::Config[:knife][:cloudformation][:file].to_s))
|
78
|
+
ui.fatal "Invalid formation file path provided: #{Chef::Config[:knife][:cloudformation][:file]}"
|
79
|
+
exit 1
|
80
|
+
end
|
81
|
+
name = name_args.first
|
82
|
+
if(Chef::Config[:knife][:cloudformation][:enable_processing])
|
83
|
+
file = KnifeCloudformation::SparkleFormation.compile(Chef::Config[:knife][:cloudformation][:file])
|
84
|
+
else
|
85
|
+
file = File.read(Chef::Config[:knife][:cloudformation][:file])
|
86
|
+
end
|
87
|
+
ui.info "#{ui.color('Cloud Formation: ', :bold)} #{ui.color('CREATE', :green)}"
|
88
|
+
ui.info " -> #{ui.color('Name:', :bold)} #{name} #{ui.color('Path:', :bold)} #{Chef::Config[:knife][:cloudformation][:file]} #{ui.color('(not pre-processed)', :yellow) if Chef::Config[:knife][:cloudformation][:disable_processing]}"
|
89
|
+
stack = build_stack(file)
|
90
|
+
create_stack(name, stack)
|
91
|
+
unless(Chef::Config[:knife][:cloudformation][:disable_polling])
|
92
|
+
poll_stack(name)
|
93
|
+
else
|
94
|
+
ui.warn 'Stack state polling has been disabled.'
|
95
|
+
end
|
96
|
+
ui.info "Stack creation complete: #{ui.color('SUCCESS', :green)}"
|
97
|
+
end
|
98
|
+
|
99
|
+
def build_stack(template)
|
100
|
+
stack = Mash.new
|
101
|
+
Chef::Config[:knife][:cloudformation][:options].each do |key, value|
|
102
|
+
format_key = key.split('_').map(&:capitalize).join
|
103
|
+
case value
|
104
|
+
when Hash
|
105
|
+
i = 1
|
106
|
+
value.each do |k, v|
|
107
|
+
stack["#{format_key}.member.#{i}.#{format_key[0, (format_key.length - 1)]}Key"] = k
|
108
|
+
stack["#{format_key}.member.#{i}.#{format_key[0, (format_key.length - 1)]}Value"] = v
|
109
|
+
end
|
110
|
+
when Array
|
111
|
+
value.each_with_index do |v, i|
|
112
|
+
stack["#{format_key}.member.#{i+1}"] = v
|
113
|
+
end
|
114
|
+
else
|
115
|
+
stack[format_key] = value
|
116
|
+
end
|
117
|
+
end
|
118
|
+
populate_parameters!(template)
|
119
|
+
stack['TemplateBody'] = Chef::JSONCompat.to_json(template)
|
120
|
+
stack
|
121
|
+
end
|
122
|
+
|
123
|
+
def populate_parameters!(stack)
|
124
|
+
unless(config[:disable_interactive_parameters])
|
125
|
+
if(stack['Parameters'])
|
126
|
+
Chef::Config[:knife][:cloudformation][:options][:parameters] ||= Mash.new
|
127
|
+
stack['Parameters'].each do |k,v|
|
128
|
+
valid = false
|
129
|
+
until(valid)
|
130
|
+
default = Chef::Config[:knife][:cloudformation][:options][:parameters][k] || v['Default']
|
131
|
+
answer = ui.ask_question("#{k.split(/([A-Z]+[^A-Z]*)/).find_all{|s|!s.empty?}.join(' ')} ", :default => default)
|
132
|
+
if(v['AllowedValues'])
|
133
|
+
valid = v['AllowedValues'].include?(answer)
|
134
|
+
else
|
135
|
+
valid = true
|
136
|
+
end
|
137
|
+
if(valid)
|
138
|
+
Chef::Config[:knife][:cloudformation][:options][:parameters][k] = answer
|
139
|
+
else
|
140
|
+
ui.error "Not an allowed value: #{v['AllowedValues'].join(', ')}"
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def create_stack(name, stack)
|
149
|
+
begin
|
150
|
+
res = aws_con.create_stack(name, stack)
|
151
|
+
rescue => e
|
152
|
+
ui.fatal "Failed to create stack #{name}. Reason: #{e}"
|
153
|
+
_debug(e, "Generated template used:\n#{stack.inspect}")
|
154
|
+
exit 1
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def poll_stack(name)
|
159
|
+
knife_events = Chef::Knife::CloudformationEvents.new
|
160
|
+
knife_events.name_args.push(name)
|
161
|
+
Chef::Config[:knife][:cloudformation][:poll] = true
|
162
|
+
knife_events.run
|
163
|
+
unless(create_successful?(name))
|
164
|
+
ui.fatal "Creation of new stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
|
165
|
+
exit 1
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def create_in_progress?(name)
|
170
|
+
stack_status(name) == 'CREATE_IN_PROGRESS'
|
171
|
+
end
|
172
|
+
|
173
|
+
def create_successful?(name)
|
174
|
+
stack_status(name) == 'CREATE_COMPLETE'
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'chef/knife/cloudformation_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class CloudformationDescribe < CloudformationBase
|
6
|
+
include CloudformationDefault
|
7
|
+
|
8
|
+
banner 'knife cloudformation describe NAME'
|
9
|
+
|
10
|
+
option(:resources,
|
11
|
+
:short => '-r',
|
12
|
+
:long => '--resources',
|
13
|
+
:description => 'Display resources for stack(s)'
|
14
|
+
)
|
15
|
+
|
16
|
+
option(:attribute,
|
17
|
+
:short => '-a ATTR',
|
18
|
+
:long => '--attribute ATTR',
|
19
|
+
:description => 'Attribute to print. Can be used multiple times.',
|
20
|
+
:proc => lambda {|val|
|
21
|
+
Chef::Config[:knife][:cloudformation][:attributes] ||= []
|
22
|
+
Chef::Config[:knife][:cloudformation][:attributes].push(val).uniq!
|
23
|
+
}
|
24
|
+
)
|
25
|
+
|
26
|
+
option(:all,
|
27
|
+
:long => '--all-attributes',
|
28
|
+
:description => 'Print all attributes'
|
29
|
+
)
|
30
|
+
|
31
|
+
def run
|
32
|
+
name_args.each do |stack|
|
33
|
+
things_output(stack,
|
34
|
+
*(config[:resources] ? [get_resources(stack), :resources] : [get_description(stack), :description])
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def default_attributes
|
40
|
+
config[:resources] ? %w(Timestamp ResourceType ResourceStatus) : %w(StackName CreationTime StackStatus DisableRollback)
|
41
|
+
end
|
42
|
+
|
43
|
+
def get_description(stack)
|
44
|
+
get_things(stack) do
|
45
|
+
[aws_con.describe_stacks('StackName' => stack).body['Stacks'].first]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def get_resources(stack)
|
50
|
+
get_things(stack) do
|
51
|
+
aws_con.describe_stack_resources('StackName' => stack).body['StackResources']
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'chef/knife/cloudformation_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class CloudformationDestroy < CloudformationBase
|
6
|
+
|
7
|
+
include CloudformationDefault
|
8
|
+
|
9
|
+
banner 'knife cloudformation destroy NAME[ NAME ...]'
|
10
|
+
|
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
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy_formation!(stack_name)
|
21
|
+
get_things(stack_name, 'Failed to perform destruction') do
|
22
|
+
aws_con.delete_stack(stack_name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'chef/knife/cloudformation_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class CloudformationEvents < CloudformationBase
|
6
|
+
|
7
|
+
banner 'knife cloudformation events NAME'
|
8
|
+
|
9
|
+
include CloudformationDefault
|
10
|
+
|
11
|
+
option(:poll,
|
12
|
+
:short => '-p',
|
13
|
+
:long => '--poll',
|
14
|
+
:description => 'Poll events while stack status is "in progress"',
|
15
|
+
:proc => lambda {|v| Chef::Config[:knife][:cloudformation][:poll] = true }
|
16
|
+
)
|
17
|
+
|
18
|
+
option(:attribute,
|
19
|
+
:short => '-a ATTR',
|
20
|
+
:long => '--attribute ATTR',
|
21
|
+
:description => 'Attribute to print. Can be used multiple times.',
|
22
|
+
:proc => lambda {|val|
|
23
|
+
Chef::Config[:knife][:cloudformation][:attributes] ||= []
|
24
|
+
Chef::Config[:knife][:cloudformation][:attributes].push(val).uniq!
|
25
|
+
}
|
26
|
+
)
|
27
|
+
|
28
|
+
option(:all_attributes,
|
29
|
+
:long => '--all-attributes',
|
30
|
+
:description => 'Print all attributes'
|
31
|
+
)
|
32
|
+
|
33
|
+
def run
|
34
|
+
name = name_args.first
|
35
|
+
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)
|
40
|
+
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 = {}
|
52
|
+
end
|
53
|
+
res = aws_con.describe_stack_events(name, options)
|
54
|
+
@_stack_events[name] = res.body['StackToken']
|
55
|
+
res.body['StackEvents']
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
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(1)
|
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)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def stack_in_progress?(name)
|
77
|
+
stack_status(name).downcase.include?('in_progress')
|
78
|
+
end
|
79
|
+
|
80
|
+
def default_attributes
|
81
|
+
%w(Timestamp LogicalResourceId ResourceType ResourceStatus)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'chef/knife/cloudformation_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class CloudformationList < CloudformationBase
|
6
|
+
include CloudformationDefault
|
7
|
+
|
8
|
+
banner 'knife cloudformation list NAME'
|
9
|
+
|
10
|
+
option(:attribute,
|
11
|
+
:short => '-a ATTR',
|
12
|
+
:long => '--attribute ATTR',
|
13
|
+
:description => 'Attribute to print. Can be used multiple times.',
|
14
|
+
:proc => lambda {|val|
|
15
|
+
Chef::Config[:knife][:cloudformation][:attributes] ||= []
|
16
|
+
Chef::Config[:knife][:cloudformation][:attributes].push(val).uniq!
|
17
|
+
}
|
18
|
+
)
|
19
|
+
|
20
|
+
option(:all,
|
21
|
+
:long => '--all-attributes',
|
22
|
+
:description => 'Print all attributes'
|
23
|
+
)
|
24
|
+
|
25
|
+
option(:status,
|
26
|
+
:short => '-S STATUS',
|
27
|
+
:long => '--status STATUS',
|
28
|
+
:description => 'Match given status. Use "none" to disable. Can be used multiple times.',
|
29
|
+
:proc => lambda {|val|
|
30
|
+
Chef::Config[:knife][:cloudformation][:status] ||= []
|
31
|
+
Chef::Config[:knife][:cloudformation][:status].push(val).uniq!
|
32
|
+
}
|
33
|
+
)
|
34
|
+
|
35
|
+
def run
|
36
|
+
things_output(nil, get_list, nil)
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_list
|
40
|
+
get_things do
|
41
|
+
aws_con.list_stacks(aws_filter_hash).body['StackSummaries']
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def default_attributes
|
46
|
+
%w(StackName CreationTime StackStatus TemplateDescription)
|
47
|
+
end
|
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
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'knife-cloudformation/version'
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'chef/mash'
|
2
|
+
require 'attribute_struct'
|
3
|
+
|
4
|
+
AttributeStruct.camel_keys = true
|
5
|
+
|
6
|
+
module KnifeCloudformation
|
7
|
+
class SparkleFormation
|
8
|
+
class << self
|
9
|
+
def compile(path)
|
10
|
+
formation = self.instance_eval(IO.read(path), path, 1)
|
11
|
+
formation.compile._dump
|
12
|
+
end
|
13
|
+
|
14
|
+
def build(&block)
|
15
|
+
struct = AttributeStruct.new
|
16
|
+
struct.instance_exec(&block)
|
17
|
+
struct
|
18
|
+
end
|
19
|
+
|
20
|
+
def load_component(path)
|
21
|
+
self.instance_eval(IO.read(path), path, 1)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :name
|
27
|
+
attr_reader :sparkle_path
|
28
|
+
attr_reader :components
|
29
|
+
attr_reader :load_order
|
30
|
+
|
31
|
+
def initialize(name, options={})
|
32
|
+
@name = name
|
33
|
+
@sparkle_path = options[:sparkle_path] || File.join(Dir.pwd, 'cloudformation/components')
|
34
|
+
@components = Mash.new
|
35
|
+
@load_order = []
|
36
|
+
end
|
37
|
+
|
38
|
+
def load(*args)
|
39
|
+
args.each do |thing|
|
40
|
+
if(thing.is_a?(Symbol))
|
41
|
+
path = File.join(sparkle_path, "#{thing}.rb")
|
42
|
+
else
|
43
|
+
path = thing
|
44
|
+
end
|
45
|
+
key = File.basename(path).sub('.rb', '')
|
46
|
+
components[key] = self.class.load_component(path)
|
47
|
+
@load_order << key
|
48
|
+
end
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def overrides(&block)
|
53
|
+
@overrides = self.class.build(&block)
|
54
|
+
self
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns compiled Mash instance
|
58
|
+
def compile
|
59
|
+
compiled = AttributeStruct.new
|
60
|
+
@load_order.each do |key|
|
61
|
+
compiled._merge!(components[key])
|
62
|
+
end
|
63
|
+
if(@overrides)
|
64
|
+
compiled._merge!(@overrides)
|
65
|
+
end
|
66
|
+
compiled
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: knife-cloudformation
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Chris Roberts
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-07-08 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: chef
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: fog
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 1.12.1
|
38
|
+
type: :runtime
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 1.12.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: attribute_struct
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.1.2
|
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.1.2
|
62
|
+
description: Knife tooling for Cloud Formation
|
63
|
+
email: chrisroberts.code@gmail.com
|
64
|
+
executables: []
|
65
|
+
extensions: []
|
66
|
+
extra_rdoc_files: []
|
67
|
+
files:
|
68
|
+
- lib/chef/knife/cloudformation_events.rb
|
69
|
+
- lib/chef/knife/cloudformation_base.rb
|
70
|
+
- lib/chef/knife/cloudformation_destroy.rb
|
71
|
+
- lib/chef/knife/cloudformation_describe.rb
|
72
|
+
- lib/chef/knife/cloudformation_list.rb
|
73
|
+
- lib/chef/knife/cloudformation_create.rb
|
74
|
+
- lib/knife-cloudformation.rb
|
75
|
+
- lib/knife-cloudformation/version.rb
|
76
|
+
- lib/knife-cloudformation/sparkle_formation.rb
|
77
|
+
- README.md
|
78
|
+
- knife-cloudformation.gemspec
|
79
|
+
- CHANGELOG.md
|
80
|
+
homepage: http://github.com/heavywater/knife-cloudformation
|
81
|
+
licenses: []
|
82
|
+
post_install_message:
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
none: false
|
88
|
+
requirements:
|
89
|
+
- - ! '>='
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: '0'
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
none: false
|
94
|
+
requirements:
|
95
|
+
- - ! '>='
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 1.8.24
|
101
|
+
signing_key:
|
102
|
+
specification_version: 3
|
103
|
+
summary: Knife tooling for Cloud Formation
|
104
|
+
test_files: []
|
105
|
+
has_rdoc:
|