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
@@ -0,0 +1,9 @@
1
+ require 'knife-cloudformation'
2
+
3
+ module KnifeCloudformation
4
+ module Knife
5
+ autoload :Base, 'knife-cloudformation/knife/base'
6
+ autoload :Stack, 'knife-cloudformation/knife/stack'
7
+ autoload :Template, 'knife-cloudformation/knife/template'
8
+ end
9
+ end
@@ -0,0 +1,179 @@
1
+ require 'chef/knife'
2
+ require 'knife-cloudformation'
3
+
4
+ module KnifeCloudformation
5
+ module Knife
6
+ # Base to build cloudformation related knife commands
7
+ module Base
8
+
9
+ # Instance methods for cloudformation command classes
10
+ module InstanceMethods
11
+
12
+ # @return [KnifeCloudformation::Provider]
13
+ def provider
14
+ self.class.provider
15
+ end
16
+
17
+ # Write exception information if debug is enabled
18
+ #
19
+ # @param e [Exception]
20
+ # @param args [String] extra strings to output
21
+ def _debug(e, *args)
22
+ if(ENV['DEBUG'])
23
+ ui.fatal "Exception information: #{e.class}: #{e}\n#{e.backtrace.join("\n")}\n"
24
+ args.each do |string|
25
+ ui.fatal string
26
+ end
27
+ end
28
+ end
29
+
30
+ # Get stack
31
+ #
32
+ # @param name [String] name of stack
33
+ # @return [Miasma::Models::Orchestration::Stack]
34
+ def stack(name)
35
+ provider.stacks.get(name)
36
+ end
37
+
38
+ # @return [Array<String>] attributes to display
39
+ def allowed_attributes
40
+ Chef::Config[:knife][:cloudformation][:attributes] || default_attributes
41
+ end
42
+
43
+ # @return [Array<String>] default attributes to display
44
+ def default_attributes
45
+ %w(timestamp stack_name id)
46
+ end
47
+
48
+ # Check if attribute is allowed for display
49
+ #
50
+ # @param attr [String]
51
+ # @return [TrueClass, FalseClass]
52
+ def attribute_allowed?(attr)
53
+ config[:all_attributes] || allowed_attributes.include?(attr)
54
+ end
55
+
56
+ # Poll events on stack
57
+ #
58
+ # @param name [String] name of stack
59
+ def poll_stack(name)
60
+ knife_events = Chef::Knife::CloudformationEvents.new
61
+ knife_events.name_args.push(name)
62
+ Chef::Config[:knife][:cloudformation][:poll] = true
63
+ knife_events.run
64
+ end
65
+
66
+ # Wrapper for information retrieval. Provides consistent error
67
+ # message for failures
68
+ #
69
+ # @param stack [String] stack name
70
+ # @param message [String] failure message
71
+ # @yield block to wrap error handling
72
+ # @return [Object] result of yield
73
+ def get_things(stack=nil, message=nil)
74
+ begin
75
+ yield
76
+ rescue => e
77
+ ui.fatal "#{message || 'Failed to retrieve information'}#{" for requested stack: #{stack}" if stack}"
78
+ ui.fatal "Reason: #{e}"
79
+ _debug(e)
80
+ exit 1
81
+ end
82
+ end
83
+
84
+ # Disable chef configuration. Let the dep loader do that for us
85
+ # so it doesn't squash config values set via options
86
+ def configure_chef
87
+ true
88
+ end
89
+
90
+ # Wrapper to allow consistent exception handling
91
+ def run
92
+ begin
93
+ _run
94
+ rescue => e
95
+ ui.fatal "Unexpected Error: #{e}"
96
+ _debug(e)
97
+ exit 1
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+ module ClassMethods
104
+
105
+ # @return [KnifeCloudformation::Provider]
106
+ def provider
107
+ Thread.current[:_provider] ||= KnifeCloudformation::Provider.new(
108
+ :miasma => Chef::Config[:knife][:cloudformation][:credentials],
109
+ :async => false,
110
+ :fetch => false
111
+ )
112
+ end
113
+
114
+ # @return [FalseClass]
115
+ def use_separate_defaults?
116
+ false
117
+ end
118
+
119
+ end
120
+
121
+ class << self
122
+ def included(klass)
123
+ klass.instance_eval do
124
+
125
+ extend KnifeCloudformation::Knife::Base::ClassMethods
126
+ include KnifeCloudformation::Knife::Base::InstanceMethods
127
+ include KnifeCloudformation::Utils::JSON
128
+ include KnifeCloudformation::Utils::AnimalStrings
129
+ include KnifeCloudformation::Utils::Output
130
+
131
+ deps do
132
+ Chef::Knife.new.configure_chef
133
+ require 'miasma'
134
+ Chef::Config[:knife][:cloudformation] ||= Mash.new
135
+ Chef::Config[:knife][:cloudformation][:credentials] ||= Mash.new
136
+ Chef::Config[:knife][:cloudformation][:options] ||= Mash.new
137
+ Chef::Config[:knife][:cloudformation][:ignore_parameters] = []
138
+ %w(poll interactive_parameters).each do |key|
139
+ if(Chef::Config[:knife][:cloudformation][key].nil?)
140
+ Chef::Config[:knife][:cloudformation][key] = true
141
+ end
142
+ end
143
+ end
144
+
145
+ option(:credentials,
146
+ :short => '-S CREDENTIALS',
147
+ :long => '--credentials CREDENTIALS',
148
+ :description => 'Miasma API options. Comma delimited or used multiple times. (-S "aws_access_key_id=MYKEY")',
149
+ :proc => lambda {|val|
150
+ val.split(',').each do |pair|
151
+ key, value = pair.split('=')
152
+ Chef::Config[:knife][:cloudformation][:credentials][key] = value
153
+ end
154
+ }
155
+ )
156
+
157
+ option(:ignore_parameter,
158
+ :long => '--ignore-parameter PARAMETER_NAME',
159
+ :description => 'Parameter to ignore during modifications (can be used multiple times)',
160
+ :proc => lambda{|val| Chef::Config[:knife][:cloudformation][:ignore_parameters].push(val).uniq! }
161
+ )
162
+
163
+ # Populate up the hashes so they are available for knife config
164
+ # with issues of nils
165
+ ['knife.cloudformation.credentials', 'knife.cloudformation.options'].each do |stack|
166
+ stack.split('.').inject(Chef::Config) do |memo, item|
167
+ memo[item.to_sym] = Mash.new unless memo[item.to_sym]
168
+ memo[item.to_sym]
169
+ end
170
+ end
171
+
172
+ end
173
+
174
+ end
175
+ end
176
+ end
177
+
178
+ end
179
+ end
@@ -0,0 +1,94 @@
1
+ require 'knife-cloudformation'
2
+ require 'sparkle_formation'
3
+
4
+ module KnifeCloudformation
5
+ module Knife
6
+ # Stack handling helper methods
7
+ module Stack
8
+
9
+ module InstanceMethods
10
+
11
+ # maximum number of attempts to get valid parameter value
12
+ MAX_PARAMETER_ATTEMPTS = 5
13
+
14
+ # Prompt for parameter values and store result
15
+ #
16
+ # @param stack [Hash] stack template
17
+ # @return [Hash]
18
+ def populate_parameters!(stack)
19
+ if(Chef::Config[:knife][:cloudformation][:interactive_parameters])
20
+ if(stack['Parameters'] || stack['parameters'])
21
+ Chef::Config[:knife][:cloudformation][:options][:parameters] ||= Mash.new
22
+ stack.fetch('Parameters', stack.fetch('parameters', {})).each do |k,v|
23
+ next if Chef::Config[:knife][:cloudformation][:options][:parameters][k]
24
+ attempt = 0
25
+ valid = false
26
+ until(valid)
27
+ attempt += 1
28
+ default = Chef::Config[:knife][:cloudformation][:options][:parameters][k] || v['Default'] || v['default']
29
+ answer = ui.ask_question("#{k.split(/([A-Z]+[^A-Z]*)/).find_all{|s|!s.empty?}.join(' ')}: ", :default => default)
30
+ validation = KnifeCloudformation::Utils::StackParameterValidator.validate(answer, v)
31
+ if(validation == true)
32
+ Chef::Config[:knife][:cloudformation][:options][:parameters][k] = answer
33
+ valid = true
34
+ else
35
+ validation.each do |validation_error|
36
+ ui.error validation_error.last
37
+ end
38
+ end
39
+ if(attempt > MAX_PARAMETER_ATTEMPTS)
40
+ ui.fatal 'Failed to receive allowed parameter!'
41
+ exit 1
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
47
+ stack
48
+ end
49
+
50
+ end
51
+
52
+ module ClassMethods
53
+ end
54
+
55
+ # Load methods into class and define options
56
+ #
57
+ # @param klass [Class]
58
+ def self.included(klass)
59
+ klass.class_eval do
60
+ extend KnifeCloudformation::Knife::Stack::ClassMethods
61
+ include KnifeCloudformation::Knife::Stack::InstanceMethods
62
+
63
+ option(:parameter,
64
+ :short => '-p KEY:VALUE',
65
+ :long => '--parameter KEY:VALUE',
66
+ :description => 'Set parameter. Can be used multiple times.',
67
+ :proc => lambda {|val|
68
+ parts = val.split(':')
69
+ key = parts.first
70
+ value = parts[1, parts.size].join(':')
71
+ Chef::Config[:knife][:cloudformation][:options][:parameters] ||= Mash.new
72
+ Chef::Config[:knife][:cloudformation][:options][:parameters][key] = value
73
+ }
74
+ )
75
+ option(:polling,
76
+ :long => '--[no-]poll',
77
+ :description => 'Enable stack event polling.',
78
+ :boolean => true,
79
+ :default => true,
80
+ :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:poll] = val }
81
+ )
82
+ option(:interactive_parameters,
83
+ :long => '--[no-]parameter-prompts',
84
+ :boolean => true,
85
+ :default => true,
86
+ :description => 'Do not prompt for input on dynamic parameters',
87
+ :proc => lambda{|val| Chef::Config[:knife][:cloudformation][:interactive_parameters] = val }
88
+ )
89
+ end
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,174 @@
1
+ require 'knife-cloudformation'
2
+ require 'sparkle_formation'
3
+
4
+ module KnifeCloudformation
5
+ module Knife
6
+ # Template handling helper methods
7
+ module Template
8
+
9
+ # cloudformation directories that should be ignored
10
+ TEMPLATE_IGNORE_DIRECTORIES = %w(components dynamics registry)
11
+
12
+ module InstanceMethods
13
+
14
+ # Load the template file
15
+ #
16
+ # @param args [Symbol] options (:allow_missing)
17
+ # @return [Hash] loaded template
18
+ def load_template_file(*args)
19
+ unless(Chef::Config[:knife][:cloudformation][:template])
20
+ set_paths_and_discover_file!
21
+ unless(File.exists?(Chef::Config[:knife][:cloudformation][:file].to_s))
22
+ unless(args.include?(:allow_missing))
23
+ ui.fatal "Invalid formation file path provided: #{Chef::Config[:knife][:cloudformation][:file]}"
24
+ exit 1
25
+ end
26
+ end
27
+ end
28
+ if(Chef::Config[:knife][:cloudformation][:template])
29
+ Chef::Config[:knife][:cloudformation][:template]
30
+ elsif(Chef::Config[:knife][:cloudformation][:file])
31
+ if(Chef::Config[:knife][:cloudformation][:processing])
32
+ SparkleFormation.compile(Chef::Config[:knife][:cloudformation][:file])
33
+ else
34
+ _from_json(File.read(Chef::Config[:knife][:cloudformation][:file]))
35
+ end
36
+ end
37
+ end
38
+
39
+ # Apply template translation
40
+ #
41
+ # @param template [Hash]
42
+ # @return [Hash]
43
+ def translate_template(template)
44
+ if(klass_name = Chef::Config[:knife][:cloudformation][:translate])
45
+ klass = SparkleFormation::Translation.const_get(camel(klass_name))
46
+ args = {
47
+ :parameters => Chef::Config[:knife][:cloudformation][:options][:parameters]
48
+ }
49
+ if(chunk_size = Chef::Config[:knife][:cloudformation][:translate_chunk_size])
50
+ args.merge!(
51
+ :options => {
52
+ :serialization_chunk_size => chunk_size
53
+ }
54
+ )
55
+ end
56
+ translator = klass.new(template, args)
57
+ translator.translate!
58
+ template = translator.translated
59
+ ui.info "#{ui.color('Translation applied:', :bold)} #{ui.color(klass_name, :yellow)}"
60
+ end
61
+ template
62
+ end
63
+
64
+ # Set SparkleFormation paths and locate tempate
65
+ #
66
+ # @return [TrueClass]
67
+ def set_paths_and_discover_file!
68
+ if(Chef::Config[:knife][:cloudformation][:base_directory])
69
+ SparkleFormation.components_path = File.join(
70
+ Chef::Config[:knife][:cloudformation][:base_directory], 'components'
71
+ )
72
+ SparkleFormation.dynamics_path = File.join(
73
+ Chef::Config[:knife][:cloudformation][:base_directory], 'dynamics'
74
+ )
75
+ end
76
+ if(!Chef::Config[:knife][:cloudformation][:file] && Chef::Config[:knife][:cloudformation][:file_path_prompt])
77
+ root = File.expand_path(
78
+ Chef::Config[:knife][:cloudformation].fetch(:base_directory,
79
+ File.join(Dir.pwd, 'cloudformation')
80
+ )
81
+ ).split('/')
82
+ bucket = root.pop
83
+ root = root.join('/')
84
+ directory = File.join(root, bucket)
85
+ Chef::Config[:knife][:cloudformation][:file] = prompt_for_file(directory,
86
+ :directories_name => 'Collections',
87
+ :files_name => 'Templates',
88
+ :ignore_directories => TEMPLATE_IGNORE_DIRECTORIES
89
+ )
90
+ else
91
+ unless(Pathname(Chef::Config[:knife][:cloudformation][:file].to_s).absolute?)
92
+ base_dir = Chef::Config[:knife][:cloudformation][:base_directory].to_s
93
+ file = Chef::Config[:knife][:cloudformation][:file].to_s
94
+ pwd = Dir.pwd
95
+ Chef::Config[:knife][:cloudformation][:file] = [
96
+ File.join(base_dir, file),
97
+ File.join(pwd, file),
98
+ File.join(pwd, 'cloudformation', file)
99
+ ].detect do |file_path|
100
+ File.file?(file_path)
101
+ end
102
+ end
103
+ end
104
+ true
105
+ end
106
+
107
+ end
108
+
109
+ module ClassMethods
110
+ end
111
+
112
+ # Load methods into class and define options
113
+ #
114
+ # @param klass [Class]
115
+ def self.included(klass)
116
+ klass.class_eval do
117
+ extend KnifeCloudformation::Knife::Template::ClassMethods
118
+ include KnifeCloudformation::Knife::Template::InstanceMethods
119
+ include KnifeCloudformation::Utils::PathSelector
120
+
121
+ option(:processing,
122
+ :long => '--[no-]processing',
123
+ :description => 'Call the unicorns and explode the glitter bombs',
124
+ :boolean => true,
125
+ :default => false,
126
+ :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:processing] = val }
127
+ )
128
+ option(:file,
129
+ :short => '-f PATH',
130
+ :long => '--file PATH',
131
+ :description => 'Path to Cloud Formation to process',
132
+ :proc => lambda {|val|
133
+ Chef::Config[:knife][:cloudformation][:file] = val
134
+ }
135
+ )
136
+ option(:file_path_prompt,
137
+ :long => '--[no-]file-path-prompt',
138
+ :description => 'Interactive prompt for template path discovery',
139
+ :boolean => true,
140
+ :default => true,
141
+ :proc => lambda {|val|
142
+ Chef::Config[:knife][:cloudformation][:file_path_prompt] = val
143
+ }
144
+ )
145
+ option(:base_directory,
146
+ :long => '--cloudformation-directory PATH',
147
+ :description => 'Path to cloudformation directory',
148
+ :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:base_directory] = val}
149
+ )
150
+ option(:no_base_directory,
151
+ :long => '--no-cloudformation-directory',
152
+ :description => 'Unset any value used for cloudformation path',
153
+ :proc => lambda {|*val| Chef::Config[:knife][:cloudformation][:base_directory] = nil}
154
+ )
155
+ option(:translate,
156
+ :long => '--translate PROVIDER',
157
+ :description => 'Translate generated template to given provider',
158
+ :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:translate] = val}
159
+ )
160
+ option(:translate_chunk,
161
+ :long => '--translate-chunk-size SIZE',
162
+ :description => 'Chunk length for serialization',
163
+ :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:translate_chunk_size] = val}
164
+ )
165
+
166
+ Chef::Config[:knife][:cloudformation][:file_path_prompt] = true
167
+
168
+ end
169
+
170
+ end
171
+
172
+ end
173
+ end
174
+ end