sfn 0.0.1 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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)