sfn 0.5.0 → 1.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3673b71af607d02ac346e2419e0f3ee10d29e7fb
4
- data.tar.gz: 43fd1549a7deed286de8a4c59b9c1b3148700d3e
3
+ metadata.gz: 1e25f48c5fa80038624e08c5e224e62c740be5a8
4
+ data.tar.gz: 2a983906bcdb4b5e02d81c9762b1a033c3b4410e
5
5
  SHA512:
6
- metadata.gz: 3e47a279dc5d53e18674281d2371f0cd72539dd797d2a0e0276813125acc8118ee5087e96278b2bdf960f42147fda969bf1ab855c4b1b5b5f7532a6d85e62e46
7
- data.tar.gz: 28ab80f21ef727f319a7c5c3ac685ced8f33c7fadcc5e680b444812957734c49cf13e30d27e18d0944004d91d5065e79b71c9135dbb6df58d6f7f075fa8bc090
6
+ metadata.gz: 9b41dd94c68cff3b29a487c4d03e86c36d352b4a3403d99787db582d3eaf54e7b2f93418e54398c4dcf8e3083813830cbc24a1a897c70765cce0869c236a5f76
7
+ data.tar.gz: 230a3ef0bcf85adc1c6fd4bb80617dd2b20ab19de4c6b91b43a319fb2265a3cf9d50b7cee4195b119c787ea68528bd47817c75ccddbfb147670c6dd727974103
data/CHANGELOG.md CHANGED
@@ -1,11 +1,22 @@
1
- ## v0.5.0
2
- * Add support for compile time parameters
3
-
4
- ## v0.4.16
5
- * Fix apply stack on update command when template provides new parameters
6
-
7
- ## v0.4.14
8
- * Add constraint on sparkle_formation library to stay below 1.0 release
1
+ ## v1.0.0
2
+
3
+ _NOTE: This release contains breaking changes! Please review the
4
+ changes in this release and test your configuration and
5
+ templates. Changes that may introduce breakage have been
6
+ labeled below._
7
+
8
+ * [BREAKING] Update sparkle_formation constraint to 1.0 versions
9
+ * [BREAKING] Default to "deep" style nesting (previous default: "shallow")
10
+ * [BREAKING] Template loading via SparklePacks in place of direct file loading
11
+ * Provide support for shallow and deep stack nesting styles
12
+ * Add support for customized callbacks
13
+ * Fix `--print-only` behavior on `update` command, add to `validate` command
14
+ * On validation of template with nesting, validate all nested templates _and_ root template
15
+ * Display details of all nested stacks on `describe` command
16
+ * Disable automatic stack inspection on failed `create` / `update` command
17
+ * Add support for SparklePacks
18
+ * Add support for AWS stack policies via optional callback
19
+ * And lots of internal refactors!
9
20
 
10
21
  ## v0.4.12
11
22
  * Fix transient uninitialized constant error for update command
data/README.md CHANGED
@@ -1,8 +1,14 @@
1
+ ![SparkleFormation CLI](img/sfn.jpg)
2
+
1
3
  # SparkleFormation CLI
2
4
 
3
5
  SparkleFormation command line interface for interacting
4
6
  with orchestration APIs.
5
7
 
8
+ ## Extra Documentation
9
+
10
+ * [User Documentation](https://sparkleformation.github.io/sfn/UserDocs)
11
+
6
12
  ## API Compatibility
7
13
 
8
14
  * AWS
@@ -26,7 +32,7 @@ formats:
26
32
  ```json
27
33
  {
28
34
  "credentials": {
29
- AWS_CREDENTIALS
35
+ MIASMA_CREDENTIALS
30
36
  },
31
37
  "options": {
32
38
  "disable_rollback": true
@@ -34,7 +40,7 @@ formats:
34
40
  }
35
41
  ```
36
42
 
37
- ### YAML
43
+ #### YAML
38
44
 
39
45
  ```yaml
40
46
  ---
@@ -44,12 +50,12 @@ formats:
44
50
  :disable_rollback: true
45
51
  ```
46
52
 
47
- ### XML
53
+ #### XML
48
54
 
49
55
  ```xml
50
56
  <configuration>
51
57
  <credentials>
52
- AWS_CREDENTIALS
58
+ MIASMA_CREDENTIALS
53
59
  </credentials>
54
60
  <options>
55
61
  <disable_rollback>
@@ -59,17 +65,86 @@ formats:
59
65
  </configuration>
60
66
  ```
61
67
 
62
- ### Ruby
68
+ #### Ruby
63
69
 
64
70
  ```ruby
65
71
  Configuration.new do
66
72
  credentials do
67
- AWS_CREDENTIALS
73
+ MIASMA_CREDENTIALS
68
74
  end
69
- options.disable_rollback true
75
+ options.on_failure 'nothing'
70
76
  end
71
77
  ```
72
78
 
79
+ ### Configuration Options
80
+
81
+ * `processing` - Enable SparkleFormation processing
82
+ * Valid: Boolean
83
+ * Default: `true`
84
+
85
+ * `apply_nesting` - Style of nested stack processing
86
+ * Valid: `"shallow"`, `"deep"`
87
+ * Default: `"deep"`
88
+
89
+ * `options` - API options for target orchestration API (see miasma)
90
+ * Valid: `Hash`
91
+ * Default: none
92
+
93
+ * `ssh_attempt_users` - List of users to attempt SSH connection on node failure
94
+ * Valid: `Array<String>`
95
+ * Default: none
96
+
97
+ * `identity_file` - Custom SSH identity file to use for connection on node failure
98
+ * Valid: `String`
99
+ * Default: none
100
+
101
+ * `nesting_bucket` - Name of bucket to store nested stack templates
102
+ * Valid: `String`
103
+ * Default: none
104
+
105
+ * `credentials` - API credentials for target orchestration API (see [miasma](https://github.com/miasma-rb/miasma))
106
+ * Valid: `Hash`
107
+ * Default: none
108
+
109
+ * `callbacks` - Callbacks to execute around API calls
110
+ * Valid: `Hash`
111
+ * Default: none
112
+ * `before` - Callbacks to execute before _any_ API call
113
+ * Valid: `Array<String>`
114
+ * Default: none
115
+ * `after` - Callbacks to execute after _any_ API call
116
+ * Valid: `Array<String>`
117
+ * Default: none
118
+ * `before_COMMAND` - Callbacks to execute before specific `COMMAND` API call
119
+ * Valid: `Array<String>`
120
+ * Default: none
121
+ * `after_COMMAND` - Callbacks to execute after specific `COMMAND` API call
122
+ * Valid: `Array<String>`
123
+ * Default: none
124
+ * `template` - Callbacks to execute on template
125
+ * Valid: `Array<String>`
126
+ * Default: none
127
+ * `default` - Callbacks to always execute
128
+ * Valid: `Array<String>`
129
+ * Default: none
130
+ * `require` - List of custom libraries to load
131
+ * Valid: `Array<String>`
132
+ * Default: none
133
+
134
+ * `retry` - Configuration of API request retries
135
+ * Valid: `Hash`
136
+ * Default: none
137
+ * `type` - Type of retry
138
+ * Valid: `"flat"`, `"linear"`, `"exponential"`
139
+ * Default: `"exponential"`
140
+ * `interval` - Base wait interval for retry
141
+ * Valid: `Numeric`
142
+ * Default: 5
143
+ * `max_attempts` - Maximum number of attempts allowed
144
+ * Valid: `Numeric`
145
+ * Default: 20
146
+ * _NOTE_: Set to `nil` for infinite retry
147
+
73
148
  ## Commands
74
149
 
75
150
  * `sfn list`
@@ -81,45 +156,20 @@ end
81
156
  * `sfn inspect`
82
157
  * `sfn validate`
83
158
 
159
+ _NOTE: All commands respond to `--help` and will provide a full list of valid options._
160
+
84
161
  ### `sfn list`
85
162
 
86
163
  Provides listing of current stacks and state of each stack.
87
164
 
88
- #### Supported options
89
-
90
- * `--attribute ATTR` stack attribute to display
91
- * `--status STATUS` match stacks with given status
92
-
93
165
  ### `sfn validate`
94
166
 
95
167
  Validates template with API
96
168
 
97
- #### Supported options
98
-
99
- * `--[no-]processing` enable template processing
100
- * `--file PATH` path to stack template file
101
- * `--translate PROVIDER` translate template to provider
102
- * `--[no-]apply-nesting` apply template nesting logic
103
- * `--nesting-bucket BUCKET` asset store bucket to place nested stack templates
104
-
105
169
  ### `sfn create NAME`
106
170
 
107
171
  Creates a new stack with the provided name (`NAME`).
108
172
 
109
- #### Supported options
110
-
111
- * `--timeout MINUTES` stack creation timeout limit
112
- * `--[no-]rollback` disable rollback on failure
113
- * `--capability CAPABILITY` enable capability within API
114
- * `--notifications ARN` add notification ARN
115
- * `--print-only` print stack template JSON and exit
116
- * `--apply-stack NAME` apply existing stack outputs
117
- * `--[no-]processing` enable template processing
118
- * `--file PATH` path to stack template file
119
- * `--translate PROVIDER` translate template to provider
120
- * `--[no-]apply-nesting` apply template nesting logic
121
- * `--nesting-bucket BUCKET` asset store bucket to place nested stack templates
122
-
123
173
  #### Apply Stacks
124
174
 
125
175
  The `--apply-stack` option allows providing the name of an existing
@@ -193,16 +243,6 @@ resources are supported.
193
243
 
194
244
  Update an existing stack.
195
245
 
196
- #### Supported options
197
-
198
- * `--print-only` print stack template JSON and exit
199
- * `--apply-stack NAME` apply existing stack outputs
200
- * `--[no-]processing` enable template processing
201
- * `--file PATH` path to stack template file
202
- * `--translate PROVIDER` translate template to provider
203
- * `--[no-]apply-nesting` apply template nesting logic
204
- * `--nesting-bucket BUCKET` asset store bucket to place nested stack templates
205
-
206
246
  ### `sfn destroy STACK`
207
247
 
208
248
  Destroy an existing stack.
@@ -232,18 +272,10 @@ stack is "in progress", the polling option will result in
232
272
  polling and displaying new events until the stack reaches a
233
273
  completed state.
234
274
 
235
- #### Supported options
236
-
237
- * `--[no-]poll` poll for new events until completed state reached
238
-
239
275
  ### `sfn describe STACK`
240
276
 
241
277
  Display resources and outputs of give stack.
242
278
 
243
- #### Supported options
244
-
245
- * `--resources` display resources
246
- * `--outputs` display outputs
247
279
 
248
280
  ### `sfn inspect STACK`
249
281
 
@@ -252,7 +284,7 @@ underlying resource modeling objects provided via the
252
284
  [miasma][miasma] library. It also provides extra helpers for
253
285
  running common inspection commands.
254
286
 
255
- ### Supported options
287
+ ### Interesting `inspect` options
256
288
 
257
289
  * `--nodes` list node addresses within stack
258
290
  * `--instance-failure [LOG_FILE]` print log file from failed instance
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ require 'fileutils'
3
+
4
+ unless(system("yardoc"))
5
+ $stderr.puts 'ACK: Failed to create docs!'
6
+ exit -1
7
+ end
8
+
9
+ FileUtils.mkdir_p('doc/UserDocs')
10
+
11
+ Dir.glob('docs/**/*').each do |path|
12
+ next unless File.file?(path)
13
+ content = File.read(path)
14
+ rel_path = path.sub(/.*?docs\//, '')
15
+ new_path = File.join('doc/UserDocs', rel_path)
16
+ user_doc_root = (['..'] * rel_path.scan('/').size).join('/')
17
+ unless(user_doc_root.to_s.empty?)
18
+ user_doc_root << '/'
19
+ end
20
+ FileUtils.mkdir_p(File.dirname(new_path))
21
+ File.open(new_path, 'w') do |file|
22
+ file.puts content
23
+ end
24
+ if(new_path.end_with?('.md'))
25
+ File.open(new_path.sub('.md', '.html'), 'w') do |file|
26
+ file.print "<!DOCTYPE html><html><head><title>SparkleFormation CLI User Documentation</title><script src=\"#{user_doc_root}v/0.3.2/marked.js\"></script><script src=\"#{user_doc_root}v/jquery-2.1.3.min.js\"></script><script src=\"#{user_doc_root}v/loader.js\"></script><script src=\"#{user_doc_root}v/highlight.min.js\"></script><link rel=\"stylesheet\" href=\"#{user_doc_root}v/bootstrap.min.css\"></link><link rel=\"stylesheet\" href=\"#{user_doc_root}v/highlight.min.css\"></link><link rel=\"stylesheet\" href=\"#{user_doc_root}v/finalizer.css\"></link>"
27
+ file.print "</head><body><div class=\"markdown-body\"><div class=\"navbar navbar-top\"><div class=\"navbar-inner\"><div class=\"container\"><div class=\"navbar-brand\"><a href=\"#{user_doc_root}README.html\">SparkleFormation - User documentation</a></div></div></div></div><div class=\"panel panel-default\"><div class=\"panel-body\" id=\"content\"></div></div></div>"
28
+ file.print "</body></html>"
29
+ end
30
+ end
31
+ end
32
+
33
+ File.open('doc/UserDocs/index.html', 'w') do |file|
34
+ file.puts '<html><head><meta http-equiv="refresh" content="0; url=README.html" /></head></html>'
35
+ end
36
+
37
+ puts 'done.'
@@ -0,0 +1,94 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ class Callback
5
+ class StackPolicy < Callback
6
+
7
+ # Policy to apply prior to stack deletion
8
+ DEFENSELESS_POLICY = {
9
+ 'Statement' => [{
10
+ 'Effect' => 'Allow',
11
+ 'Action' => 'Update:*',
12
+ 'Resource' => '*',
13
+ 'Principal' => '*'
14
+ }]
15
+ }
16
+
17
+ # @return [Smash] cached policies
18
+ attr_reader :policies
19
+
20
+ # Overload to init policy cache
21
+ #
22
+ # @return [self]
23
+ def initialize(*args)
24
+ super
25
+ @policies = Smash.new
26
+ end
27
+
28
+ # Submit all cached policies
29
+ #
30
+ # @param args [Hash]
31
+ def submit_policy(args)
32
+ ui.info 'Submitting stack policy documents'
33
+ stack = args[:api_stack]
34
+ ([stack] + stack.nested_stacks).compact.each do |p_stack|
35
+ run_action "Applying stack policy to #{ui.color(p_stack.name, :yellow)}" do
36
+ save_stack_policy(p_stack)
37
+ end
38
+ end
39
+ ui.info 'Stack policy documents successfully submitted!'
40
+ end
41
+ alias_method :after_create, :submit_policy
42
+ alias_method :after_update, :submit_policy
43
+
44
+ # Update all policies to allow resource destruction
45
+ def before_destroy(args)
46
+ ui.warn 'All policies will be disabled for stack destruction!'
47
+ ui.confirm 'Continue with stack destruction'
48
+ stack = args[:api_stack]
49
+ ([stack] + stack.nested_stacks).compact.each do |p_stack|
50
+ @policies[p_stack.name] = DEFENSELESS_POLICY
51
+ run_action "Disabling stack policy for #{ui.color(p_stack.name, :yellow)}" do
52
+ save_stack_policy(p_stack)
53
+ end
54
+ end
55
+ ui.warn "Policy modification for deletion not currently enabled!"
56
+ end
57
+
58
+ # Generate stack policy for stack and cache for the after hook
59
+ # to handle
60
+ #
61
+ # @param info [Hash]
62
+ def template(info)
63
+ if(info[:sparkle_stack])
64
+ @policies.set(info[:stack_name],
65
+ info[:sparkle_stack].generate_policy
66
+ )
67
+ end
68
+ end
69
+
70
+ # Save the cached policy for the given stack
71
+ #
72
+ # @param p_stack [Miasma::Models::Orchestration::Stack]
73
+ # @return [NilClass]
74
+ def save_stack_policy(p_stack)
75
+ result = p_stack.api.request(
76
+ :path => '/',
77
+ :method => :post,
78
+ :form => Smash.new(
79
+ 'Action' => 'SetStackPolicy',
80
+ 'StackName' => p_stack.id,
81
+ 'StackPolicyBody' => MultiJson.dump(
82
+ @policies.fetch(p_stack.id,
83
+ @policies.fetch(p_stack.data[:logical_id],
84
+ @policies[p_stack.name]
85
+ )
86
+ )
87
+ )
88
+ )
89
+ )
90
+ end
91
+
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,48 @@
1
+ require 'sfn'
2
+
3
+ module Sfn
4
+ # Interface for injecting custom functionality
5
+ class Callback
6
+
7
+ autoload :StackPolicy, 'sfn/callback/stack_policy'
8
+
9
+ # @return [Bogo::Ui]
10
+ attr_reader :ui
11
+ # @return [Smash]
12
+ attr_reader :config
13
+
14
+ # Create a new callback instance
15
+ #
16
+ # @param [Bogo::Ui]
17
+ # @param [Smash] configuration hash
18
+ # @param [Array<String>] arguments from the CLI
19
+ # @param [Provider] API connection
20
+ #
21
+ # @return [self]
22
+ def initialize(ui, config, arguments, api)
23
+ @ui = ui
24
+ @config = config
25
+ @arguments = arguments
26
+ @api = api
27
+ end
28
+
29
+ # Wrap action within status text
30
+ #
31
+ # @param msg [String] action text
32
+ # @yieldblock action to perform
33
+ # @return [Object] result of yield
34
+ def run_action(msg)
35
+ ui.info("#{msg}... ", :nonewline)
36
+ begin
37
+ result = yield
38
+ ui.puts ui.color('complete!', :green, :bold)
39
+ result
40
+ rescue => e
41
+ ui.puts ui.color('error!', :red, :bold)
42
+ ui.error "Reason - #{e}"
43
+ raise
44
+ end
45
+ end
46
+
47
+ end
48
+ end
@@ -25,7 +25,7 @@ module Sfn
25
25
  end
26
26
 
27
27
  unless(config[:print_only])
28
- ui.info "#{ui.color('SparkleFormation:', :bold)} #{ui.color('create', :green)}"
28
+ ui.info "#{ui.color('Cloud Formation:', :bold)} #{ui.color('create', :green)}"
29
29
  end
30
30
 
31
31
  stack_info = "#{ui.color('Name:', :bold)} #{name}"
@@ -66,32 +66,31 @@ module Sfn
66
66
  end
67
67
 
68
68
  populate_parameters!(stack.template)
69
- stack.parameters = config[:parameters]
69
+ stack.parameters = config_root_parameters
70
70
 
71
71
  stack.template = translate_template(stack.template)
72
- stack.save
73
72
 
74
- end
75
-
76
- if(stack)
77
- if(config[:poll])
78
- poll_stack(stack.name)
79
- stack = provider.connection.stacks.get(name)
80
-
81
- if(stack.reload.state == :create_complete)
82
- ui.info "Stack create complete: #{ui.color('SUCCESS', :green)}"
83
- namespace.const_get(:Describe).new({:outputs => true}, [name]).execute!
73
+ api_action!(:api_stack => stack) do
74
+ stack.save
75
+ if(config[:poll])
76
+ poll_stack(stack.name)
77
+ stack = provider.connection.stacks.get(name)
78
+
79
+ if(stack.reload.state == :create_complete)
80
+ ui.info "Stack create complete: #{ui.color('SUCCESS', :green)}"
81
+ namespace.const_get(:Describe).new({:outputs => true}, [name]).execute!
82
+ else
83
+ ui.fatal "Create of new stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
84
+ raise
85
+ end
84
86
  else
85
- ui.fatal "Create of new stack #{ui.color(name, :bold)}: #{ui.color('FAILED', :red, :bold)}"
86
- ui.info ""
87
- namespace.const_get(:Inspect).new({:instance_failure => true}, [name]).execute!
88
- raise
87
+ ui.warn 'Stack state polling has been disabled.'
88
+ ui.info "Stack creation initialized for #{ui.color(name, :green)}"
89
89
  end
90
- else
91
- ui.warn 'Stack state polling has been disabled.'
92
- ui.info "Stack creation initialized for #{ui.color(name, :green)}"
93
90
  end
91
+
94
92
  end
93
+
95
94
  end
96
95
 
97
96
  end
@@ -15,18 +15,24 @@ module Sfn
15
15
  # Run the stack describe action
16
16
  def execute!
17
17
  stack_name = name_args.last
18
- stack = provider.connection.stacks.get(stack_name)
19
- if(stack)
20
- display = [].tap do |to_display|
21
- AVAILABLE_DISPLAYS.each do |display_option|
22
- if(config[display_option])
23
- to_display << display_option
18
+ root_stack = api_action! do
19
+ provider.connection.stacks.get(stack_name)
20
+ end
21
+ if(root_stack)
22
+ ([root_stack] + root_stack.nested_stacks).compact.each do |stack|
23
+ ui.info "Stack description of #{ui.color(stack.name, :bold)}:"
24
+ display = [].tap do |to_display|
25
+ AVAILABLE_DISPLAYS.each do |display_option|
26
+ if(config[display_option])
27
+ to_display << display_option
28
+ end
24
29
  end
25
30
  end
26
- end
27
- display = AVAILABLE_DISPLAYS.dup if display.empty?
28
- display.each do |display_method|
29
- self.send(display_method, stack)
31
+ display = AVAILABLE_DISPLAYS.dup if display.empty?
32
+ display.each do |display_method|
33
+ self.send(display_method, stack)
34
+ end
35
+ ui.puts
30
36
  end
31
37
  else
32
38
  ui.fatal "Failed to find requested stack: #{ui.color(stack_name, :bold, :red)}"
@@ -29,7 +29,9 @@ module Sfn
29
29
  stack = provider.connection.stacks.get(stack_name)
30
30
  if(stack)
31
31
  nested_stack_cleanup!(stack)
32
- stack.destroy
32
+ api_action!(:api_stack => stack) do
33
+ stack.destroy
34
+ end
33
35
  ui.info "Destroy request complete for stack: #{ui.color(stack_name, :red)}"
34
36
  else
35
37
  ui.warn "Failed to locate requested stack: #{ui.color(stack_name, :bold)}"
@@ -42,13 +44,16 @@ module Sfn
42
44
  ui.error "Stack polling is not available when multiple stack deletion is requested!"
43
45
  end
44
46
  end
45
- ui.info " -> Destroyed SparkleFormation#{plural}: #{ui.color(stacks.join(', '), :bold, :red)}"
47
+ ui.info " -> Destroyed Cloud Formation#{plural}: #{ui.color(stacks.join(', '), :bold, :red)}"
46
48
  end
47
49
 
48
50
  # Cleanup persisted templates if nested stack resources are included
49
51
  def nested_stack_cleanup!(stack)
52
+ stack.nested_stacks.each do |n_stack|
53
+ nested_stack_cleanup!(n_stack)
54
+ end
50
55
  nest_stacks = stack.template.fetch('Resources', {}).values.find_all do |resource|
51
- resource['Type'] == 'AWS::CloudFormation::Stack'
56
+ resource['Type'] == stack.api.class.const_get(:RESOURCE_MAPPING).key(stack.class).to_s
52
57
  end.each do |resource|
53
58
  url = resource['Properties']['TemplateURL']
54
59
  if(url)
@@ -19,32 +19,34 @@ module Sfn
19
19
  @stacks << stack
20
20
  discover_stacks(stack)
21
21
  if(stack)
22
- table = ui.table(self) do
23
- table(:border => false) do
24
- events = get_events
25
- row(:header => true) do
26
- allowed_attributes.each do |attr|
27
- column attr.split('_').map(&:capitalize).join(' '), :width => ((val = events.map{|e| e[attr].to_s.length}.push(attr.length).max + 2) > 70 ? 70 : val)
28
- end
29
- end
30
- events.each do |event|
31
- row do
22
+ api_action!(:api_stack => stack) do
23
+ table = ui.table(self) do
24
+ table(:border => false) do
25
+ events = get_events
26
+ row(:header => true) do
32
27
  allowed_attributes.each do |attr|
33
- column event[attr]
28
+ column attr.split('_').map(&:capitalize).join(' '), :width => ((val = events.map{|e| e[attr].to_s.length}.push(attr.length).max + 2) > 70 ? 70 : val)
29
+ end
30
+ end
31
+ events.each do |event|
32
+ row do
33
+ allowed_attributes.each do |attr|
34
+ column event[attr]
35
+ end
34
36
  end
35
37
  end
36
38
  end
37
- end
38
- end.display
39
- if(config[:poll])
40
- while(stack.in_progress?)
41
- to_wait = config.fetch(:poll_wait_time, 10).to_f
42
- while(to_wait > 0)
43
- sleep(0.1)
44
- to_wait -= 0.1
39
+ end.display
40
+ if(config[:poll])
41
+ while(stack.in_progress?)
42
+ to_wait = config.fetch(:poll_wait_time, 10).to_f
43
+ while(to_wait > 0)
44
+ sleep(0.1)
45
+ to_wait -= 0.1
46
+ end
47
+ stack.reload
48
+ table.display
45
49
  end
46
- stack.reload
47
- table.display
48
50
  end
49
51
  end
50
52
  else
@@ -64,7 +66,7 @@ module Sfn
64
66
  stack.events.all.map do |e|
65
67
  e.attributes.merge(:stack_name => stack.name).to_smash
66
68
  end
67
- end.flatten.compact
69
+ end.flatten.compact.find_all{|e| e[:time] }
68
70
  stack_events.sort do |x,y|
69
71
  Time.parse(x[:time].to_s) <=> Time.parse(y[:time].to_s)
70
72
  end
@@ -13,12 +13,14 @@ module Sfn
13
13
  stack_name = name_args.last
14
14
  stack = provider.connection.stacks.get(stack_name)
15
15
  ui.info "Stack inspection #{ui.color(stack_name, :bold)}:"
16
- outputs = [:attribute, :nodes, :instance_failure].map do |key|
17
- if(config.has_key?(key))
18
- send("display_#{key}", stack)
19
- key
20
- end
21
- end.compact
16
+ outputs = api_action!(:api_stack => stack) do
17
+ [:attribute, :nodes, :instance_failure].map do |key|
18
+ if(config.has_key?(key))
19
+ send("display_#{key}", stack)
20
+ key
21
+ end
22
+ end.compact
23
+ end
22
24
  if(outputs.empty?)
23
25
  ui.info ' Stack dump:'
24
26
  ui.puts MultiJson.dump(