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 CHANGED
@@ -1,3 +1,8 @@
1
+ ## v0.1.16
2
+ * Fix exit code on stack destroy
3
+ * Update stack loading for single stack requests
4
+ * Add import and export functionality
5
+
1
6
  ## v0.1.14
2
7
  * Extract template building tools
3
8
  * Add support for custom CF locations and prompting
@@ -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 'chef/knife/cloudformation_base'
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
- set_paths_and_discover_file!
130
- unless(File.exists?(Chef::Config[:knife][:cloudformation][:file].to_s))
131
- ui.fatal "Invalid formation file path provided: #{Chef::Config[:knife][:cloudformation][:file]}"
132
- exit 1
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][:processing])
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
- ui.info " -> #{ui.color('Name:', :bold)} #{name} #{ui.color('Path:', :bold)} #{Chef::Config[:knife][:cloudformation][:file]} #{ui.color('(not pre-processed)', :yellow) if Chef::Config[:knife][:cloudformation][:disable_processing]}"
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 'chef/knife/cloudformation_base'
1
+ require 'knife-cloudformation/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
@@ -1,4 +1,4 @@
1
- require 'chef/knife/cloudformation_base'
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)}"
@@ -1,4 +1,4 @@
1
- require 'chef/knife/cloudformation_base'
1
+ require 'knife-cloudformation/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
@@ -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
@@ -1,4 +1,4 @@
1
- require 'chef/knife/cloudformation_base'
1
+ require 'knife-cloudformation/cloudformation_base'
2
2
  require 'knife-cloudformation/utils'
3
3
 
4
4
  class Chef
@@ -1,4 +1,4 @@
1
- require 'chef/knife/cloudformation_base'
1
+ require 'knife-cloudformation/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
@@ -1,4 +1,4 @@
1
- require 'chef/knife/cloudformation_create'
1
+ require 'knife-cloudformation/cloudformation_base'
2
2
 
3
3
  class Chef
4
4
  class Knife
@@ -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 :timeout_in_minutes, :timeout
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
- seed = stacks.detect do |stk|
122
- stk['StackId'] == s_id
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
@@ -2,5 +2,5 @@ module KnifeCloudformation
2
2
  class Version < Gem::Version
3
3
  end
4
4
 
5
- VERSION = Version.new('0.1.14')
5
+ VERSION = Version.new('0.1.16')
6
6
  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.14
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-10-31 00:00:00.000000000 Z
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