sfn 3.0.32 → 3.1.0

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