knife-cloudformation 0.2.24 → 0.5.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +10 -0
  3. data/README.md +16 -0
  4. data/knife-cloudformation.gemspec +14 -4
  5. data/lib/knife-cloudformation.rb +0 -28
  6. data/lib/knife-cloudformation/version.rb +1 -1
  7. metadata +18 -80
  8. data/lib/chef/knife/cloudformation_create.rb +0 -147
  9. data/lib/chef/knife/cloudformation_describe.rb +0 -99
  10. data/lib/chef/knife/cloudformation_destroy.rb +0 -84
  11. data/lib/chef/knife/cloudformation_events.rb +0 -117
  12. data/lib/chef/knife/cloudformation_export.rb +0 -162
  13. data/lib/chef/knife/cloudformation_import.rb +0 -141
  14. data/lib/chef/knife/cloudformation_inspect.rb +0 -206
  15. data/lib/chef/knife/cloudformation_list.rb +0 -72
  16. data/lib/chef/knife/cloudformation_promote.rb +0 -40
  17. data/lib/chef/knife/cloudformation_update.rb +0 -137
  18. data/lib/chef/knife/cloudformation_validate.rb +0 -36
  19. data/lib/knife-cloudformation/cache.rb +0 -385
  20. data/lib/knife-cloudformation/knife.rb +0 -9
  21. data/lib/knife-cloudformation/knife/base.rb +0 -195
  22. data/lib/knife-cloudformation/knife/stack.rb +0 -197
  23. data/lib/knife-cloudformation/knife/template.rb +0 -213
  24. data/lib/knife-cloudformation/monkey_patch.rb +0 -8
  25. data/lib/knife-cloudformation/monkey_patch/stack.rb +0 -195
  26. data/lib/knife-cloudformation/provider.rb +0 -225
  27. data/lib/knife-cloudformation/utils.rb +0 -24
  28. data/lib/knife-cloudformation/utils/animal_strings.rb +0 -28
  29. data/lib/knife-cloudformation/utils/debug.rb +0 -31
  30. data/lib/knife-cloudformation/utils/json.rb +0 -64
  31. data/lib/knife-cloudformation/utils/object_storage.rb +0 -28
  32. data/lib/knife-cloudformation/utils/output.rb +0 -79
  33. data/lib/knife-cloudformation/utils/path_selector.rb +0 -99
  34. data/lib/knife-cloudformation/utils/ssher.rb +0 -29
  35. data/lib/knife-cloudformation/utils/stack_exporter.rb +0 -271
  36. data/lib/knife-cloudformation/utils/stack_parameter_scrubber.rb +0 -37
  37. data/lib/knife-cloudformation/utils/stack_parameter_validator.rb +0 -124
@@ -1,84 +0,0 @@
1
- require 'knife-cloudformation'
2
-
3
- class Chef
4
- class Knife
5
- # Cloudformation destroy command
6
- class CloudformationDestroy < Knife
7
-
8
- include KnifeCloudformation::Knife::Base
9
-
10
- banner 'knife cloudformation destroy NAME [NAME]'
11
-
12
- option(:polling,
13
- :long => '--[no-]poll',
14
- :description => 'Enable stack event polling.',
15
- :boolean => true,
16
- :default => true,
17
- :proc => lambda {|val| Chef::Config[:knife][:cloudformation][:poll] = val }
18
- )
19
-
20
- # Run the stack destruction action
21
- def _run
22
- stacks = name_args.sort
23
- plural = 's' if stacks.size > 1
24
- globs = stacks.find_all do |s|
25
- s !~ /^[a-zA-Z0-9-]+$/
26
- end
27
- unless(globs.empty?)
28
- glob_stacks = provider.connection.stacks.all.find_all do |remote_stack|
29
- globs.detect do |glob|
30
- File.fnmatch(glob, remote_stack.name)
31
- end
32
- end
33
- stacks += glob_stacks.map(&:name)
34
- stacks -= globs
35
- stacks.sort!
36
- end
37
- ui.warn "Destroying Cloud Formation#{plural}: #{ui.color(stacks.join(', '), :bold)}"
38
- ui.confirm "Destroy formation#{plural}"
39
- stacks.each do |stack_name|
40
- stack = provider.connection.stacks.get(stack_name)
41
- if(stack)
42
- nested_stack_cleanup!(stack)
43
- stack.destroy
44
- else
45
- ui.warn "Failed to locate requested stack: #{ui.color(stack_name, :bold)}"
46
- end
47
- end
48
- if(config[:polling])
49
- if(stacks.size == 1)
50
- poll_stack(stacks.first)
51
- else
52
- ui.error "Stack polling is not available when multiple stack deletion is requested!"
53
- end
54
- end
55
- ui.info " -> Destroyed Cloud Formation#{plural}: #{ui.color(stacks.join(', '), :bold, :red)}"
56
- end
57
-
58
- # Cleanup persisted templates if nested stack resources are included
59
- def nested_stack_cleanup!(stack)
60
- nest_stacks = stack.template.fetch('Resources', {}).values.find_all do |resource|
61
- resource['Type'] == 'AWS::CloudFormation::Stack'
62
- end.each do |resource|
63
- url = resource['Properties']['TemplateURL']
64
- if(url)
65
- _, bucket_name, path = URI.parse(url).path.split('/', 3)
66
- bucket = provider.connection.api_for(:storage).buckets.get(bucket_name)
67
- if(bucket)
68
- file = bucket.files.get(path)
69
- if(file)
70
- file.destroy
71
- ui.info "Deleted nested stack template! (Bucket: #{bucket_name} Template: #{path})"
72
- else
73
- ui.warn "Failed to locate template file within bucket for deletion! (#{path})"
74
- end
75
- else
76
- ui.warn "Failed to locate bucket containing template file for deletion! (#{bucket_name})"
77
- end
78
- end
79
- end
80
- end
81
-
82
- end
83
- end
84
- end
@@ -1,117 +0,0 @@
1
- require 'knife-cloudformation'
2
-
3
- class Chef
4
- class Knife
5
- # Cloudformation list command
6
- class CloudformationEvents < Knife
7
-
8
- banner 'knife cloudformation events NAME'
9
-
10
- include KnifeCloudformation::Knife::Base
11
-
12
- option(:polling,
13
- :short => '-p',
14
- :long => '--[no-]poll',
15
- :boolean => true,
16
- :default => true,
17
- :description => 'Poll events while stack status is "in progress"',
18
- :proc => lambda {|v| Chef::Config[:knife][:cloudformation][:poll] = v }
19
- )
20
-
21
- option(:attribute,
22
- :short => '-a ATTR',
23
- :long => '--attribute ATTR',
24
- :description => 'Attribute to print. Can be used multiple times.',
25
- :proc => lambda {|val|
26
- Chef::Config[:knife][:cloudformation][:attributes] ||= []
27
- Chef::Config[:knife][:cloudformation][:attributes].push(val).uniq!
28
- }
29
- )
30
-
31
- option(:poll_delay,
32
- :short => '-D secs',
33
- :long => '--poll-delay secs',
34
- :description => 'Number of seconds to pause between event poll',
35
- :proc => lambda {|val|
36
- Chef::Config[:knife][:cloudformation][:poll_delay] = val.to_i
37
- }
38
- )
39
-
40
- option(:all_attributes,
41
- :long => '--all-attributes',
42
- :description => 'Print all attributes'
43
- )
44
-
45
- # Run the events list action
46
- def _run
47
- name = name_args.first
48
- ui.info "Cloud Formation Events for Stack: #{ui.color(name, :bold)}\n"
49
- stack = provider.connection.stacks.get(name)
50
- last_id = nil
51
- if(stack)
52
- events = get_events(stack)
53
- things_output(name, events, 'events')
54
- last_id = events.last ? events.last[:id] : nil
55
- if(Chef::Config[:knife][:cloudformation][:poll])
56
- cycle_events = true
57
- while(cycle_events)
58
- cycle_events = stack.in_progress?
59
- sleep(Chef::Config[:knife][:cloudformation][:poll_delay] || 10)
60
- stack.events.reload
61
- events = get_events(stack, last_id)
62
- unless(events.empty?)
63
- last_id = events.last[:id]
64
- things_output(nil, events, 'events', :no_title, :ignore_empty_output)
65
- end
66
- nest_stacks = stack.resources.all.find_all do |resource|
67
- resource.state.to_s.end_with?('in_progress') &&
68
- resource.type == 'AWS::CloudFormation::Stack'
69
- end
70
- if(nest_stacks)
71
- nest_stacks.each do |nest_stack|
72
- begin
73
- poll_stack(nest_stack.id)
74
- ui.info "Complete event listing for nested stack (#{nest_stack.name})"
75
- rescue => e
76
- ui.warn "Error encountered on event listing for nested stack - #{e} (#{nest_stack.name})"
77
- end
78
- end
79
- end
80
- stack.reload
81
- end
82
- # Extra to see completion
83
- things_output(nil, get_events(stack, last_id), 'events', :no_title, :ignore_empty_output)
84
- end
85
- else
86
- ui.fatal "Failed to locate requested stack: #{ui.color(name, :bold, :red)}"
87
- raise "Failed to locate stack: #{name}!"
88
- end
89
- end
90
-
91
- # Fetch events from stack
92
- #
93
- # @param stack [Miasma::Models::Orchestration::Stack]
94
- # @param last_id [String] only return events after this ID
95
- # @return [Array<Hash>]
96
- def get_events(stack, last_id=nil)
97
- get_things do
98
- stack_events = stack.events.all
99
- if(last_id)
100
- start_index = stack_events.index{|event| event.id == last_id}
101
- events = stack_events.slice(0, start_index.to_i)
102
- else
103
- events = stack_events
104
- end
105
- events.map do |event|
106
- Mash.new(event.attributes)
107
- end
108
- end
109
- end
110
-
111
- # @return [Array<String>] default attributes for events
112
- def default_attributes
113
- %w(time resource_logical_id resource_status resource_status_reason)
114
- end
115
- end
116
- end
117
- end
@@ -1,162 +0,0 @@
1
- require 'knife-cloudformation'
2
-
3
- class Chef
4
- class Knife
5
- # Cloudformation export command
6
- class CloudformationExport < Knife
7
-
8
- include KnifeCloudformation::Knife::Base
9
- include KnifeCloudformation::Utils::ObjectStorage
10
-
11
- banner 'knife cloudformation export STACK_NAME'
12
-
13
- option(:export_name,
14
- :long => '--export-file-name NAME',
15
- :description => 'File basename to contain the export. Can be callable block if defined within configuration',
16
- :proc => lambda{|v| Chef::Config[:knife][:cloudformation][:export][:name] = v}
17
- )
18
-
19
- option(:path,
20
- :long => '--export-path PATH',
21
- :description => 'Directory path write export JSON file',
22
- :proc => lambda{|v| Chef::Config[:knife][:cloudformation][:export][:path] = v}
23
- )
24
-
25
- option(:bucket,
26
- :long => '--export-bucket BUCKET_NAME',
27
- :description => 'Remote file bucket to write export JSON file',
28
- :proc => lambda{|v| Chef::Config[:knife][:cloudformation][:export][:bucket] = v}
29
- )
30
-
31
- option(:bucket_prefix,
32
- :long => '--bucket-key-prefix PREFIX',
33
- :description => 'Key prefix for file storage in bucket. Can be callable block if defined within configuration',
34
- :proc => lambda{|v| Chef::Config[:knife][:cloudformation][:export][:bucket_prefix] = v}
35
- )
36
-
37
- option(:ignore_parameters,
38
- :short => '-P NAME',
39
- :long => '--exclude-parameter NAME',
40
- :description => 'Exclude parameter from export (can be used multiple times)',
41
- :proc => lambda{|v|
42
- Chef::Config[:knife][:cloudformation][:export][:ignore_parameters].push(v).uniq!
43
- }
44
- )
45
-
46
- option(:chef_environment_parameter,
47
- :long => '--chef-environment-parameter NAME',
48
- :description => 'Parameter used within stack to specify Chef environment',
49
- :proc => lambda{|v|
50
- Chef::Config[:knife][:cloudformation][:export][:chef_environment_parameter] = v
51
- }
52
- )
53
-
54
- option(:chef_popsicle,
55
- :long => '--[no-]freeze-run-list',
56
- :boolean => true,
57
- :default => true,
58
- :description => 'Freezes first run files',
59
- :proc => lambda{|v| Chef::Config[:knife][:cloudformation][:export][:chef_popsicle] = v }
60
- )
61
-
62
- unless(Chef::Config[:knife][:cloudformation].has_key?(:export))
63
- Chef::Config[:knife][:cloudformation][:export] = Mash.new(
64
- :credentials => Mash.new,
65
- :ignore_parameters => []
66
- )
67
- end
68
-
69
- # Run export action
70
- def _run
71
- stack_name = name_args.first
72
- ui.info "#{ui.color('Stack Export:', :bold)} #{stack_name}"
73
- ui.confirm 'Perform export'
74
- stack = provider.stacks.get(stack_name)
75
- if(stack)
76
- export_options = Mash.new.tap do |opts|
77
- [:chef_popsicle, :chef_environment_parameter, :ignore_parameters].each do |key|
78
- unless(Chef::Config[:knife][:cloudformation][:export][key].nil?)
79
- opts[key] = Chef::Config[:knife][:cloudformation][:export][key]
80
- end
81
- end
82
- end
83
- exporter = KnifeCloudformation::Utils::StackExporter.new(stack, export_options)
84
- result = exporter.export
85
- outputs = [
86
- write_to_file(result, stack),
87
- write_to_bucket(result, stack)
88
- ].compact
89
- if(outputs.empty?)
90
- ui.warn 'No persistent output location defined. Printing export:'
91
- ui.info _format_json(result)
92
- end
93
- ui.info "#{ui.color('Stack export', :bold)} (#{name_args.first}): #{ui.color('complete', :green)}"
94
- unless(outputs.empty?)
95
- outputs.each do |output|
96
- ui.info ui.color(" -> #{output}", :blue)
97
- end
98
- end
99
- else
100
- ui.fatal "Failed to discover requested stack: #{ui.color(stack_name, :red, :bold)}"
101
- exit -1
102
- end
103
- end
104
-
105
- # Generate file name for stack export JSON contents
106
- #
107
- # @param stack [Miasma::Models::Orchestration::Stack]
108
- # @return [String] file name
109
- def export_file_name(stack)
110
- name = Chef::Config[:knife][:cloudformation][:export][:file]
111
- if(name)
112
- if(name.respond_to?(:call))
113
- name.call(stack)
114
- else
115
- name.to_s
116
- end
117
- else
118
- "#{stack.stack_name}-#{Time.now.to_i}.json"
119
- end
120
- end
121
-
122
- # Write stack export to local file
123
- #
124
- # @param payload [Hash] stack export payload
125
- # @param stack [Misama::Stack::Orchestration::Stack]
126
- # @return [String, NilClass] path to file
127
- def write_to_file(payload, stack)
128
- raise NotImplementedError
129
- if(Chef::Config[:knife][:cloudformation][:export][:path])
130
- full_path = File.join(
131
- File.expand_path(Chef::Config[:knife][:cloudformation][:export][:path]),
132
- export_file_name(stack)
133
- )
134
- _, bucket, path = full_path.split('/', 3)
135
- directory = provider.service_for(:storage,
136
- :provider => :local,
137
- :local_root => '/'
138
- ).directories.get(bucket)
139
- file_store(payload, path, directory)
140
- end
141
- end
142
-
143
- # Write stack export to remote bucket
144
- #
145
- # @param payload [Hash] stack export payload
146
- # @param stack [Miasma::Models::Orchestration::Stack]
147
- # @return [String, NilClass] remote bucket key
148
- def write_to_bucket(payload, stack)
149
- raise NotImplementedError
150
- if(bucket = Chef::Config[:knife][:cloudformation][:export][:bucket])
151
- key_path = File.join(*[
152
- bucket_prefix(stack),
153
- export_file_name(stack)
154
- ].compact
155
- )
156
- file_store(payload, key_path, provider.service_for(:storage).directories.get(bucket))
157
- end
158
- end
159
-
160
- end
161
- end
162
- end
@@ -1,141 +0,0 @@
1
- require 'stringio'
2
- require 'knife-cloudformation'
3
-
4
- class Chef
5
- class Knife
6
- # Cloudformation import command
7
- class CloudformationImport < Knife
8
-
9
- include KnifeCloudformation::Knife::Base
10
- include KnifeCloudformation::Utils::JSON
11
- include KnifeCloudformation::Utils::ObjectStorage
12
- include KnifeCloudformation::Utils::PathSelector
13
-
14
- banner 'knife cloudformation import NEW_STACK_NAME [JSON_EXPORT_FILE]'
15
-
16
- option(:path,
17
- :long => '--import-path PATH',
18
- :description => 'Directory path JSON export files are located',
19
- :proc => lambda{|v|
20
- Chef::Config[:knife][:cloudformation][:import][:path] = File.expand_path(v)
21
- }
22
- )
23
-
24
- option(:bucket,
25
- :long => '--export-bucket BUCKET_NAME',
26
- :description => 'Remote file bucket JSON export files are located',
27
- :proc => lambda{|v| Chef::Config[:knife][:cloudformation][:import][:bucket] = v}
28
- )
29
-
30
- option(:bucket_prefix,
31
- :long => '--bucket-key-prefix PREFIX',
32
- :description => 'Key prefix for file storage in bucket. Can be callable block if defined within configuration',
33
- :proc => lambda{|v| Chef::Config[:knife][:cloudformation][:import][:bucket_prefix] = v}
34
- )
35
-
36
- unless(Chef::Config[:knife][:cloudformation].has_key?(:import))
37
- Chef::Config[:knife][:cloudformation][:import] = Mash.new
38
- end
39
-
40
- # Run the import action
41
- def _run
42
- stack_name, json_file = name_args
43
- ui.info "#{ui.color('Stack Import:', :bold)} #{stack_name}"
44
- unless(json_file)
45
- entries = [].tap do |_entries|
46
- _entries.push('s3') if Chef::Config[:knife][:cloudformation][:import][:bucket]
47
- _entries.push('fs') if Chef::Config[:knife][:cloudformation][:import][:path]
48
- end
49
- if(entries.size > 1)
50
- valid = false
51
- until(valid)
52
- answer = ui.ask_question('Import via file system (fs) or remote bucket (remote)?', :default => 'remote')
53
- valid = true if %w(remote fs).include?(answer)
54
- entries = [answer]
55
- end
56
- elsif(entries.size < 1)
57
- ui.fatal 'No path or bucket set. Unable to perform dynamic lookup!'
58
- exit 1
59
- end
60
- case entries.first
61
- when 'remote'
62
- json_file = remote_discovery
63
- else
64
- json_file = local_discovery
65
- end
66
- end
67
- if(File.exists?(json_file) || json_file.is_a?(IO))
68
- content = json_file.is_a?(IO) ? json_file.read : File.read(json_file)
69
- export = Mash.new(_from_json(content))
70
- begin
71
- creator = Chef::Knife::CloudformationCreate.new
72
- creator.name_args = [stack_name]
73
- Chef::Config[:knife][:cloudformation][:template] = _from_json(export[:stack][:template])
74
- Chef::Config[:knife][:cloudformation][:options] = export[:stack][:options]
75
- ui.info ' - Starting creation of import'
76
- creator.run
77
- ui.info "#{ui.color('Stack Import', :bold)} (#{json_file}): #{ui.color('complete', :green)}"
78
- rescue => e
79
- ui.fatal "Failed to import stack: #{e}"
80
- debug "#{e.class}: #{e}\n#{e.backtrace.join("\n")}"
81
- exit -1
82
- end
83
- else
84
- ui.fatal "Failed to locate JSON export file (#{json_file})"
85
- exit 1
86
- end
87
- end
88
-
89
- # Generate bucket prefix
90
- #
91
- # @return [String, NilClass]
92
- def bucket_prefix
93
- if(prefix = Chef::Config[:knife][:cloudformation][:import][:bucket_prefix])
94
- if(prefix.respond_to?(:cal))
95
- prefix.call
96
- else
97
- prefix.to_s
98
- end
99
- end
100
- end
101
-
102
- # Discover remote file
103
- #
104
- # @return [IO] stack export IO
105
- def remote_discovery
106
- storage = provider.service_for(:storage)
107
- directory = storage.directories.get(
108
- Chef::Config[:knife][:cloudformation][:import][:bucket]
109
- )
110
- file = prompt_for_file(
111
- directory,
112
- :directories_name => 'Collections',
113
- :files_names => 'Exports',
114
- :filter_prefix => bucket_prefix
115
- )
116
- if(file)
117
- remote_file = storage.files.get(file)
118
- StringIO.new(remote_file.body)
119
- end
120
- end
121
-
122
- # Discover remote file
123
- #
124
- # @return [IO] stack export IO
125
- def local_discovery
126
- _, bucket = Chef::Config[:knife][:cloudformation][:import][:path].split('/', 2)
127
- storage = provider.service_for(:storage,
128
- :provider => :local,
129
- :local_root => '/'
130
- )
131
- directory = storage.directories.get(bucket)
132
- prompt_for_file(
133
- directory,
134
- :directories_name => 'Collections',
135
- :files_names => 'Exports'
136
- )
137
- end
138
-
139
- end
140
- end
141
- end