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