knife-cloudformation 0.1.14 → 0.1.16
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +5 -0
- data/knife-cloudformation.gemspec +1 -0
- data/lib/chef/knife/cloudformation_create.rb +19 -7
- data/lib/chef/knife/cloudformation_describe.rb +1 -1
- data/lib/chef/knife/cloudformation_destroy.rb +2 -2
- data/lib/chef/knife/cloudformation_events.rb +1 -1
- data/lib/chef/knife/cloudformation_export.rb +79 -0
- data/lib/chef/knife/cloudformation_import.rb +35 -0
- data/lib/chef/knife/cloudformation_inspect.rb +1 -1
- data/lib/chef/knife/cloudformation_list.rb +1 -1
- data/lib/chef/knife/cloudformation_update.rb +1 -1
- data/lib/knife-cloudformation/aws_commons/stack.rb +1 -1
- data/lib/knife-cloudformation/aws_commons.rb +5 -2
- data/lib/knife-cloudformation/export.rb +173 -0
- data/lib/knife-cloudformation/version.rb +1 -1
- metadata +22 -3
- /data/lib/{chef/knife → knife-cloudformation}/cloudformation_base.rb +0 -0
data/CHANGELOG.md
CHANGED
@@ -11,6 +11,7 @@ Gem::Specification.new do |s|
|
|
11
11
|
s.require_path = 'lib'
|
12
12
|
s.add_dependency 'chef'
|
13
13
|
s.add_dependency 'fog', '~> 1.17'
|
14
|
+
s.add_dependency 'unf'
|
14
15
|
s.add_dependency 'net-sftp'
|
15
16
|
s.add_dependency 'sparkle_formation', '~> 0.1.2'
|
16
17
|
s.add_dependency 'redis-objects'
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'sparkle_formation'
|
2
2
|
require 'pathname'
|
3
|
-
require '
|
3
|
+
require 'knife-cloudformation/cloudformation_base'
|
4
4
|
|
5
5
|
class Chef
|
6
6
|
class Knife
|
@@ -126,13 +126,17 @@ class Chef
|
|
126
126
|
exit 1
|
127
127
|
end
|
128
128
|
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
129
|
+
unless(Chef::Config[:knife][:cloudformation][:template])
|
130
|
+
set_paths_and_discover_file!
|
131
|
+
unless(File.exists?(Chef::Config[:knife][:cloudformation][:file].to_s))
|
132
|
+
ui.fatal "Invalid formation file path provided: #{Chef::Config[:knife][:cloudformation][:file]}"
|
133
|
+
exit 1
|
134
|
+
end
|
133
135
|
end
|
134
136
|
|
135
|
-
if(Chef::Config[:knife][:cloudformation][:
|
137
|
+
if(Chef::Config[:knife][:cloudformation][:template])
|
138
|
+
file = Chef::Config[:knife][:cloudformation][:template]
|
139
|
+
elsif(Chef::Config[:knife][:cloudformation][:processing])
|
136
140
|
file = SparkleFormation.compile(Chef::Config[:knife][:cloudformation][:file])
|
137
141
|
else
|
138
142
|
file = _from_json(File.read(Chef::Config[:knife][:cloudformation][:file]))
|
@@ -143,7 +147,14 @@ class Chef
|
|
143
147
|
exit 1
|
144
148
|
end
|
145
149
|
ui.info "#{ui.color('Cloud Formation: ', :bold)} #{ui.color(action_type, :green)}"
|
146
|
-
|
150
|
+
stack_info = "#{ui.color('Name:', :bold)} #{name}"
|
151
|
+
if(Chef::Config[:knife][:cloudformation][:path])
|
152
|
+
stack_info << " #{ui.color('Path:', :bold)} #{Chef::Config[:knife][:cloudformation][:file]}"
|
153
|
+
if(Chef::Config[:knife][:cloudformation][:disable_processing])
|
154
|
+
stack_info << " #{ui.color('(not pre-processed)', :yellow)}"
|
155
|
+
end
|
156
|
+
end
|
157
|
+
ui.info " -> #{stack_info}"
|
147
158
|
populate_parameters!(file)
|
148
159
|
stack_def = KnifeCloudformation::AwsCommons::Stack.build_stack_definition(file, Chef::Config[:knife][:cloudformation][:options])
|
149
160
|
aws.create_stack(name, stack_def)
|
@@ -175,6 +186,7 @@ class Chef
|
|
175
186
|
if(stack['Parameters'])
|
176
187
|
Chef::Config[:knife][:cloudformation][:options][:parameters] ||= Mash.new
|
177
188
|
stack['Parameters'].each do |k,v|
|
189
|
+
next if Chef::Config[:knife][:cloudformation][:options][:parameters][k]
|
178
190
|
valid = false
|
179
191
|
until(valid)
|
180
192
|
default = Chef::Config[:knife][:cloudformation][:options][:parameters][k] || v['Default']
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require '
|
1
|
+
require 'knife-cloudformation/cloudformation_base'
|
2
2
|
|
3
3
|
class Chef
|
4
4
|
class Knife
|
@@ -30,7 +30,7 @@ class Chef
|
|
30
30
|
stacks.each do |stack_name|
|
31
31
|
poll_stack(stack_name)
|
32
32
|
end
|
33
|
-
rescue Fog::AWS::CloudFormation::NotFound
|
33
|
+
rescue SystemExit, Fog::AWS::CloudFormation::NotFound
|
34
34
|
# ignore this error since this is the end result we want!
|
35
35
|
end
|
36
36
|
ui.info " -> Destroyed Cloud Formation#{plural}: #{ui.color(stacks.join(', '), :bold, :red)}"
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'knife-cloudformation/cloudformation_base'
|
2
|
+
require 'knife-cloudformation/export'
|
3
|
+
|
4
|
+
class Chef
|
5
|
+
class Knife
|
6
|
+
class CloudformationExport < Knife
|
7
|
+
|
8
|
+
include KnifeCloudformation::KnifeBase
|
9
|
+
|
10
|
+
banner 'knife cloudformation export NAME'
|
11
|
+
|
12
|
+
# TODO: Add option for s3 exports
|
13
|
+
|
14
|
+
option(:path,
|
15
|
+
:long => '--export-path PATH',
|
16
|
+
:description => 'Path to write export JSON file',
|
17
|
+
:proc => lambda{|v| Chef::Config[:knife][:cloudformation][:export_path] = v }
|
18
|
+
)
|
19
|
+
|
20
|
+
option(:ignore_parameters,
|
21
|
+
:short => '-P NAME',
|
22
|
+
:long => '--exclude-parameter NAME',
|
23
|
+
:description => 'Exclude parameter from export (can be used multiple times)',
|
24
|
+
:proc => lambda{|v|
|
25
|
+
Chef::Config[:knife][:cloudformation][:ignore_parameters] ||= []
|
26
|
+
Chef::Config[:knife][:cloudformation][:ignore_parameters].push(v).uniq!
|
27
|
+
}
|
28
|
+
)
|
29
|
+
|
30
|
+
option(:chef_environment_parameter,
|
31
|
+
:long => '--chef-environment-parameter NAME',
|
32
|
+
:description => 'Parameter used within stack to specify Chef environment',
|
33
|
+
:proc => lambda{|v|
|
34
|
+
Chef::Config[:knife][:cloudformation][:chef_environment_parameter] = v
|
35
|
+
}
|
36
|
+
)
|
37
|
+
|
38
|
+
option(:chef_popsicle,
|
39
|
+
:long => '--[no-]freeze-run-list',
|
40
|
+
:boolean => true,
|
41
|
+
:default => true,
|
42
|
+
:description => 'Freezes first run files',
|
43
|
+
:proc => lambda{|v| Chef::Config[:knife][:cloudformation][:chef_popsicle] = v }
|
44
|
+
)
|
45
|
+
|
46
|
+
def run
|
47
|
+
if(Chef::Config[:knife][:cloudformation][:export_path])
|
48
|
+
file_path = File.join(
|
49
|
+
Chef::Config[:knife][:cloudformation][:export_path], "#{name_args.first}-#{Time.now.to_i}.json"
|
50
|
+
)
|
51
|
+
end
|
52
|
+
ui.info "#{ui.color('Stack Export:', :bold)} #{name_args.first}"
|
53
|
+
if(file_path)
|
54
|
+
ui.info " - Writing to: #{file_path}"
|
55
|
+
else
|
56
|
+
ui.info " - Printing to console"
|
57
|
+
end
|
58
|
+
ui.confirm 'Perform export'
|
59
|
+
ex_opts = {}
|
60
|
+
[:chef_popsicle, :chef_environment_parameter, :ignore_parameters].each do |key|
|
61
|
+
unless(Chef::Config[:knife][:cloudformation][key].nil?)
|
62
|
+
ex_opts[key] = Chef::Config[:knife][:cloudformation][key]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
exporter = KnifeCloudformation::Export.new(name_args.first, ex_opts.merge(:aws_commons => aws))
|
66
|
+
result = exporter.export
|
67
|
+
if(file_path)
|
68
|
+
File.open(file_path, 'w') do |file|
|
69
|
+
file.puts _format_json(result)
|
70
|
+
end
|
71
|
+
else
|
72
|
+
ui.info _format_json(result)
|
73
|
+
end
|
74
|
+
ui.info "#{ui.color('Stack export', :bold)} (#{name_args.first}): #{ui.color('complete', :green)}"
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'knife-cloudformation/cloudformation_base'
|
2
|
+
|
3
|
+
class Chef
|
4
|
+
class Knife
|
5
|
+
class CloudformationImport < Knife
|
6
|
+
|
7
|
+
include KnifeCloudformation::KnifeBase
|
8
|
+
|
9
|
+
banner 'knife cloudformation import NEW_STACK_NAME JSON_EXPORT_FILE'
|
10
|
+
|
11
|
+
def run
|
12
|
+
stack_name, json_file = name_args
|
13
|
+
ui.info "#{ui.color('Stack Import', :bold)} (#{json_file})"
|
14
|
+
if(File.exists?(json_file))
|
15
|
+
stack = _from_json(File.read(json_file))
|
16
|
+
creator = Chef::Knife::CloudformationCreate.new
|
17
|
+
creator.name_args = [stack_name]
|
18
|
+
Chef::Config[:knife][:cloudformation][:template] = stack['template_body']
|
19
|
+
Chef::Config[:knife][:cloudformation][:options] = Mash.new
|
20
|
+
Chef::Config[:knife][:cloudformation][:options][:parameters] = Mash.new
|
21
|
+
stack['parameters'].each do |k,v|
|
22
|
+
Chef::Config[:knife][:cloudformation][:options][:parameters][k] = v
|
23
|
+
end
|
24
|
+
ui.info ' - Starting creation of import'
|
25
|
+
creator.run
|
26
|
+
ui.info "#{ui.color('Stack Import', :bold)} (#{json_file}): #{ui.color('complete', :green)}"
|
27
|
+
else
|
28
|
+
ui.error "Failed to locate JSON export file (#{json_file})"
|
29
|
+
exit 1
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -229,7 +229,7 @@ module KnifeCloudformation
|
|
229
229
|
def timeout_in_minutes
|
230
230
|
@memo[:raw_stack].value['TimeoutInMinutes']
|
231
231
|
end
|
232
|
-
alias_method :
|
232
|
+
alias_method :timeout, :timeout_in_minutes
|
233
233
|
|
234
234
|
def stack_id
|
235
235
|
@memo[:raw_stack].value['StackId']
|
@@ -90,6 +90,7 @@ module KnifeCloudformation
|
|
90
90
|
end
|
91
91
|
if(@memo[:stacks].update_allowed? || args[:force_refresh])
|
92
92
|
@memo[:stacks_lock].lock do
|
93
|
+
@_stacks_cached = true
|
93
94
|
@memo[:stacks].value = aws(:cloud_formation).describe_stacks.body['Stacks']
|
94
95
|
end
|
95
96
|
end
|
@@ -118,8 +119,10 @@ module KnifeCloudformation
|
|
118
119
|
[name, name.start_with?('arn:') ? name : id_from_stack_name(name)]
|
119
120
|
end.map do |name, s_id|
|
120
121
|
unless(@local[:stacks][s_id])
|
121
|
-
|
122
|
-
|
122
|
+
if(@_stacks_cached)
|
123
|
+
seed = stacks.detect do |stk|
|
124
|
+
stk['StackId'] == s_id
|
125
|
+
end
|
123
126
|
end
|
124
127
|
@local[:stacks][s_id] = Stack.new(name, self, seed)
|
125
128
|
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'knife-cloudformation/aws_commons'
|
2
|
+
require 'chef'
|
3
|
+
|
4
|
+
module KnifeCloudformation
|
5
|
+
class Export
|
6
|
+
|
7
|
+
DEFAULT_OPTIONS = {
|
8
|
+
:chef_popsicle => true,
|
9
|
+
:ignored_parameters => ['Environment', 'StackCreator'],
|
10
|
+
:chef_environment_parameter => 'Environment'
|
11
|
+
}
|
12
|
+
|
13
|
+
attr_reader :stack, :stack_name, :stack_id, :options, :aws_commons
|
14
|
+
|
15
|
+
def initialize(stack_name, options={})
|
16
|
+
@stack_name = stack_name
|
17
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
18
|
+
if(aws_commons?)
|
19
|
+
@aws_commons = options[:aws_commons]
|
20
|
+
else
|
21
|
+
raise ArgumentError.new('Expecting `AwsCommons` instance but none provided!')
|
22
|
+
end
|
23
|
+
load_stack
|
24
|
+
end
|
25
|
+
|
26
|
+
def export
|
27
|
+
exported = stack.to_hash
|
28
|
+
if(chef_popsicle?)
|
29
|
+
freeze_runlists(exported)
|
30
|
+
end
|
31
|
+
remove_ignored_parameters(exported)
|
32
|
+
exported
|
33
|
+
end
|
34
|
+
|
35
|
+
def method_missing(*args)
|
36
|
+
m = args.first.to_s
|
37
|
+
if(m.end_with?('?') && options.has_key?(k = m.sub('?', '').to_sym))
|
38
|
+
!!options[k]
|
39
|
+
else
|
40
|
+
super
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def remove_ignored_parameters(export)
|
47
|
+
options[:ignored_parameters].each do |param|
|
48
|
+
export[:parameters].delete(param)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def chef_environment_name
|
53
|
+
if(chef_environment_parameter?)
|
54
|
+
name = stack[:parameters][options[:chef_environment_parameter]]
|
55
|
+
end
|
56
|
+
name || '_default'
|
57
|
+
end
|
58
|
+
|
59
|
+
def environment
|
60
|
+
unless(@env)
|
61
|
+
@env = Chef::Environment.load('imdev')
|
62
|
+
end
|
63
|
+
@env
|
64
|
+
end
|
65
|
+
|
66
|
+
def load_stack
|
67
|
+
@stack = AwsCommons::Stack.new(stack_name, aws_commons)
|
68
|
+
@stack_id = @stack.stack_id
|
69
|
+
@stack
|
70
|
+
end
|
71
|
+
|
72
|
+
def allowed_cookbook_version(cookbook)
|
73
|
+
restriction = environment.cookbook_versions[cookbook]
|
74
|
+
requirement = Gem::Requirement.new(restriction)
|
75
|
+
Chef::CookbookVersion.available_versions(cookbook).detect do |v|
|
76
|
+
requirement.satisfied_by?(Gem::Version.new(v))
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def extract_runlist_item(item)
|
81
|
+
rl_item = item.is_a?(Chef::RunList::RunListItem) ? item : Chef::RunList::RunListItem.new(item)
|
82
|
+
static_content = Mash.new(:run_list => [])
|
83
|
+
if(rl_item.recipe?)
|
84
|
+
cookbook, recipe = rl_item.name.split('::')
|
85
|
+
peg_version = allowed_cookbook_version(cookbook)
|
86
|
+
static_content[:run_list] << "recipe[#{[cookbook, recipe || 'default'].join('::')}@#{peg_version}]"
|
87
|
+
elsif(rl_item.role?)
|
88
|
+
role = Chef::Role.load(rl_item.name)
|
89
|
+
role.run_list.each do |item|
|
90
|
+
static_content = Chef::Mixin::DeepMerge.merge(static_content, extract_runlist_item(item))
|
91
|
+
end
|
92
|
+
static_content = Chef::Mixin::DeepMerge.merge(
|
93
|
+
static_content, Chef::Mixin::DeepMerge.merge(role.default_attributes, role.override_attributes)
|
94
|
+
)
|
95
|
+
else
|
96
|
+
# dunno what this is
|
97
|
+
end
|
98
|
+
static_content
|
99
|
+
end
|
100
|
+
|
101
|
+
def unpack_and_freeze_runlist(first_run)
|
102
|
+
extracted_runlists = first_run['run_list'].map do |item|
|
103
|
+
extract_runlist_item(cf_replace(item))
|
104
|
+
end
|
105
|
+
first_run.delete('run_list')
|
106
|
+
first_run.replace(
|
107
|
+
extracted_runlists.inject(first_run) do |memo, first_run_item|
|
108
|
+
Chef::Mixin::DeepMerge.merge(memo, first_run_item)
|
109
|
+
end
|
110
|
+
)
|
111
|
+
end
|
112
|
+
|
113
|
+
def freeze_runlists(exported)
|
114
|
+
first_runs = locate_runlists(exported)
|
115
|
+
first_runs.each do |first_run|
|
116
|
+
unpack_and_freeze_runlist(first_run)
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def locate_runlists(thing)
|
121
|
+
result = []
|
122
|
+
case thing
|
123
|
+
when Hash
|
124
|
+
if(thing['content'] && thing['content']['run_list'])
|
125
|
+
result << thing['content']
|
126
|
+
else
|
127
|
+
thing.each do |k,v|
|
128
|
+
result += locate_runlists(v)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
when Array
|
132
|
+
thing.each do |v|
|
133
|
+
result += locate_runlists(v)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
result
|
137
|
+
end
|
138
|
+
|
139
|
+
def cf_replace(hsh)
|
140
|
+
if(hsh.is_a?(Hash))
|
141
|
+
case hsh.keys.first
|
142
|
+
when 'Fn::Join'
|
143
|
+
cf_join(*hsh.values.first)
|
144
|
+
when 'Ref'
|
145
|
+
cf_ref(hsh.values.first)
|
146
|
+
else
|
147
|
+
hsh
|
148
|
+
end
|
149
|
+
else
|
150
|
+
hsh
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def cf_ref(ref_name)
|
155
|
+
if(stack.parameters.has_key?(ref_name))
|
156
|
+
stack.parameters[ref_name]
|
157
|
+
else
|
158
|
+
raise KeyError.new("No parameter found with given reference name (#{ref_name}). " <<
|
159
|
+
"Only parameter based references supported!")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def cf_join(delim, args)
|
164
|
+
args.map do |arg|
|
165
|
+
if(arg.is_a?(Hash))
|
166
|
+
cf_replace(arg)
|
167
|
+
else
|
168
|
+
arg.to_s
|
169
|
+
end
|
170
|
+
end.join(delim)
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: knife-cloudformation
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.16
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-11-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: chef
|
@@ -43,6 +43,22 @@ dependencies:
|
|
43
43
|
- - ~>
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '1.17'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: unf
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :runtime
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
46
62
|
- !ruby/object:Gem::Dependency
|
47
63
|
name: net-sftp
|
48
64
|
requirement: !ruby/object:Gem::Requirement
|
@@ -114,15 +130,18 @@ extensions: []
|
|
114
130
|
extra_rdoc_files: []
|
115
131
|
files:
|
116
132
|
- lib/chef/knife/cloudformation_events.rb
|
117
|
-
- lib/chef/knife/cloudformation_base.rb
|
118
133
|
- lib/chef/knife/cloudformation_destroy.rb
|
119
134
|
- lib/chef/knife/cloudformation_describe.rb
|
135
|
+
- lib/chef/knife/cloudformation_export.rb
|
120
136
|
- lib/chef/knife/cloudformation_list.rb
|
121
137
|
- lib/chef/knife/cloudformation_create.rb
|
122
138
|
- lib/chef/knife/cloudformation_update.rb
|
123
139
|
- lib/chef/knife/cloudformation_inspect.rb
|
140
|
+
- lib/chef/knife/cloudformation_import.rb
|
124
141
|
- lib/knife-cloudformation.rb
|
142
|
+
- lib/knife-cloudformation/cloudformation_base.rb
|
125
143
|
- lib/knife-cloudformation/version.rb
|
144
|
+
- lib/knife-cloudformation/export.rb
|
126
145
|
- lib/knife-cloudformation/aws_commons.rb
|
127
146
|
- lib/knife-cloudformation/cache.rb
|
128
147
|
- lib/knife-cloudformation/utils.rb
|
File without changes
|