knife-cloudformation 0.1.22 → 0.2.0

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