sfn 0.0.1 → 0.3.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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +107 -0
  3. data/LICENSE +13 -0
  4. data/README.md +142 -61
  5. data/bin/sfn +43 -0
  6. data/lib/chef/knife/knife_plugin_seed.rb +117 -0
  7. data/lib/sfn.rb +17 -0
  8. data/lib/sfn/cache.rb +385 -0
  9. data/lib/sfn/command.rb +45 -0
  10. data/lib/sfn/command/create.rb +87 -0
  11. data/lib/sfn/command/describe.rb +87 -0
  12. data/lib/sfn/command/destroy.rb +74 -0
  13. data/lib/sfn/command/events.rb +98 -0
  14. data/lib/sfn/command/export.rb +103 -0
  15. data/lib/sfn/command/import.rb +117 -0
  16. data/lib/sfn/command/inspect.rb +160 -0
  17. data/lib/sfn/command/list.rb +59 -0
  18. data/lib/sfn/command/promote.rb +17 -0
  19. data/lib/sfn/command/update.rb +95 -0
  20. data/lib/sfn/command/validate.rb +34 -0
  21. data/lib/sfn/command_module.rb +9 -0
  22. data/lib/sfn/command_module/base.rb +150 -0
  23. data/lib/sfn/command_module/stack.rb +166 -0
  24. data/lib/sfn/command_module/template.rb +147 -0
  25. data/lib/sfn/config.rb +106 -0
  26. data/lib/sfn/config/create.rb +35 -0
  27. data/lib/sfn/config/describe.rb +19 -0
  28. data/lib/sfn/config/destroy.rb +9 -0
  29. data/lib/sfn/config/events.rb +25 -0
  30. data/lib/sfn/config/export.rb +29 -0
  31. data/lib/sfn/config/import.rb +24 -0
  32. data/lib/sfn/config/inspect.rb +37 -0
  33. data/lib/sfn/config/list.rb +25 -0
  34. data/lib/sfn/config/promote.rb +23 -0
  35. data/lib/sfn/config/update.rb +20 -0
  36. data/lib/sfn/config/validate.rb +49 -0
  37. data/lib/sfn/monkey_patch.rb +8 -0
  38. data/lib/sfn/monkey_patch/stack.rb +200 -0
  39. data/lib/sfn/provider.rb +224 -0
  40. data/lib/sfn/utils.rb +23 -0
  41. data/lib/sfn/utils/debug.rb +31 -0
  42. data/lib/sfn/utils/json.rb +37 -0
  43. data/lib/sfn/utils/object_storage.rb +28 -0
  44. data/lib/sfn/utils/output.rb +79 -0
  45. data/lib/sfn/utils/path_selector.rb +99 -0
  46. data/lib/sfn/utils/ssher.rb +29 -0
  47. data/lib/sfn/utils/stack_exporter.rb +275 -0
  48. data/lib/sfn/utils/stack_parameter_scrubber.rb +37 -0
  49. data/lib/sfn/utils/stack_parameter_validator.rb +124 -0
  50. data/lib/sfn/version.rb +4 -0
  51. data/sfn.gemspec +19 -0
  52. metadata +110 -4
data/lib/sfn/config.rb ADDED
@@ -0,0 +1,106 @@
1
+ require 'sfn'
2
+ require 'bogo-config'
3
+
4
+ module Sfn
5
+
6
+ # Top level configuration
7
+ class Config < Bogo::Config
8
+
9
+ # Only values allowed designating bool type
10
+ BOOLEAN_VALUES = [TrueClass, FalseClass]
11
+
12
+ autoload :Create, 'sfn/config/create'
13
+ autoload :Describe, 'sfn/config/describe'
14
+ autoload :Destroy, 'sfn/config/destroy'
15
+ autoload :Describe, 'sfn/config/describe'
16
+ autoload :Events, 'sfn/config/events'
17
+ autoload :Export, 'sfn/config/export'
18
+ autoload :Import, 'sfn/config/import'
19
+ autoload :Inspect, 'sfn/config/inspect'
20
+ autoload :List, 'sfn/config/list'
21
+ autoload :Promote, 'sfn/config/promote'
22
+ autoload :Update, 'sfn/config/update'
23
+ autoload :Validate, 'sfn/config/validate'
24
+
25
+ attribute(
26
+ :credentials, Smash,
27
+ :coerce => proc{|v|
28
+ v = Smash[v.split(',').map{|x| v.split('=')}] if v.is_a?(String)
29
+ v.to_smash
30
+ },
31
+ :description => 'Provider credentials'
32
+ )
33
+ attribute(
34
+ :ignore_parameters, String,
35
+ :multiple => true,
36
+ :description => 'Parameters to ignore during modifications'
37
+ )
38
+ attribute(
39
+ :interactive_parameters, [TrueClass, FalseClass],
40
+ :default => true,
41
+ :description => 'Prompt for template parameters'
42
+ )
43
+ attribute(
44
+ :poll, [TrueClass, FalseClass],
45
+ :default => true,
46
+ :description => 'Poll stack events on modification actions'
47
+ )
48
+ attribute(
49
+ :defaults, [TrueClass, FalseClass],
50
+ :description => 'Automatically accept default values'
51
+ )
52
+ attribute(
53
+ :yes, [TrueClass, FalseClass],
54
+ :description => 'Automatically accept any requests for confirmation'
55
+ )
56
+ attribute(
57
+ :config, String,
58
+ :description => 'Configuration file path'
59
+ )
60
+
61
+ attribute :create, Create, :coerce => proc{|v| Create.new(v)}
62
+ attribute :update, Update, :coerce => proc{|v| Update.new(v)}
63
+ attribute :destroy, Destroy, :coerce => proc{|v| Destroy.new(v)}
64
+ attribute :events, Events, :coerce => proc{|v| Events.new(v)}
65
+ attribute :export, Export, :coerce => proc{|v| Export.new(v)}
66
+ attribute :import, Import, :coerce => proc{|v| Import.new(v)}
67
+ attribute :inspect, Inspect, :coerce => proc{|v| Inpsect.new(v)}
68
+ attribute :describe, Describe, :coerce => proc{|v| Describe.new(v)}
69
+ attribute :list, List, :coerce => proc{|v| List.new(v)}
70
+ attribute :promote, Promote, :coerce => proc{|v| Promote.new(v)}
71
+ attribute :validate, Validate, :coerce => proc{|v| Validate.new(v)}
72
+
73
+ # Provide all options for config class (includes global configs)
74
+ #
75
+ # @param klass [Class]
76
+ # @return [Smash]
77
+ def self.options_for(klass)
78
+ shorts = ['h'] # always reserve `-h` for help
79
+ _options_for(self, shorts).merge(
80
+ _options_for(klass, shorts)
81
+ )
82
+ end
83
+
84
+ # Provide options for config class
85
+ #
86
+ # @param klass [Class]
87
+ # @param shorts [Array<String>]
88
+ # @return [Smash]
89
+ def self._options_for(klass, shorts)
90
+ Smash[
91
+ klass.attributes.sort_by(&:first).map do |name, info|
92
+ next unless info[:description]
93
+ short = name.chars.zip(name.chars.map(&:upcase)).flatten.detect do |c|
94
+ !shorts.include?(c)
95
+ end
96
+ shorts << short
97
+ info[:short] = short
98
+ info[:long] = name.tr('_', '-')
99
+ info[:boolean] = [info[:type]].compact.flatten.all?{|t| BOOLEAN_VALUES.include?(t)}
100
+ [name, info]
101
+ end.compact
102
+ ]
103
+ end
104
+
105
+ end
106
+ end
@@ -0,0 +1,35 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Create command configuration
6
+ class Create < Update
7
+
8
+ attribute(
9
+ :timeout, Integer,
10
+ :coerce => proc{|v| v.to_i},
11
+ :description => 'Seconds to wait for stack to complete'
12
+ )
13
+ attribute(
14
+ :rollback, [TrueClass, FalseClass],
15
+ :description => 'Rollback stack on failure'
16
+ )
17
+ attribute(
18
+ :capabilities, String,
19
+ :multiple => true,
20
+ :description => 'Capabilities to allow the stack'
21
+ )
22
+ attribute(
23
+ :options, String,
24
+ :multiple => true,
25
+ :description => 'Extra options to apply to the API call'
26
+ )
27
+ attribute(
28
+ :notifications, String,
29
+ :multiple => true,
30
+ :description => 'Notification endpoints for stack events'
31
+ )
32
+
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ class Describe < Bogo::Config
6
+
7
+ attribute(
8
+ :resources, [TrueClass, FalseClass],
9
+ :description => 'Display stack resource list'
10
+ )
11
+
12
+ attribute(
13
+ :outputs, [TrueClass, FalseClass],
14
+ :description => 'Display stack outputs'
15
+ )
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,9 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Destroy command configuration
6
+ class Destroy < Bogo::Config
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Events command configuration
6
+ class Events < Bogo::Config
7
+
8
+ attribute(
9
+ :attribute, String,
10
+ :multiple => true,
11
+ :description => 'Event attribute to display'
12
+ )
13
+ attribute(
14
+ :poll_delay, Integer,
15
+ :default => 20,
16
+ :description => 'Seconds to pause between each event poll'
17
+ )
18
+ attribute(
19
+ :all_attributes, [TrueClass, FalseClass],
20
+ :description => 'Display all event attributes'
21
+ )
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+
6
+ # Export command configuration
7
+ class Export < Bogo::Config
8
+
9
+ attribute(
10
+ :name, String,
11
+ :description => 'Export file base name'
12
+ )
13
+ attribute(
14
+ :path, String,
15
+ :description => 'Local path prefix for dump file'
16
+ )
17
+ attribute(
18
+ :bucket, String,
19
+ :description => 'Remote storage bucket'
20
+ )
21
+ attribute(
22
+ :bucket_prefix, String,
23
+ :description => 'Remote key prefix within bucket for dump file'
24
+ )
25
+
26
+ end
27
+
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+
6
+ # Import command configuration
7
+ class Import < Bogo::Config
8
+
9
+ attribute(
10
+ :path, String,
11
+ :description => 'Directory path JSON export files are located'
12
+ )
13
+ attribute(
14
+ :bucket, String,
15
+ :description => 'Remote storage bucket JSON export files are located'
16
+ )
17
+ attribute(
18
+ :bucket_prefix, String,
19
+ :description => 'Remote key prefix within bucket for dump file'
20
+ )
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Inspect command configuration
6
+ class Inspect < Bogo::Config
7
+
8
+ attribute(
9
+ :attribute, String,
10
+ :multiple => true,
11
+ :description => 'Dot delimited attribute to view'
12
+ )
13
+ attribute(
14
+ :nodes, [TrueClass, FalseClass],
15
+ :description => 'Locate all instances and display addresses'
16
+ )
17
+ attribute(
18
+ :instance_failure, String,
19
+ :description => 'Display log file error from failed not if possible',
20
+ )
21
+ attribute(
22
+ :failure_log_path, String,
23
+ :description => 'Path to remote log file for display on failure',
24
+ :default => '/var/log/chef/client.log'
25
+ )
26
+ attribute(
27
+ :identity_file, String,
28
+ :description => 'SSH identity file for authentication'
29
+ )
30
+ attribute(
31
+ :ssh_user, String,
32
+ :description => 'SSH username for inspection connect'
33
+ )
34
+
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,25 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # List command configuration
6
+ class List < Bogo::Config
7
+
8
+ attribute(
9
+ :attribute, String,
10
+ :multiple => true,
11
+ :description => 'Attribute of stack to print'
12
+ )
13
+ attribute(
14
+ :all_attributes, [TrueClass, FalseClass],
15
+ :description => 'Print all available attributes'
16
+ )
17
+ attribute(
18
+ :status, String,
19
+ :multiple => true,
20
+ :description => 'Match stacks with given status. Use "none" to disable.'
21
+ )
22
+
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Promote command configuration
6
+ class Promote < Bogo::Config
7
+
8
+ attribute(
9
+ :accounts, String,
10
+ :description => 'JSON accounts file path'
11
+ )
12
+ attribute(
13
+ :bucket, String,
14
+ :description => 'Bucket name containing the exports'
15
+ )
16
+ attribute(
17
+ :bucket_prefix, String,
18
+ :description => 'Key prefix within remote bucket'
19
+ )
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,20 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Update command configuration
6
+ class Update < Validate
7
+
8
+ attribute(
9
+ :print_only, [TrueClass, FalseClass],
10
+ :description => 'Print the resulting stack template'
11
+ )
12
+ attribute(
13
+ :apply_stack, String,
14
+ :multiple => true,
15
+ :description => 'Apply outputs from stack to input parameters'
16
+ )
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,49 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Config
5
+ # Validate command configuration
6
+ class Validate < Bogo::Config
7
+
8
+ attribute(
9
+ :processing, [TrueClass, FalseClass],
10
+ :description => 'Call the unicorns and explode the glitter bombs'
11
+ )
12
+ attribute(
13
+ :file, String,
14
+ :description => 'Path to template file'
15
+ )
16
+ attribute(
17
+ :file_path_prompt, [TrueClass, FalseClass],
18
+ :default => true,
19
+ :description => 'Enable interactive prompt for template path discovery'
20
+ )
21
+ attribute(
22
+ :base_directory, String,
23
+ :description => 'Path to root of of templates directory'
24
+ )
25
+ attribute(
26
+ :no_base_directory, [TrueClass, FalseClass],
27
+ :description => 'Unset any value used for the template root directory path'
28
+ )
29
+ attribute(
30
+ :translate, String,
31
+ :description => 'Translate generated template to given prodiver'
32
+ )
33
+ attribute(
34
+ :translate_chunk, Integer,
35
+ :description => 'Chunk length for serialization'
36
+ )
37
+ attribute(
38
+ :apply_nesting, [TrueClass, FalseClass],
39
+ :default => true,
40
+ :description => 'Apply stack nesting'
41
+ )
42
+ attribute(
43
+ :nesting_bucket, String,
44
+ :description => 'Bucket to use for storing nested stack templates'
45
+ )
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,8 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ # Container for monkey patches
5
+ module MonkeyPatch
6
+ autoload :Stack, 'sfn/monkey_patch/stack'
7
+ end
8
+ end
@@ -0,0 +1,200 @@
1
+ require 'base64'
2
+ require 'sfn'
3
+
4
+ module Sfn
5
+ module MonkeyPatch
6
+
7
+ # Expand stack model functionality
8
+ module Stack
9
+
10
+ include Bogo::AnimalStrings
11
+
12
+ ## Status helpers
13
+
14
+ # Check for state suffix
15
+ #
16
+ # @param args [String, Symbol] state suffix to check for (multiple allowed)
17
+ # @return [TrueClass, FalseClass] true if any matches found in argument list
18
+ def status_ends_with?(*args)
19
+ stat = status.to_s.downcase
20
+ !!args.map(&:to_s).map(&:downcase).detect do |suffix|
21
+ stat.end_with?(suffix)
22
+ end
23
+ end
24
+
25
+ # Check for state prefix
26
+ #
27
+ # @param args [String, Symbol] state prefix to check for (multiple allowed)
28
+ # @return [TrueClass, FalseClass] true if any matches found in argument list
29
+ def status_starts_with?(*args)
30
+ stat = status.to_s.downcase
31
+ !!args.map(&:to_s).map(&:downcase).detect do |prefix|
32
+ stat.start_with?(prefix)
33
+ end
34
+ end
35
+
36
+ # Check for state inclusion
37
+ #
38
+ # @param args [String, Symbol] state string to check for (multiple allowed)
39
+ # @return [TrueClass, FalseClass] true if any matches found in argument list
40
+ def status_includes?(*args)
41
+ stat = status.to_s.downcase
42
+ !!args.map(&:to_s).map(&:downcase).detect do |string|
43
+ stat.include?(string)
44
+ end
45
+ end
46
+
47
+ # @return [TrueClass, FalseClass] stack is in progress
48
+ def in_progress?
49
+ status_ends_with?(:in_progress)
50
+ end
51
+
52
+ # @return [TrueClass, FalseClass] stack is in complete state
53
+ def complete?
54
+ status_ends_with?(:complete, :failed)
55
+ end
56
+
57
+ # @return [TrueClass, FalseClass] stack is failed state
58
+ def failed?
59
+ status_ends_with?(:failed) ||
60
+ (status_includes?(:rollback) && status_ends_with?(:complete))
61
+ end
62
+
63
+ # @return [TrueClass, FalseClass] stack is in success state
64
+ def success?
65
+ !failed? && complete?
66
+ end
67
+
68
+ # @return [TrueClass, FalseClass] stack is creating
69
+ def creating?
70
+ in_progress? && status_starts_with?(:create)
71
+ end
72
+
73
+ # @return [TrueClass, FalseClass] stack is deleting
74
+ def deleting?
75
+ in_progress? && status_starts_with?(:delete)
76
+ end
77
+
78
+ # @return [TrueClass, FalseClass] stack is updating
79
+ def updating?
80
+ in_progress? && status_starts_with?(:update)
81
+ end
82
+
83
+ # @return [TrueClass, FalseClass] stack is rolling back
84
+ def rollbacking?
85
+ in_progress? && status_starts_with?(:rollback)
86
+ end
87
+
88
+ # @return [String] action currently being performed
89
+ def performing
90
+ if(in_progress?)
91
+ status.to_s.downcase.split('_').first.to_sym
92
+ end
93
+ end
94
+
95
+ ### Color coders
96
+
97
+ # @return [TrueClass, FalseClass] stack is in red state
98
+ def red?
99
+ failed? || deleting?
100
+ end
101
+
102
+ # @return [TrueClass, FalseClass] stack is in green state
103
+ def green?
104
+ success?
105
+ end
106
+
107
+ # @return [TrueClass, FalseClass] stack is in yellow state
108
+ def yellow?
109
+ !red? && !green?
110
+ end
111
+
112
+ # Provides color of stack state. Red is an error state, yellow
113
+ # is a warning state and green is a success state
114
+ #
115
+ # @return [Symbol] color of state (:red, :yellow, :green)
116
+ def color_state
117
+ red? ? :red : green? ? :green : :yellow
118
+ end
119
+
120
+ # Provides text of stack state. Danger is an error state, warning
121
+ # is a warning state and success is a success state
122
+ #
123
+ # @return [Symbol] color of state (:danger, :warning, :success)
124
+ def text_state
125
+ red? ? :danger : green? ? :success : :warning
126
+ end
127
+
128
+ # @return [String] URL safe encoded stack id
129
+ def encoded_id
130
+ Base64.urlsafe_encode64(id)
131
+ end
132
+
133
+ # Whole number representation of current completion
134
+ #
135
+ # @param min [Integer] lowest allowed return value (defaults 5)
136
+ # @return [Integer] percent complete (0..100)
137
+ def percent_complete(min = 5)
138
+ if(in_progress?)
139
+ total_resources = load_template.fetch('Resources', []).size
140
+ total_complete = resources.all.find_all do |resource|
141
+ resource.resource_status.downcase.end_with?('complete')
142
+ end.size
143
+ result = ((total_complete.to_f / total_resources) * 100).to_i
144
+ result > min.to_i ? result : min
145
+ else
146
+ 100
147
+ end
148
+ end
149
+
150
+ # Apply stack outputs to current stack parameters
151
+ #
152
+ # @param remote_stack [Miasma::Orchestration::Stack]
153
+ # @return [self]
154
+ # @note setting `DisableApply` within parameter hash will
155
+ # prevent parameters being overridden
156
+ def apply_stack(remote_stack, ignore_params=nil)
157
+ default_key = 'Default'
158
+ stack_parameters = template['Parameters']
159
+ valid_parameters = Smash[
160
+ stack_parameters.map do |key, val|
161
+ unless(val['DisableApply'])
162
+ [snake(key), key]
163
+ end
164
+ end.compact
165
+ ]
166
+ unless(ignore_params)
167
+ if(defined?(Chef::Config))
168
+ ignore_params = Chef::Config[:knife].to_smash.get(:cloudformation, :ignore_parameters)
169
+ end
170
+ end
171
+ if(ignore_params)
172
+ valid_parameters = Hash[
173
+ valid_parameters.map do |snake_param, camel_param|
174
+ unless(ignore_params.include?(camel_param))
175
+ [snake_param, camel_param]
176
+ end
177
+ end.compact
178
+ ]
179
+ end
180
+ if(persisted?)
181
+ remote_stack.outputs.each do |output|
182
+ if(param_key = valid_parameters[snake(output.key)])
183
+ parameters.merge!(param_key => output.value)
184
+ end
185
+ end
186
+ else
187
+ remote_stack.outputs.each do |output|
188
+ if(param_key = valid_parameters[snake(output.key)])
189
+ stack_parameters[param_key][default_key] = output.value
190
+ end
191
+ end
192
+ end
193
+ end
194
+
195
+ end
196
+ end
197
+ end
198
+
199
+ # Infect miasma
200
+ Miasma::Models::Orchestration::Stack.send(:include, Sfn::MonkeyPatch::Stack)