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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/README.md +56 -2
- data/knife-cloudformation.gemspec +4 -7
- data/lib/chef/knife/cloudformation_create.rb +105 -245
- data/lib/chef/knife/cloudformation_describe.rb +50 -26
- data/lib/chef/knife/cloudformation_destroy.rb +17 -18
- data/lib/chef/knife/cloudformation_events.rb +48 -14
- data/lib/chef/knife/cloudformation_export.rb +117 -34
- data/lib/chef/knife/cloudformation_import.rb +124 -18
- data/lib/chef/knife/cloudformation_inspect.rb +159 -71
- data/lib/chef/knife/cloudformation_list.rb +20 -24
- data/lib/chef/knife/cloudformation_promote.rb +40 -0
- data/lib/chef/knife/cloudformation_update.rb +132 -15
- data/lib/chef/knife/cloudformation_validate.rb +35 -0
- data/lib/knife-cloudformation.rb +28 -0
- data/lib/knife-cloudformation/cache.rb +213 -35
- data/lib/knife-cloudformation/knife.rb +9 -0
- data/lib/knife-cloudformation/knife/base.rb +179 -0
- data/lib/knife-cloudformation/knife/stack.rb +94 -0
- data/lib/knife-cloudformation/knife/template.rb +174 -0
- data/lib/knife-cloudformation/monkey_patch.rb +8 -0
- data/lib/knife-cloudformation/monkey_patch/stack.rb +195 -0
- data/lib/knife-cloudformation/provider.rb +225 -0
- data/lib/knife-cloudformation/utils.rb +18 -98
- data/lib/knife-cloudformation/utils/animal_strings.rb +28 -0
- data/lib/knife-cloudformation/utils/debug.rb +31 -0
- data/lib/knife-cloudformation/utils/json.rb +64 -0
- data/lib/knife-cloudformation/utils/object_storage.rb +28 -0
- data/lib/knife-cloudformation/utils/output.rb +79 -0
- data/lib/knife-cloudformation/utils/path_selector.rb +99 -0
- data/lib/knife-cloudformation/utils/ssher.rb +29 -0
- data/lib/knife-cloudformation/utils/stack_exporter.rb +271 -0
- data/lib/knife-cloudformation/utils/stack_parameter_scrubber.rb +35 -0
- data/lib/knife-cloudformation/utils/stack_parameter_validator.rb +124 -0
- data/lib/knife-cloudformation/version.rb +2 -4
- metadata +47 -94
- data/Gemfile +0 -3
- data/Gemfile.lock +0 -90
- data/knife-cloudformation-0.1.20.gem +0 -0
- data/lib/knife-cloudformation/aws_commons.rb +0 -267
- data/lib/knife-cloudformation/aws_commons/stack.rb +0 -435
- data/lib/knife-cloudformation/aws_commons/stack_parameter_validator.rb +0 -79
- data/lib/knife-cloudformation/cloudformation_base.rb +0 -168
- data/lib/knife-cloudformation/export.rb +0 -174
@@ -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
|