knife-cloudformation 0.1.22 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/README.md +56 -2
  4. data/knife-cloudformation.gemspec +4 -7
  5. data/lib/chef/knife/cloudformation_create.rb +105 -245
  6. data/lib/chef/knife/cloudformation_describe.rb +50 -26
  7. data/lib/chef/knife/cloudformation_destroy.rb +17 -18
  8. data/lib/chef/knife/cloudformation_events.rb +48 -14
  9. data/lib/chef/knife/cloudformation_export.rb +117 -34
  10. data/lib/chef/knife/cloudformation_import.rb +124 -18
  11. data/lib/chef/knife/cloudformation_inspect.rb +159 -71
  12. data/lib/chef/knife/cloudformation_list.rb +20 -24
  13. data/lib/chef/knife/cloudformation_promote.rb +40 -0
  14. data/lib/chef/knife/cloudformation_update.rb +132 -15
  15. data/lib/chef/knife/cloudformation_validate.rb +35 -0
  16. data/lib/knife-cloudformation.rb +28 -0
  17. data/lib/knife-cloudformation/cache.rb +213 -35
  18. data/lib/knife-cloudformation/knife.rb +9 -0
  19. data/lib/knife-cloudformation/knife/base.rb +179 -0
  20. data/lib/knife-cloudformation/knife/stack.rb +94 -0
  21. data/lib/knife-cloudformation/knife/template.rb +174 -0
  22. data/lib/knife-cloudformation/monkey_patch.rb +8 -0
  23. data/lib/knife-cloudformation/monkey_patch/stack.rb +195 -0
  24. data/lib/knife-cloudformation/provider.rb +225 -0
  25. data/lib/knife-cloudformation/utils.rb +18 -98
  26. data/lib/knife-cloudformation/utils/animal_strings.rb +28 -0
  27. data/lib/knife-cloudformation/utils/debug.rb +31 -0
  28. data/lib/knife-cloudformation/utils/json.rb +64 -0
  29. data/lib/knife-cloudformation/utils/object_storage.rb +28 -0
  30. data/lib/knife-cloudformation/utils/output.rb +79 -0
  31. data/lib/knife-cloudformation/utils/path_selector.rb +99 -0
  32. data/lib/knife-cloudformation/utils/ssher.rb +29 -0
  33. data/lib/knife-cloudformation/utils/stack_exporter.rb +271 -0
  34. data/lib/knife-cloudformation/utils/stack_parameter_scrubber.rb +35 -0
  35. data/lib/knife-cloudformation/utils/stack_parameter_validator.rb +124 -0
  36. data/lib/knife-cloudformation/version.rb +2 -4
  37. metadata +47 -94
  38. data/Gemfile +0 -3
  39. data/Gemfile.lock +0 -90
  40. data/knife-cloudformation-0.1.20.gem +0 -0
  41. data/lib/knife-cloudformation/aws_commons.rb +0 -267
  42. data/lib/knife-cloudformation/aws_commons/stack.rb +0 -435
  43. data/lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb +0 -79
  44. data/lib/knife-cloudformation/cloudformation_base.rb +0 -168
  45. data/lib/knife-cloudformation/export.rb +0 -174
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 5038817ca4a6fbe9658bbd9652b66281b02db80e
4
+ data.tar.gz: 4b4da29974e40d7077c56919fb83c7c66de27e6d
5
+ SHA512:
6
+ metadata.gz: e67e6e3bcd675159997cf4627f429a8c492b49d1e9a6ef22bf6820abd51ec74850de2b2997b675cd7de130bb01a6db5ff150578a088172f9b3fe3b16e4ccc423
7
+ data.tar.gz: 146d85f4a0db3e29a20335a9383d7bc5cea11d42c39a5a87c92dcad2d9b9959cc16dfeccb842438c0f691981b642f74d6d5def6582d3e09d24c7bcffdb4a3417
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## v0.2.0
2
+ * This release should be considered "breaking"
3
+ * Underlying cloud API has been changed from fog to miasma
4
+ * The `inspect` command has been fully reworked to support `--attribute`
5
+ * Lots and lots of other changes. See commit log.
6
+
1
7
  ## v0.1.22
2
8
  * Prevent full stack list loading in knife commands
3
9
  * Default logger to INFO level and allow DEBUG level via `ENV['DEBUG']`
data/README.md CHANGED
@@ -1,3 +1,57 @@
1
- ## Knife Cloud Formation
1
+ # Knife CloudFormation
2
2
 
3
- Cloud formation tooling for knife.
3
+ A plugin for the knife command provided by Chef to
4
+ interact with AWS CloudFormation.
5
+
6
+ ## Compatibility
7
+
8
+ This plugin now provides support for other cloud
9
+ orchestration APIs as well:
10
+
11
+ * OpenStack
12
+ * Rackspace
13
+
14
+ ## Configuration
15
+
16
+ The easiest way to configure the plugin is via the
17
+ `knife.rb` file. Credentials are the only configuration
18
+ requirement, and the `Hash` provided is proxied to
19
+ Miasma:
20
+
21
+ ```ruby
22
+ # .chef/knife.rb
23
+
24
+ knife[:cloudformation][:credentials] = {
25
+ :aws_access_key_id => ENV['AWS_ACCESS_KEY_ID'],
26
+ :aws_secret_access_key => ENV['AWS_SECRET_ACCESS_KEY'],
27
+ :aws_region => ENV['AWS_REGION']
28
+ }
29
+ ```
30
+
31
+ If are using Rackspace:
32
+
33
+ ```ruby
34
+ # .chef/knife.rb
35
+
36
+ knife[:cloudformation][:credentials] = {
37
+ :rackspace_username => ENV['RACKSPACE_USERNAME'],
38
+ :rackspace_api_key => ENV['RACKSPACE_API_KEY'],
39
+ :rackspace_region => ENV['RACKSPACE_REGION']
40
+ }
41
+ ```
42
+
43
+ ## Commands
44
+
45
+ * `knife cloudformation list`
46
+ * `knife cloudformation create`
47
+ * `knife cloudformation update`
48
+ * `knife cloudformation destroy`
49
+ * `knife cloudformation events`
50
+ * `knife cloudformation describe`
51
+ * `knife cloudformation inspect`
52
+ * `knife cloudformation validate`
53
+
54
+ # Info
55
+
56
+ * Repository: https://github.com/heavywater/knife-cloudformation
57
+ * IRC: Freenode @ #heavywater
@@ -10,11 +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.17'
14
- s.add_dependency 'unf'
15
- s.add_dependency 'net-sftp'
16
- s.add_dependency 'sparkle_formation', '~> 0.1.2'
17
- s.add_dependency 'redis-objects'
18
- s.add_dependency 'attribute_struct', '~> 0.1.8'
19
- s.files = Dir['**/*']
13
+ s.add_dependency 'miasma'
14
+ s.add_dependency 'net-ssh'
15
+ s.add_dependency 'sparkle_formation', '~> 0.2.0'
16
+ s.files = Dir['lib/**/*'] + %w(knife-cloudformation.gemspec README.md CHANGELOG.md)
20
17
  end
@@ -1,152 +1,78 @@
1
1
  require 'sparkle_formation'
2
2
  require 'pathname'
3
- require 'knife-cloudformation/cloudformation_base'
3
+ require 'knife-cloudformation'
4
4
 
5
5
  class Chef
6
6
  class Knife
7
+ # Cloudformation create command
7
8
  class CloudformationCreate < Knife
8
9
 
9
- include KnifeCloudformation::KnifeBase
10
+ include KnifeCloudformation::Knife::Base
11
+ include KnifeCloudformation::Knife::Template
12
+ include KnifeCloudformation::Knife::Stack
10
13
 
11
14
  banner 'knife cloudformation create NAME'
12
15
 
13
- module Options
14
- class << self
15
- def included(klass)
16
- klass.class_eval do
17
-
18
- attr_accessor :action_type
19
-
20
- option(:parameter,
21
- :short => '-p KEY:VALUE',
22
- :long => '--parameter KEY:VALUE',
23
- :description => 'Set parameter. Can be used multiple times.',
24
- :proc => lambda {|val|
25
- parts = val.split(':')
26
- key = parts.first
27
- value = parts[1, parts.size].join(':')
28
- Chef::Config[:knife][:cloudformation][:options][:parameters] ||= Mash.new
29
- Chef::Config[:knife][:cloudformation][:options][:parameters][key] = value
30
- }
31
- )
32
- option(:timeout,
33
- :short => '-t MIN',
34
- :long => '--timeout MIN',
35
- :description => 'Set timeout for stack creation',
36
- :proc => lambda {|val|
37
- Chef::Config[:knife][:cloudformation][:options][:timeout_in_minutes] = val
38
- }
39
- )
40
- option(:rollback,
41
- :short => '-R',
42
- :long => '--[no]-rollback',
43
- :description => 'Rollback on stack creation failure',
44
- :boolean => true,
45
- :default => true,
46
- :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:options][:disable_rollback] = !val }
47
- )
48
- option(:capability,
49
- :short => '-C CAPABILITY',
50
- :long => '--capability CAPABILITY',
51
- :description => 'Specify allowed capabilities. Can be used multiple times.',
52
- :proc => lambda {|val|
53
- Chef::Config[:knife][:cloudformation][:options][:capabilities] ||= []
54
- Chef::Config[:knife][:cloudformation][:options][:capabilities].push(val).uniq!
55
- }
56
- )
57
- option(:processing,
58
- :long => '--[no-]processing',
59
- :description => 'Call the unicorns and explode the glitter bombs',
60
- :boolean => true,
61
- :default => false,
62
- :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:processing] = val }
63
- )
64
- option(:polling,
65
- :long => '--[no-]poll',
66
- :description => 'Enable stack event polling.',
67
- :boolean => true,
68
- :default => true,
69
- :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:poll] = val }
70
- )
71
- option(:notifications,
72
- :long => '--notification ARN',
73
- :description => 'Add notification ARN. Can be used multiple times.',
74
- :proc => lambda {|val|
75
- Chef::Config[:knife][:cloudformation][:options][:notification_ARNs] ||= []
76
- Chef::Config[:knife][:cloudformation][:options][:notification_ARNs].push(val).uniq!
77
- }
78
- )
79
- option(:file,
80
- :short => '-f PATH',
81
- :long => '--file PATH',
82
- :description => 'Path to Cloud Formation to process',
83
- :proc => lambda {|val|
84
- Chef::Config[:knife][:cloudformation][:file] = val
85
- }
86
- )
87
- option(:interactive_parameters,
88
- :long => '--[no-]parameter-prompts',
89
- :boolean => true,
90
- :default => true,
91
- :description => 'Do not prompt for input on dynamic parameters',
92
- :default => true
93
- )
94
- option(:print_only,
95
- :long => '--print-only',
96
- :description => 'Print template and exit'
97
- )
98
- option(:base_directory,
99
- :long => '--cloudformation-directory PATH',
100
- :description => 'Path to cloudformation directory',
101
- :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:base_directory] = val}
102
- )
103
- option(:no_base_directory,
104
- :long => '--no-cloudformation-directory',
105
- :description => 'Unset any value used for cloudformation path',
106
- :proc => lambda {|*val| Chef::Config[:knife][:cloudformation][:base_directory] = nil}
107
- )
108
-
109
- %w(rollback polling interactive_parameters).each do |key|
110
- if(Chef::Config[:knife][:cloudformation][key].nil?)
111
- Chef::Config[:knife][:cloudformation][key] = true
112
- end
113
- end
114
- end
115
- end
116
- end
117
- end
118
-
119
- include Options
120
-
121
- def run
122
- @action_type = self.class.name.split('::').last.sub('Cloudformation', '').upcase
16
+ option(:timeout,
17
+ :short => '-t MIN',
18
+ :long => '--timeout MIN',
19
+ :description => 'Set timeout for stack creation',
20
+ :proc => lambda {|val|
21
+ Chef::Config[:knife][:cloudformation][:options][:timeout_in_minutes] = val
22
+ }
23
+ )
24
+ option(:rollback,
25
+ :short => '-R',
26
+ :long => '--[no]-rollback',
27
+ :description => 'Rollback on stack creation failure',
28
+ :boolean => true,
29
+ :default => true,
30
+ :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:options][:disable_rollback] = !val }
31
+ )
32
+ option(:capability,
33
+ :short => '-C CAPABILITY',
34
+ :long => '--capability CAPABILITY',
35
+ :description => 'Specify allowed capabilities. Can be used multiple times.',
36
+ :proc => lambda {|val|
37
+ Chef::Config[:knife][:cloudformation][:options][:capabilities] ||= []
38
+ Chef::Config[:knife][:cloudformation][:options][:capabilities].push(val).uniq!
39
+ }
40
+ )
41
+ option(:notifications,
42
+ :long => '--notification ARN',
43
+ :description => 'Add notification ARN. Can be used multiple times.',
44
+ :proc => lambda {|val|
45
+ Chef::Config[:knife][:cloudformation][:options][:notification_ARNs] ||= []
46
+ Chef::Config[:knife][:cloudformation][:options][:notification_ARNs].push(val).uniq!
47
+ }
48
+ )
49
+ option(:print_only,
50
+ :long => '--print-only',
51
+ :description => 'Print template and exit'
52
+ )
53
+ option(:apply_stacks,
54
+ :long => '--apply-stack NAME_OR_ID',
55
+ :description => 'Autofill parameters using existing stack outputs. Can be used multiple times',
56
+ :proc => lambda {|val|
57
+ Chef::Config[:knife][:cloudformation][:create] ||= Mash.new
58
+ Chef::Config[:knife][:cloudformation][:create][:apply_stacks] ||= []
59
+ Chef::Config[:knife][:cloudformation][:create][:apply_stacks].push(val).uniq!
60
+ }
61
+ )
62
+
63
+ # Run the stack creation command
64
+ def _run
123
65
  name = name_args.first
124
66
  unless(name)
125
67
  ui.fatal "Formation name must be specified!"
126
68
  exit 1
127
69
  end
128
-
129
- unless(Chef::Config[:knife][:cloudformation][:template])
130
- set_paths_and_discover_file!
131
- unless(File.exists?(Chef::Config[:knife][:cloudformation][:file].to_s))
132
- ui.fatal "Invalid formation file path provided: #{Chef::Config[:knife][:cloudformation][:file]}"
133
- exit 1
134
- end
135
- end
136
-
137
70
  if(Chef::Config[:knife][:cloudformation][:template])
138
71
  file = Chef::Config[:knife][:cloudformation][:template]
139
- elsif(Chef::Config[:knife][:cloudformation][:processing])
140
- file = SparkleFormation.compile(Chef::Config[:knife][:cloudformation][:file])
141
72
  else
142
- file = _from_json(File.read(Chef::Config[:knife][:cloudformation][:file]))
73
+ file = load_template_file
143
74
  end
144
- if(config[:print_only])
145
- ui.warn 'Print only requested'
146
- ui.info _format_json(file)
147
- exit 1
148
- end
149
- ui.info "#{ui.color('Cloud Formation: ', :bold)} #{ui.color(action_type, :green)}"
75
+ ui.info "#{ui.color('Cloud Formation:', :bold)} #{ui.color('create', :green)}"
150
76
  stack_info = "#{ui.color('Name:', :bold)} #{name}"
151
77
  if(Chef::Config[:knife][:cloudformation][:path])
152
78
  stack_info << " #{ui.color('Path:', :bold)} #{Chef::Config[:knife][:cloudformation][:file]}"
@@ -154,20 +80,46 @@ class Chef
154
80
  stack_info << " #{ui.color('(not pre-processed)', :yellow)}"
155
81
  end
156
82
  end
157
- ui.info " -> #{stack_info}"
158
- populate_parameters!(file)
159
- stack_def = KnifeCloudformation::AwsCommons::Stack.build_stack_definition(file, Chef::Config[:knife][:cloudformation][:options])
160
- aws.create_stack(name, stack_def)
161
- if(Chef::Config[:knife][:cloudformation][:polling])
162
- poll_stack(name)
163
- if(stack(name).success?)
164
- ui.info "Stack #{action_type} complete: #{ui.color('SUCCESS', :green)}"
83
+
84
+ unless(config[:print_only])
85
+ ui.info " -> #{stack_info}"
86
+ end
87
+
88
+ stack = provider.stacks.build(
89
+ Chef::Config[:knife][:cloudformation][:options].dup.merge(
90
+ :name => name,
91
+ :template => file
92
+ )
93
+ )
94
+
95
+ apply_stacks!(stack)
96
+ stack.template = KnifeCloudformation::Utils::StackParameterScrubber.scrub!(stack.template)
97
+
98
+ if(config[:print_only])
99
+ ui.info _format_json(translate_template(stack.template))
100
+ exit 0
101
+ end
102
+
103
+ populate_parameters!(stack.template)
104
+ stack.parameters = Chef::Config[:knife][:cloudformation][:options][:parameters]
105
+
106
+ stack.template = translate_template(stack.template)
107
+ stack.save
108
+
109
+ if(Chef::Config[:knife][:cloudformation][:poll])
110
+ provider.fetch_stacks
111
+ poll_stack(stack.name)
112
+ stack = provider.stacks.get(name)
113
+
114
+ if(stack.reload.success?)
115
+ ui.info "Stack create complete: #{ui.color('SUCCESS', :green)}"
116
+ provider.fetch_stacks
165
117
  knife_output = Chef::Knife::CloudformationDescribe.new
166
118
  knife_output.name_args.push(name)
167
119
  knife_output.config[:outputs] = true
168
120
  knife_output.run
169
121
  else
170
- ui.fatal "#{action_type} of new stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
122
+ ui.fatal "Create of new stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
171
123
  ui.info ""
172
124
  knife_inspect = Chef::Knife::CloudformationInspect.new
173
125
  knife_inspect.name_args.push(name)
@@ -181,115 +133,23 @@ class Chef
181
133
  end
182
134
  end
183
135
 
184
- def populate_parameters!(stack)
185
- if(Chef::Config[:knife][:cloudformation][:interactive_parameters])
186
- if(stack['Parameters'])
187
- Chef::Config[:knife][:cloudformation][:options][:parameters] ||= Mash.new
188
- stack['Parameters'].each do |k,v|
189
- next if Chef::Config[:knife][:cloudformation][:options][:parameters][k]
190
- valid = false
191
- until(valid)
192
- default = Chef::Config[:knife][:cloudformation][:options][:parameters][k] || v['Default']
193
- answer = ui.ask_question("#{k.split(/([A-Z]+[^A-Z]*)/).find_all{|s|!s.empty?}.join(' ')}: ", :default => default)
194
- validation = KnifeCloudformation::AwsCommons::Stack::ParameterValidator.validate(answer, v)
195
- if(validation == true)
196
- Chef::Config[:knife][:cloudformation][:options][:parameters][k] = answer
197
- valid = true
198
- else
199
- validation.each do |validation_error|
200
- ui.error validation_error.last
201
- end
202
- end
203
- end
204
- end
205
- end
206
- end
207
- end
208
-
209
- private
210
-
211
- def set_paths_and_discover_file!
212
- if(Chef::Config[:knife][:cloudformation][:base_directory])
213
- SparkleFormation.components_path = File.join(
214
- Chef::Config[:knife][:cloudformation][:base_directory], 'components'
215
- )
216
- SparkleFormation.dynamics_path = File.join(
217
- Chef::Config[:knife][:cloudformation][:base_directory], 'dynamics'
218
- )
219
- end
220
- unless(Chef::Config[:knife][:cloudformation][:file])
221
- Chef::Config[:knife][:cloudformation][:file] = prompt_for_file(
222
- Chef::Config[:knife][:cloudformation][:base_directory] || File.join(Dir.pwd, 'cloudformation')
223
- )
224
- else
225
- unless(Pathname(Chef::Config[:knife][:cloudformation][:file]).absolute?)
226
- Chef::Config[:knife][:cloudformation][:file] = File.join(
227
- Chef::Config[:knife][:cloudformation][:base_directory] || File.join(Dir.pwd, 'cloudformation'),
228
- Chef::Config[:knife][:cloudformation][:file]
229
- )
230
- end
231
- end
232
- end
233
-
234
- def prompt_for_file(dir)
235
- directory = Dir.new(dir)
236
- directories = directory.map do |d|
237
- if(!d.start_with?('.') && !%w(dynamics components).include?(d) && File.directory?(path = File.join(dir, d)))
238
- path
239
- end
240
- end.compact.sort
241
- files = directory.map do |f|
242
- if(!f.start_with?('.') && File.file?(path = File.join(dir, f)))
243
- path
244
- end
245
- end.compact.sort
246
- if(directories.empty? && files.empty?)
247
- ui.fatal 'No formation paths discoverable!'
248
- else
249
- output = ['Please select the formation to create']
250
- output << '(or directory to list):' unless directories.empty?
251
- ui.info output.join(' ')
252
- output.clear
253
- idx = 1
254
- valid = {}
255
- unless(directories.empty?)
256
- output << ui.color('Directories:', :bold)
257
- directories.each do |path|
258
- valid[idx] = {:path => path, :type => :directory}
259
- output << [idx, "#{File.basename(path).sub('.rb', '').split(/[-_]/).map(&:capitalize).join(' ')}"]
260
- idx += 1
261
- end
262
- end
263
- unless(files.empty?)
264
- output << ui.color('Templates:', :bold)
265
- files.each do |path|
266
- valid[idx] = {:path => path, :type => :file}
267
- output << [idx, "#{File.basename(path).sub('.rb', '').split(/[-_]/).map(&:capitalize).join(' ')}"]
268
- idx += 1
269
- end
270
- end
271
- max = idx.to_s.length
272
- output.map! do |o|
273
- if(o.is_a?(Array))
274
- " #{o.first}.#{' ' * (max - o.first.to_s.length)} #{o.last}"
275
- else
276
- o
277
- end
278
- end
279
- ui.info "#{output.join("\n")}\n"
280
- response = ask_question('Enter selection: ').to_i
281
- unless(valid[response])
282
- ui.fatal 'How about using a real value'
283
- exit 1
136
+ # Apply any defined remote stacks
137
+ #
138
+ # @param stack [Miasma::Models::Orchestration::Stack]
139
+ # @return [Miasma::Models::Orchestration::Stack]
140
+ def apply_stacks!(stack)
141
+ remote_stacks = Chef::Config[:knife][:cloudformation].
142
+ fetch(:create, {}).fetch(:apply_stacks, [])
143
+ remote_stacks.each do |stack_name|
144
+ remote_stack = provider.stacks.get(stack_name)
145
+ if(remote_stack)
146
+ stack.apply_stack(remote_stack)
284
147
  else
285
- entry = valid[response.to_i]
286
- if(entry[:type] == :directory)
287
- prompt_for_file(entry[:path])
288
- else
289
- Chef::Config[:knife][:cloudformation][:file] = entry[:path]
290
- end
148
+ ui.error "Failed to apply requested stack. Unable to locate. (#{stack_name})"
149
+ exit 1
291
150
  end
292
151
  end
152
+ stack
293
153
  end
294
154
 
295
155
  end