knife-cloudformation 0.0.1
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 +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:
|