sfn 3.0.32 → 3.1.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.
@@ -2,12 +2,12 @@
2
2
  title: "CLI Commands"
3
3
  weight: 4
4
4
  anchors:
5
- - title: "Lifecycle commands"
6
- url: "#lifecycle-commands"
7
- - title: "Inspection and Information commands"
8
- url: "#inspection-and-information-commands"
9
- - title: "Configuration commands"
10
- url: "#configuration-commands"
5
+ - title: "Lifecycle"
6
+ url: "#lifecycle"
7
+ - title: "Informational"
8
+ url: "#informational"
9
+ - title: "Configuration"
10
+ url: "#configuration"
11
11
  ---
12
12
 
13
13
  ## SparkleFormation CLI commands
@@ -16,7 +16,7 @@ The `sfn` command provides a collection of subcommands to handle stack
16
16
  lifecycles as well as subcommands to aid in inspection of existing
17
17
  stacks.
18
18
 
19
- ### Lifecycle commands
19
+ ### Lifecycle
20
20
 
21
21
  #### Stack create
22
22
 
@@ -172,6 +172,52 @@ Example of stack update:
172
172
 
173
173
  ![stack update](./images/d_update.png)
174
174
 
175
+ #### Stack plan
176
+
177
+ The `plan` command allows for a stack to be created or updated via `sfn`. It uses the
178
+ planning functionality of the provider to include planning information prior to
179
+ application of the creation/modifications. The `plan` command is currently only
180
+ supported with the AWS provider. When using the `update` command, the plan generation
181
+ uses the internal planner with `sfn` and supports showing resource for nested stacks.
182
+ This is currently not supported with the remote planning API.
183
+
184
+ Available options for the `plan` command are similar to the `create` and `update`
185
+ commands. Additionally, available plans can be listed for a given stack:
186
+
187
+ ~~~
188
+ $ sfn plan my-stack --list
189
+ ~~~
190
+
191
+ By default the plan will be created with a default name (`miasma-changeset-STACKNAME`). A
192
+ custom name can be used for the plan:
193
+
194
+ ~~~
195
+ $ sfn plan my-stack --file my_template --plan-name custom-plan-name
196
+ ~~~
197
+
198
+ It is also possible to apply an existing plan for a stack by providing the plan name:
199
+
200
+ ~~~
201
+ $ sfn plan my-stack --plan-name existing-plan-name
202
+ ~~~
203
+
204
+ #### Stack realize
205
+
206
+ The `realize` command loads an existing plan for a given stack and executes the plan. This
207
+ is useful to allow generating plans and then executing a plan at a later time. For example
208
+ a stack plan can be first generated and not applied:
209
+
210
+ ~~~
211
+ $ sfn plan my-stack --file my_template --plan-only
212
+ ~~~
213
+
214
+ This generates the plan and displays the result but does not apply the changes. The plan
215
+ can then be loaded and applied using the `realize` command:
216
+
217
+ ~~~
218
+ $ sfn realize my-stack
219
+ ~~~
220
+
175
221
  #### Stack destroy
176
222
 
177
223
  Existing stacks can be destroyed via `sfn`:
@@ -209,7 +255,7 @@ Example of stack destroy:
209
255
 
210
256
  ![stack destroy](./images/d_destroy.png)
211
257
 
212
- ### Inspection and Information commands
258
+ ### Informational
213
259
 
214
260
  #### Stack list
215
261
 
@@ -386,7 +432,7 @@ is installed on the local system, an image can be generated directly:
386
432
  $ sfn graph --file my_template --graph-type png
387
433
  ~~~
388
434
 
389
- ### Configuration commands
435
+ ### Configuration
390
436
 
391
437
  To aid setup and configuration, `sfn` provides a configuration helper:
392
438
 
@@ -399,4 +445,4 @@ one automatically generated for them:
399
445
 
400
446
  ~~~
401
447
  $ sfn conf --generate
402
- ~~~
448
+ ~~~
@@ -76,6 +76,7 @@ in loaded [SparklePacks][sparkle_packs].
76
76
  Available template related commands:
77
77
 
78
78
  * `sfn create`
79
+ * `sfn plan`
79
80
  * `sfn update`
80
81
  * `sfn validate`
81
82
 
data/lib/sfn.rb CHANGED
@@ -6,6 +6,7 @@ require "sparkle_formation"
6
6
  module Sfn
7
7
  autoload :ApiProvider, "sfn/api_provider"
8
8
  autoload :Callback, "sfn/callback"
9
+ autoload :Error, "sfn/error"
9
10
  autoload :Provider, "sfn/provider"
10
11
  autoload :Cache, "sfn/cache"
11
12
  autoload :Config, "sfn/config"
@@ -21,6 +21,7 @@ module Sfn
21
21
  autoload :Plan, "sfn/command/plan"
22
22
  autoload :Print, "sfn/command/print"
23
23
  autoload :Promote, "sfn/command/promote"
24
+ autoload :Realize, "sfn/command/realize"
24
25
  autoload :Update, "sfn/command/update"
25
26
  autoload :Validate, "sfn/command/validate"
26
27
 
@@ -23,7 +23,14 @@ module Sfn
23
23
  if entries.size > 1
24
24
  valid = false
25
25
  until valid
26
- answer = ui.ask_question("Import via file system (fs) or remote bucket (remote)?", :default => "remote")
26
+ if config[:interactive_parameters]
27
+ answer = ui.ask_question(
28
+ "Import via file system (fs) or remote bucket (remote)?",
29
+ :default => "remote",
30
+ )
31
+ else
32
+ answer = "remote"
33
+ end
27
34
  valid = true if %w(remote fs).include?(answer)
28
35
  entries = [answer]
29
36
  end
@@ -36,7 +36,12 @@ module Sfn
36
36
  if stack && stack.plan
37
37
  ui.warn "Found existing plan for this stack"
38
38
  begin
39
- ui.confirm "Destroy existing plan?"
39
+ if config[:load_existing]
40
+ raise Bogo::Ui::ConfirmationDeclined
41
+ end
42
+ if config[:load_existing].nil?
43
+ ui.confirm "Destroy existing plan"
44
+ end
40
45
  ui.info "Destroying existing plan to generate new plan"
41
46
  stack.plan.destroy
42
47
  rescue Bogo::Ui::ConfirmationDeclined
@@ -127,56 +132,8 @@ module Sfn
127
132
  end
128
133
 
129
134
  plan = stack.plan || stack.plan_generate
130
-
131
- begin
132
- display_plan_information(plan)
133
- rescue Bogo::Ui::ConfirmationDeclined
134
- stack.reload
135
- if (stack.template.nil? || stack.template.empty?) && stack.state == :unknown
136
- ui.auto_confirm = false
137
- ui.warn "Stack appears to be empty and should be destroyed"
138
- ui.confirm "Destroy stack?"
139
- stack.destroy
140
- poll_stack(stack.name)
141
- else
142
- ui.confirm "Destroy generated plan?"
143
- plan.destroy
144
- end
145
- raise
146
- end
147
-
148
- if config[:merge_api_options]
149
- config.fetch(:options, Smash.new).each_pair do |key, value|
150
- if stack.respond_to?("#{key}=")
151
- stack.send("#{key}=", value)
152
- end
153
- end
154
- end
155
-
156
- begin
157
- api_action!(:api_stack => stack) do
158
- stack.plan_execute
159
- if config[:poll]
160
- poll_stack(stack.name)
161
- if stack.reload.state == :update_complete || stack.reload.state == :create_complete
162
- ui.info "Stack plan apply complete: #{ui.color("SUCCESS", :green)}"
163
- namespace.const_get(:Describe).new({:outputs => true}, [name]).execute!
164
- else
165
- ui.fatal "Update of stack #{ui.color(name, :bold)}: #{ui.color("FAILED", :red, :bold)}"
166
- raise "Stack did not reach a successful completion state."
167
- end
168
- else
169
- ui.warn "Stack state polling has been disabled."
170
- ui.info "Stack plan apply initialized for #{ui.color(name, :green)}"
171
- end
172
- end
173
- rescue Miasma::Error::ApiError::RequestError => e
174
- if e.message.downcase.include?("no updates")
175
- ui.warn "No changes detected for stack (#{stack.name})"
176
- else
177
- raise
178
- end
179
- end
135
+ namespace.const_get(:Realize).
136
+ new(config, [name]).execute!
180
137
  end
181
138
 
182
139
  # Display plan list in table form
@@ -0,0 +1,82 @@
1
+ require "sfn"
2
+
3
+ module Sfn
4
+ class Command
5
+ # Realize command
6
+ class Realize < Command
7
+ include Sfn::CommandModule::Base
8
+ include Sfn::CommandModule::Planning
9
+
10
+ # Run the stack realize command
11
+ def execute!
12
+ name_required!
13
+ name = name_args.first
14
+
15
+ stack_info = "#{ui.color("Name:", :bold)} #{name}"
16
+ begin
17
+ stack = provider.stacks.get(name)
18
+ rescue Miasma::Error::ApiError::RequestError
19
+ raise Error::StackNotFound,
20
+ "Failed to locate stack: #{name}"
21
+ end
22
+
23
+ if config[:plan_name]
24
+ ui.debug "Setting custom plan name - #{config[:plan_name]}"
25
+ # ensure custom attribute is dirty so we can modify
26
+ stack.custom = stack.custom.dup
27
+ stack.custom[:plan_name] = config[:plan_name]
28
+ end
29
+
30
+ ui.info " -> Loading plan information..."
31
+
32
+ plan = stack.plan
33
+ if plan.nil?
34
+ raise Error::StackPlanNotFound,
35
+ "Failed to locate plan for stack `#{name}`"
36
+ end
37
+
38
+ display_plan_information(plan)
39
+
40
+ return if config[:plan_only]
41
+
42
+ if config[:merge_api_options]
43
+ config.fetch(:options, Smash.new).each_pair do |key, value|
44
+ if stack.respond_to?("#{key}=")
45
+ stack.send("#{key}=", value)
46
+ end
47
+ end
48
+ end
49
+
50
+ begin
51
+ api_action!(:api_stack => stack) do
52
+ stack.plan_execute
53
+ if config[:poll]
54
+ poll_stack(stack.name)
55
+ if [:update_complete, :create_complete].
56
+ include?(stack.reload.state)
57
+ ui.info "Stack plan apply complete: " \
58
+ "#{ui.color("SUCCESS", :green)}"
59
+ namespace.const_get(:Describe).
60
+ new({:outputs => true}, [name]).execute!
61
+ else
62
+ ui.fatal "Update of stack #{ui.color(name, :bold)}: " \
63
+ "#{ui.color("FAILED", :red, :bold)}"
64
+ raise Error::StackStateIncomplete
65
+ end
66
+ else
67
+ ui.warn "Stack state polling has been disabled."
68
+ ui.info "Stack plan apply initialized for " \
69
+ "#{ui.color(name, :green)}"
70
+ end
71
+ end
72
+ rescue Miasma::Error::ApiError::RequestError => e
73
+ if e.message.downcase.include?("no updates")
74
+ ui.warn "No changes detected for stack (#{stack.name})"
75
+ else
76
+ raise
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -112,7 +112,7 @@ module Sfn
112
112
  end
113
113
  if config[:plan_only]
114
114
  ui.info "Plan only mode requested. Exiting."
115
- exit 0
115
+ return
116
116
  end
117
117
  end
118
118
  stack.parameters = config_root_parameters
@@ -27,8 +27,12 @@ module Sfn
27
27
  unless print_plan_result(result, [result.name])
28
28
  ui.info "No resources life cycle changes detected in this update!"
29
29
  end
30
- cmd = self.class.to_s.split("::").last.downcase
31
- ui.confirm "Apply this stack #{cmd}?" unless config[:plan_only]
30
+ if config[:plan_apply]
31
+ return ui.info "Realizing this stack plan..."
32
+ elsif config[:plan_only]
33
+ return
34
+ end
35
+ ui.confirm "Realize this stack plan?"
32
36
  end
33
37
 
34
38
  # Print plan information to the UI
@@ -340,9 +340,17 @@ module Sfn
340
340
  end
341
341
  if current_value && current_value.to_s != stack_value.to_s
342
342
  if config[:parameter_validation] == "default"
343
- ui.warn "Nested stack has been altered directly! This update may cause unexpected modifications!"
344
- ui.warn "Stack name: #{c_stack.name}. Parameter: #{p_key}. Current value: #{stack_value}. Expected value: #{current_value} (via: #{c_value.inspect})"
345
- answer = ui.ask_question("Use current value or expected value for #{p_key} [current/expected]?", :valid => ["current", "expected"])
343
+ ui.warn "Nested stack has been altered directly! " \
344
+ "This update may cause unexpected modifications!"
345
+ ui.warn "Stack name: #{c_stack.name}. Parameter: #{p_key}. " \
346
+ "Current value: #{stack_value}. Expected value: #{current_value} " \
347
+ "(via: #{c_value.inspect})"
348
+ if config[:interactive_parameters]
349
+ answer = ui.ask_question("Use current value or expected value for #{p_key} " \
350
+ "[current/expected]?", :valid => ["current", "expected"])
351
+ else
352
+ raise Error::InteractionDisabled
353
+ end
346
354
  else
347
355
  answer = config[:parameter_validation]
348
356
  end
@@ -588,7 +588,11 @@ module Sfn
588
588
  ui.puts "#{output.join("\n")}\n"
589
589
  response = nil
590
590
  until valid[response]
591
- response = ui.ask_question("Enter selection").to_i
591
+ if config[:interactive_parameters]
592
+ response = ui.ask_question("Enter selection").to_i
593
+ else
594
+ raise Error::InteractionDisabled
595
+ end
592
596
  end
593
597
  entry = valid[response]
594
598
  if entry[:type] == :collection
@@ -38,7 +38,9 @@ module Sfn
38
38
  end
39
39
 
40
40
  # Only values allowed designating bool type
41
- BOOLEAN = BOOLEAN_VALUES = [TrueClass, FalseClass]
41
+ BOOLEAN = BOOLEAN_VALUES = [TrueClass, FalseClass].freeze
42
+ # Boolean type with nil included
43
+ TRISTATE_BOOLEAN = (BOOLEAN + [NilClass]).freeze
42
44
 
43
45
  autoload :Conf, "sfn/config/conf"
44
46
  autoload :Create, "sfn/config/create"
@@ -57,6 +59,7 @@ module Sfn
57
59
  autoload :Plan, "sfn/config/plan"
58
60
  autoload :Print, "sfn/config/print"
59
61
  autoload :Promote, "sfn/config/promote"
62
+ autoload :Realize, "sfn/config/realize"
60
63
  autoload :Update, "sfn/config/update"
61
64
  autoload :Validate, "sfn/config/validate"
62
65
 
@@ -104,6 +107,11 @@ module Sfn
104
107
  :description => "Enable debug output",
105
108
  :short_flag => "u",
106
109
  )
110
+ attribute(
111
+ :log, String,
112
+ :description => "Enable logging with given level",
113
+ :short_flag => "G",
114
+ )
107
115
  attribute(
108
116
  :colors, [TrueClass, FalseClass],
109
117
  :description => "Enable colorized output",
@@ -17,6 +17,24 @@ module Sfn
17
17
  :description => "Custom plan name or ID (not applicable to all providers)",
18
18
  )
19
19
 
20
+ attribute(
21
+ :load_existing, TRISTATE_BOOLEAN,
22
+ :description => "Load existing plan if exists",
23
+ :default => nil,
24
+ )
25
+
26
+ attribute(
27
+ :auto_destroy_stack, TRISTATE_BOOLEAN,
28
+ :description => "Automatically destroy empty stack",
29
+ :default => nil,
30
+ )
31
+
32
+ attribute(
33
+ :auto_destroy_plan, TRISTATE_BOOLEAN,
34
+ :description => "Automatically destroy generated plan",
35
+ :default => nil,
36
+ )
37
+
20
38
  attribute(
21
39
  :list, BOOLEAN,
22
40
  :description => "List all available plans for stack",
@@ -0,0 +1,13 @@
1
+ require "sfn"
2
+
3
+ module Sfn
4
+ class Config
5
+ # Realize command configuration
6
+ class Realize < Config
7
+ attribute(
8
+ :plan_name, String,
9
+ :description => "Custom plan name or ID",
10
+ )
11
+ end
12
+ end
13
+ end
@@ -81,7 +81,14 @@ module Sfn
81
81
  result = Smash.new
82
82
  v.split(",").each do |item_pair|
83
83
  key, value = item_pair.split(/[=:]/, 2)
84
- result[key] = value
84
+ if !result[key]
85
+ result[key] = value
86
+ next
87
+ end
88
+ if !result.is_a?(Array)
89
+ result[key] = [result[key]]
90
+ end
91
+ result[key] << value
85
92
  end
86
93
  result
87
94
  when Hash