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