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,28 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
# Helper methods for string format modification
|
7
|
+
module AnimalStrings
|
8
|
+
|
9
|
+
# Camel case string
|
10
|
+
#
|
11
|
+
# @param string [String]
|
12
|
+
# @return [String]
|
13
|
+
def camel(string)
|
14
|
+
string.to_s.split('_').map{|k| "#{k.slice(0,1).upcase}#{k.slice(1,k.length)}"}.join
|
15
|
+
end
|
16
|
+
|
17
|
+
# Snake case string
|
18
|
+
#
|
19
|
+
# @param string [String]
|
20
|
+
# @return [Symbol]
|
21
|
+
def snake(string)
|
22
|
+
string.to_s.gsub(/([a-z])([A-Z])/, '\1_\2').downcase.to_sym
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
module Utils
|
5
|
+
# Debug helpers
|
6
|
+
module Debug
|
7
|
+
# Output helpers
|
8
|
+
module Output
|
9
|
+
# Write debug message
|
10
|
+
#
|
11
|
+
# @param msg [String]
|
12
|
+
def debug(msg)
|
13
|
+
puts "<KnifeCloudformation>: #{msg}" if ENV['DEBUG']
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class << self
|
18
|
+
# Load module into class
|
19
|
+
#
|
20
|
+
# @param klass [Class]
|
21
|
+
def included(klass)
|
22
|
+
klass.class_eval do
|
23
|
+
include Output
|
24
|
+
extend Output
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
# JSON helper methods
|
7
|
+
module JSON
|
8
|
+
|
9
|
+
# Attempt to load chef JSON compat helper
|
10
|
+
#
|
11
|
+
# @return [TrueClass, FalseClass] chef compat helper available
|
12
|
+
def try_json_compat
|
13
|
+
unless(@_json_loaded)
|
14
|
+
begin
|
15
|
+
require 'chef/json_compat'
|
16
|
+
rescue
|
17
|
+
require "#{ENV['RUBY_JSON_LIB'] || 'json'}"
|
18
|
+
end
|
19
|
+
@_json_loaded = true
|
20
|
+
end
|
21
|
+
defined?(Chef::JSONCompat)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Convert to JSON
|
25
|
+
#
|
26
|
+
# @param thing [Object]
|
27
|
+
# @return [String]
|
28
|
+
def _to_json(thing)
|
29
|
+
if(try_json_compat)
|
30
|
+
Chef::JSONCompat.to_json(thing)
|
31
|
+
else
|
32
|
+
JSON.dump(thing)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Load JSON data
|
37
|
+
#
|
38
|
+
# @param thing [String]
|
39
|
+
# @return [Object]
|
40
|
+
def _from_json(thing)
|
41
|
+
if(try_json_compat)
|
42
|
+
Chef::JSONCompat.from_json(thing)
|
43
|
+
else
|
44
|
+
JSON.read(thing)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Format object into pretty JSON
|
49
|
+
#
|
50
|
+
# @param thing [Object]
|
51
|
+
# @return [String]
|
52
|
+
def _format_json(thing)
|
53
|
+
thing = _from_json(thing) if thing.is_a?(String)
|
54
|
+
if(try_json_compat)
|
55
|
+
Chef::JSONCompat.to_json_pretty(thing)
|
56
|
+
else
|
57
|
+
JSON.pretty_generate(thing)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
# Storage helpers
|
7
|
+
module ObjectStorage
|
8
|
+
|
9
|
+
# Write to file
|
10
|
+
#
|
11
|
+
# @param object [Object]
|
12
|
+
# @param path [String] path to write object
|
13
|
+
# @param directory [Miasma::Models::Storage::Directory]
|
14
|
+
# @return [String] file path
|
15
|
+
def file_store(object, path, directory)
|
16
|
+
raise NotImplementedError.new 'Internal updated required! :('
|
17
|
+
content = object.is_a?(String) ? object : Utils._format_json(object)
|
18
|
+
directory.files.create(
|
19
|
+
:identity => path,
|
20
|
+
:body => content
|
21
|
+
)
|
22
|
+
loc = directory.service.service.name.split('::').last.downcase
|
23
|
+
"#{loc}://#{directory.identity}/#{path}"
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
module Utils
|
5
|
+
# Output Helpers
|
6
|
+
module Output
|
7
|
+
|
8
|
+
# Process things and return items
|
9
|
+
#
|
10
|
+
# @param things [Array] items to process
|
11
|
+
# @param args [Hash] options
|
12
|
+
# @option args [TrueClass, FalseClass] :flat flatten result array
|
13
|
+
# @option args [Array] :attributes attributes to extract
|
14
|
+
# @todo this was extracted from events and needs to be cleaned up
|
15
|
+
def process(things, args={})
|
16
|
+
@event_ids ||= []
|
17
|
+
processed = things.reverse.map do |thing|
|
18
|
+
next if @event_ids.include?(thing['id'])
|
19
|
+
@event_ids.push(thing['id']).compact!
|
20
|
+
if(args[:attributes])
|
21
|
+
args[:attributes].map do |key|
|
22
|
+
thing[key].to_s
|
23
|
+
end
|
24
|
+
else
|
25
|
+
thing.values
|
26
|
+
end
|
27
|
+
end
|
28
|
+
args[:flat] ? processed.flatten : processed
|
29
|
+
end
|
30
|
+
|
31
|
+
# Generate formatted titles
|
32
|
+
#
|
33
|
+
# @param thing [Object] thing being processed
|
34
|
+
# @param args [Hash]
|
35
|
+
# @option args [Array] :attributes
|
36
|
+
# @return [Array<String>] formatted titles
|
37
|
+
def get_titles(thing, args={})
|
38
|
+
attrs = args[:attributes] || []
|
39
|
+
if(attrs.empty?)
|
40
|
+
hash = thing.is_a?(Array) ? thing.first : thing
|
41
|
+
hash ||= {}
|
42
|
+
attrs = hash.keys
|
43
|
+
end
|
44
|
+
titles = attrs.map do |key|
|
45
|
+
camel(key).gsub(/([a-z])([A-Z])/, '\1 \2')
|
46
|
+
end.compact
|
47
|
+
if(args[:format])
|
48
|
+
titles.map{|s| @ui.color(s, :bold)}
|
49
|
+
else
|
50
|
+
titles
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Output stack related things in nice format
|
55
|
+
#
|
56
|
+
# @param stack [String] name of stack
|
57
|
+
# @param things [Array] things to display
|
58
|
+
# @param what [String] description of things for output
|
59
|
+
# @param args [Symbol] options (:ignore_empty_output)
|
60
|
+
def things_output(stack, things, what, *args)
|
61
|
+
unless(args.include?(:no_title))
|
62
|
+
output = get_titles(things, :format => true, :attributes => allowed_attributes)
|
63
|
+
else
|
64
|
+
output = []
|
65
|
+
end
|
66
|
+
columns = allowed_attributes.size
|
67
|
+
output += process(things, :flat => true, :attributes => allowed_attributes)
|
68
|
+
output.compact!
|
69
|
+
if(output.empty?)
|
70
|
+
ui.warn 'No information found' unless args.include?(:ignore_empty_output)
|
71
|
+
else
|
72
|
+
ui.info "#{what.to_s.capitalize} for stack: #{ui.color(stack, :bold)}" if stack
|
73
|
+
ui.info "#{ui.list(output, :uneven_columns_across, columns)}"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
# Helper methods for path selection
|
7
|
+
module PathSelector
|
8
|
+
|
9
|
+
# Humanize the base name of path
|
10
|
+
#
|
11
|
+
# @param path [String]
|
12
|
+
# @return [String]
|
13
|
+
def humanize_path_basename(path)
|
14
|
+
File.basename(path).sub(
|
15
|
+
File.extname(path), ''
|
16
|
+
).split(/[-_]/).map(&:capitalize).join(' ')
|
17
|
+
end
|
18
|
+
|
19
|
+
# Prompt user for file selection
|
20
|
+
#
|
21
|
+
# @param directory [String] path to directory
|
22
|
+
# @param opts [Hash] options
|
23
|
+
# @option opts [Array<String>] :ignore_directories directory names
|
24
|
+
# @option opts [String] :directories_name title for directories
|
25
|
+
# @option opts [String] :files_name title for files
|
26
|
+
# @option opts [String] :filter_prefix only return results matching filter
|
27
|
+
# @return [String] file path
|
28
|
+
def prompt_for_file(directory, opts={})
|
29
|
+
file_list = Dir.glob(File.join(directory, '**', '**', '*')).find_all do |file|
|
30
|
+
File.file?(file)
|
31
|
+
end
|
32
|
+
if(opts[:filter_prefix])
|
33
|
+
file_list = file_list.find_all do |file|
|
34
|
+
file.start_with?(options[:filter_prefix])
|
35
|
+
end
|
36
|
+
end
|
37
|
+
directories = file_list.map do |file|
|
38
|
+
File.dirname(file)
|
39
|
+
end.uniq
|
40
|
+
files = file_list.find_all do |path|
|
41
|
+
path.sub(directory, '').split('/').size == 2
|
42
|
+
end
|
43
|
+
if(opts[:ignore_directories])
|
44
|
+
directories.delete_if do |dir|
|
45
|
+
opts[:ignore_directories].include?(File.basename(dir))
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if(directories.empty? && files.empty?)
|
49
|
+
ui.fatal 'No formation paths discoverable!'
|
50
|
+
else
|
51
|
+
output = ['Please select an entry']
|
52
|
+
output << '(or directory to list):' unless directories.empty?
|
53
|
+
ui.info output.join(' ')
|
54
|
+
output.clear
|
55
|
+
idx = 1
|
56
|
+
valid = {}
|
57
|
+
unless(directories.empty?)
|
58
|
+
output << ui.color("#{opts.fetch(:directories_name, 'Directories')}:", :bold)
|
59
|
+
directories.each do |dir|
|
60
|
+
valid[idx] = {:path => dir, :type => :directory}
|
61
|
+
output << [idx, humanize_path_basename(dir)]
|
62
|
+
idx += 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
unless(files.empty?)
|
66
|
+
output << ui.color("#{opts.fetch(:files_name, 'Files')}:", :bold)
|
67
|
+
files.each do |file|
|
68
|
+
valid[idx] = {:path => file, :type => :file}
|
69
|
+
output << [idx, humanize_path_basename(file)]
|
70
|
+
idx += 1
|
71
|
+
end
|
72
|
+
end
|
73
|
+
max = idx.to_s.length
|
74
|
+
output.map! do |o|
|
75
|
+
if(o.is_a?(Array))
|
76
|
+
" #{o.first}.#{' ' * (max - o.first.to_s.length)} #{o.last}"
|
77
|
+
else
|
78
|
+
o
|
79
|
+
end
|
80
|
+
end
|
81
|
+
ui.info "#{output.join("\n")}\n"
|
82
|
+
response = ask_question('Enter selection: ').to_i
|
83
|
+
unless(valid[response])
|
84
|
+
ui.fatal 'How about using a real value'
|
85
|
+
exit 1
|
86
|
+
else
|
87
|
+
entry = valid[response.to_i]
|
88
|
+
if(entry[:type] == :directory)
|
89
|
+
prompt_for_file(entry[:path], opts)
|
90
|
+
else
|
91
|
+
"/#{entry[:path]}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'knife-cloudformation'
|
2
|
+
|
3
|
+
module KnifeCloudformation
|
4
|
+
module Utils
|
5
|
+
|
6
|
+
# Helper methods for SSH interactions
|
7
|
+
module Ssher
|
8
|
+
|
9
|
+
# Retrieve file from remote node
|
10
|
+
#
|
11
|
+
# @param address [String]
|
12
|
+
# @param user [String]
|
13
|
+
# @param path [String] remote file path
|
14
|
+
# @param ssh_opts [Hash]
|
15
|
+
# @return [String, NilClass]
|
16
|
+
def remote_file_contents(address, user, path, ssh_opts={})
|
17
|
+
if(path.to_s.strip.empty?)
|
18
|
+
raise ArgumentError.new 'No file path provided!'
|
19
|
+
end
|
20
|
+
require 'net/ssh'
|
21
|
+
content = ''
|
22
|
+
ssh_session = Net::SSH.start(address, user, ssh_opts)
|
23
|
+
content = ssh_session.exec!("sudo cat #{path}")
|
24
|
+
content.empty? ? nil : content
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,271 @@
|
|
1
|
+
require 'chef'
|
2
|
+
require 'knife-cloudformation'
|
3
|
+
|
4
|
+
module KnifeCloudformation
|
5
|
+
module Utils
|
6
|
+
|
7
|
+
# Stack serialization helper
|
8
|
+
class StackExporter
|
9
|
+
|
10
|
+
include KnifeCloudformation::Utils::AnimalStrings
|
11
|
+
include KnifeCloudformation::Utils::JSON
|
12
|
+
|
13
|
+
# default chef environment name
|
14
|
+
DEFAULT_CHEF_ENVIRONMENT = '_default'
|
15
|
+
# default instance options
|
16
|
+
DEFAULT_OPTIONS = Mash.new(
|
17
|
+
:chef_popsicle => true,
|
18
|
+
:ignored_parameters => ['Environment', 'StackCreator', 'Creator'],
|
19
|
+
:chef_environment_parameter => 'Environment'
|
20
|
+
)
|
21
|
+
# default structure of export payload
|
22
|
+
DEFAULT_EXPORT_STRUCTURE = {
|
23
|
+
:stack => Mash.new(
|
24
|
+
:template => nil,
|
25
|
+
:options => {
|
26
|
+
:parameters => Mash.new,
|
27
|
+
:capabilities => [],
|
28
|
+
:notification_topics => []
|
29
|
+
}
|
30
|
+
),
|
31
|
+
:generator => {
|
32
|
+
:timestamp => Time.now.to_i,
|
33
|
+
:name => 'knife-cloudformation',
|
34
|
+
:version => KnifeCloudformation::VERSION.version,
|
35
|
+
:provider => nil
|
36
|
+
}
|
37
|
+
}
|
38
|
+
|
39
|
+
# @return [Miasma::Models::Orchestration::Stack]
|
40
|
+
attr_reader :stack
|
41
|
+
# @return [Hash]
|
42
|
+
attr_reader :options
|
43
|
+
# @return [Hash]
|
44
|
+
attr_reader :stack_export
|
45
|
+
|
46
|
+
# Create new instance
|
47
|
+
#
|
48
|
+
# @param stack [Miasma::Models::Orchestration::Stack]
|
49
|
+
# @param options [Hash]
|
50
|
+
# @option options [KnifeCloudformation::Provider] :provider
|
51
|
+
# @option options [TrueClass, FalseClass] :chef_popsicle freeze run list
|
52
|
+
# @option options [Array<String>] :ignored_parameters
|
53
|
+
# @option options [String] :chef_environment_parameter
|
54
|
+
def initialize(stack, options={})
|
55
|
+
@stack = stack
|
56
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
57
|
+
@stack_export = Mash.new
|
58
|
+
end
|
59
|
+
|
60
|
+
# Export stack
|
61
|
+
#
|
62
|
+
# @return [Hash] exported stack
|
63
|
+
def export
|
64
|
+
@stack_export = Mash.new(DEFAULT_EXPORT_STRUCTURE).tap do |stack_export|
|
65
|
+
[:parameters, :capabilities, :notification_topics].each do |key|
|
66
|
+
if(val = stack.send(key))
|
67
|
+
stack_export[:stack][key] = val
|
68
|
+
end
|
69
|
+
end
|
70
|
+
stack_export[:stack][:template] = stack.template
|
71
|
+
stack_export[:generator][:timestamp] = Time.now.to_i
|
72
|
+
stack_export[:generator][:provider] = stack.provider.connection.provider
|
73
|
+
if(chef_popsicle?)
|
74
|
+
freeze_runlists(stack_export)
|
75
|
+
end
|
76
|
+
remove_ignored_parameters(stack_export)
|
77
|
+
stack_export[:stack][:template] = _to_json(
|
78
|
+
stack_export[:stack][:template]
|
79
|
+
)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Provide query methods on options hash
|
84
|
+
#
|
85
|
+
# @param args [Object] argument list
|
86
|
+
# @return [Object]
|
87
|
+
def method_missing(*args)
|
88
|
+
m = args.first.to_s
|
89
|
+
if(m.end_with?('?') && options.has_key?(k = m.sub('?', '').to_sym))
|
90
|
+
!!options[k]
|
91
|
+
else
|
92
|
+
super
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
protected
|
97
|
+
|
98
|
+
# Remove parameter values from export that are configured to be
|
99
|
+
# ignored
|
100
|
+
#
|
101
|
+
# @param export [Hash] stack export
|
102
|
+
# @return [Hash]
|
103
|
+
def remove_ignored_parameters(export)
|
104
|
+
options[:ignored_parameters].each do |param|
|
105
|
+
if(export[:stack][:options][:parameters])
|
106
|
+
export[:stack][:options][:parameters].delete(param)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
export
|
110
|
+
end
|
111
|
+
|
112
|
+
# Environment name to use when interacting with Chef
|
113
|
+
#
|
114
|
+
# @param export [Hash] current export state
|
115
|
+
# @return [String] environment name
|
116
|
+
def chef_environment_name(export)
|
117
|
+
if(chef_environment_parameter?)
|
118
|
+
name = export[:stack][:options][:parameters][options[:chef_environment_parameter]]
|
119
|
+
end
|
120
|
+
name || DEFAULT_CHEF_ENVIRONMENT
|
121
|
+
end
|
122
|
+
|
123
|
+
# @return [Chef::Environment]
|
124
|
+
def environment
|
125
|
+
unless(@env)
|
126
|
+
@env = Chef::Environment.load('_default')
|
127
|
+
end
|
128
|
+
@env
|
129
|
+
end
|
130
|
+
|
131
|
+
# Find latest available cookbook version within
|
132
|
+
# the configured environment
|
133
|
+
#
|
134
|
+
# @param cookbook [String] name of cookbook
|
135
|
+
# @return [Chef::Version]
|
136
|
+
def allowed_cookbook_version(cookbook)
|
137
|
+
restriction = environment.cookbook_versions[cookbook]
|
138
|
+
requirement = Gem::Requirement.new(restriction)
|
139
|
+
Chef::CookbookVersion.available_versions(cookbook).detect do |v|
|
140
|
+
requirement.satisfied_by?(Gem::Version.new(v))
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Extract the runlist item. Fully expands roles and provides
|
145
|
+
# version pegged runlist.
|
146
|
+
#
|
147
|
+
# @param item [Chef::RunList::RunListItem, Array<String>]
|
148
|
+
# @return [Hash] new chef configuration hash
|
149
|
+
# @note this will expand all roles
|
150
|
+
def extract_runlist_item(item)
|
151
|
+
rl_item = item.is_a?(Chef::RunList::RunListItem) ? item : Chef::RunList::RunListItem.new(item)
|
152
|
+
static_content = Mash.new(:run_list => [])
|
153
|
+
if(rl_item.recipe?)
|
154
|
+
cookbook, recipe = rl_item.name.split('::')
|
155
|
+
peg_version = allowed_cookbook_version(cookbook)
|
156
|
+
static_content[:run_list] << "recipe[#{[cookbook, recipe || 'default'].join('::')}@#{peg_version}]"
|
157
|
+
elsif(rl_item.role?)
|
158
|
+
role = Chef::Role.load(rl_item.name)
|
159
|
+
role.run_list.each do |item|
|
160
|
+
static_content = Chef::Mixin::DeepMerge.merge(static_content, extract_runlist_item(item))
|
161
|
+
end
|
162
|
+
static_content = Chef::Mixin::DeepMerge.merge(
|
163
|
+
static_content, Chef::Mixin::DeepMerge.merge(role.default_attributes, role.override_attributes)
|
164
|
+
)
|
165
|
+
else
|
166
|
+
raise TypeError.new("Unknown chef run list item encountered: #{rl_item.inspect}")
|
167
|
+
end
|
168
|
+
static_content
|
169
|
+
end
|
170
|
+
|
171
|
+
# Expand any detected chef run lists and freeze them within the
|
172
|
+
# stack template
|
173
|
+
#
|
174
|
+
# @param first_run [Hash] chef first run hash
|
175
|
+
# @return [Hash]
|
176
|
+
def unpack_and_freeze_runlist(first_run)
|
177
|
+
extracted_runlists = first_run['run_list'].map do |item|
|
178
|
+
extract_runlist_item(cf_replace(item))
|
179
|
+
end
|
180
|
+
first_run.delete('run_list')
|
181
|
+
first_run.replace(
|
182
|
+
extracted_runlists.inject(first_run) do |memo, first_run_item|
|
183
|
+
Chef::Mixin::DeepMerge.merge(memo, first_run_item)
|
184
|
+
end
|
185
|
+
)
|
186
|
+
end
|
187
|
+
|
188
|
+
# Freeze chef run lists
|
189
|
+
#
|
190
|
+
# @param exported [Hash] stack export
|
191
|
+
# @return [Hash]
|
192
|
+
def freeze_runlists(exported)
|
193
|
+
first_runs = locate_runlists(exported)
|
194
|
+
first_runs.each do |first_run|
|
195
|
+
unpack_and_freeze_runlist(first_run)
|
196
|
+
end
|
197
|
+
exported
|
198
|
+
end
|
199
|
+
|
200
|
+
# Locate chef run lists within data collection
|
201
|
+
#
|
202
|
+
# @param thing [Enumerable] collection from export
|
203
|
+
# @return [Enumerable] updated collection from export
|
204
|
+
def locate_runlists(thing)
|
205
|
+
result = []
|
206
|
+
case thing
|
207
|
+
when Hash
|
208
|
+
if(thing['content'] && thing['content']['run_list'])
|
209
|
+
result << thing['content']
|
210
|
+
else
|
211
|
+
thing.each do |k,v|
|
212
|
+
result += locate_runlists(v)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
when Array
|
216
|
+
thing.each do |v|
|
217
|
+
result += locate_runlists(v)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
result
|
221
|
+
end
|
222
|
+
|
223
|
+
# Apply cloudformation function to data
|
224
|
+
#
|
225
|
+
# @param hsh [Object] stack template item
|
226
|
+
# @return [Object]
|
227
|
+
def cf_replace(hsh)
|
228
|
+
if(hsh.is_a?(Hash))
|
229
|
+
case hsh.keys.first
|
230
|
+
when 'Fn::Join'
|
231
|
+
cf_join(*hsh.values.first)
|
232
|
+
when 'Ref'
|
233
|
+
cf_ref(hsh.values.first)
|
234
|
+
else
|
235
|
+
hsh
|
236
|
+
end
|
237
|
+
else
|
238
|
+
hsh
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# Apply Ref function
|
243
|
+
#
|
244
|
+
# @param ref_name [Hash]
|
245
|
+
# @return [Object] value in parameters
|
246
|
+
def cf_ref(ref_name)
|
247
|
+
if(stack.parameters.has_key?(ref_name))
|
248
|
+
stack.parameters[ref_name]
|
249
|
+
else
|
250
|
+
raise KeyError.new("No parameter found with given reference name (#{ref_name}). " <<
|
251
|
+
"Only parameter based references supported!")
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
# Apply Join function
|
256
|
+
#
|
257
|
+
# @param delim [String] join delimiter
|
258
|
+
# @param args [String, Hash] items to join
|
259
|
+
# @return [String]
|
260
|
+
def cf_join(delim, args)
|
261
|
+
args.map do |arg|
|
262
|
+
if(arg.is_a?(Hash))
|
263
|
+
cf_replace(arg)
|
264
|
+
else
|
265
|
+
arg.to_s
|
266
|
+
end
|
267
|
+
end.join(delim)
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|
271
|
+
end
|