moonshot 0.7.7 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/moonshot +11 -0
  3. data/lib/default/Moonfile.rb +0 -0
  4. data/lib/moonshot.rb +33 -6
  5. data/lib/moonshot/always_use_default_source.rb +17 -0
  6. data/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +140 -3
  7. data/lib/moonshot/ask_user_source.rb +38 -0
  8. data/lib/moonshot/build_mechanism/github_release.rb +1 -1
  9. data/lib/moonshot/build_mechanism/script.rb +1 -1
  10. data/lib/moonshot/command.rb +64 -0
  11. data/lib/moonshot/command_line.rb +150 -0
  12. data/lib/moonshot/commands/build.rb +12 -0
  13. data/lib/moonshot/commands/console.rb +19 -0
  14. data/lib/moonshot/commands/create.rb +37 -0
  15. data/lib/moonshot/commands/delete.rb +12 -0
  16. data/lib/moonshot/commands/deploy.rb +12 -0
  17. data/lib/moonshot/commands/doctor.rb +12 -0
  18. data/lib/moonshot/commands/list.rb +16 -0
  19. data/lib/moonshot/commands/new.rb +99 -0
  20. data/lib/moonshot/commands/parameter_arguments.rb +27 -0
  21. data/lib/moonshot/commands/push.rb +12 -0
  22. data/lib/moonshot/commands/ssh.rb +12 -0
  23. data/lib/moonshot/commands/status.rb +12 -0
  24. data/lib/moonshot/commands/update.rb +29 -0
  25. data/lib/moonshot/commands/version.rb +12 -0
  26. data/lib/moonshot/config.rb +0 -0
  27. data/lib/moonshot/controller.rb +106 -42
  28. data/lib/moonshot/controller_config.rb +31 -13
  29. data/lib/moonshot/deployment_mechanism/code_deploy.rb +17 -7
  30. data/lib/moonshot/json_stack_template.rb +17 -0
  31. data/lib/moonshot/parameter_collection.rb +50 -0
  32. data/lib/moonshot/parent_stack_parameter_loader.rb +51 -0
  33. data/lib/moonshot/resources.rb +3 -3
  34. data/lib/moonshot/resources_helper.rb +2 -2
  35. data/lib/moonshot/ssh_command.rb +31 -0
  36. data/lib/moonshot/ssh_config.rb +1 -1
  37. data/lib/moonshot/stack.rb +66 -77
  38. data/lib/moonshot/stack_list_printer.rb +21 -0
  39. data/lib/moonshot/stack_lister.rb +16 -6
  40. data/lib/moonshot/stack_parameter.rb +64 -0
  41. data/lib/moonshot/stack_parameter_printer.rb +3 -49
  42. data/lib/moonshot/stack_template.rb +13 -25
  43. data/lib/moonshot/task.rb +10 -0
  44. data/lib/moonshot/tools/asg_rollout.rb +1 -1
  45. data/lib/moonshot/tools/asg_rollout/instance_health.rb +1 -1
  46. data/lib/moonshot/tools/asg_rollout_config.rb +1 -1
  47. data/lib/moonshot/yaml_stack_template.rb +17 -0
  48. metadata +51 -9
  49. data/lib/moonshot/cli.rb +0 -220
  50. data/lib/moonshot/environment_parser.rb +0 -32
@@ -1,35 +1,53 @@
1
1
  require_relative 'default_strategy'
2
2
  require_relative 'ssh_config'
3
+ require_relative 'task'
4
+ require_relative 'ask_user_source'
3
5
 
4
6
  module Moonshot
5
7
  # Holds configuration for Moonshot::Controller
6
8
  class ControllerConfig
9
+ attr_accessor :additional_tag
10
+ attr_accessor :answer_file
7
11
  attr_accessor :app_name
8
12
  attr_accessor :artifact_repository
9
- attr_accessor :auto_prefix_stack
10
13
  attr_accessor :build_mechanism
11
14
  attr_accessor :deployment_mechanism
15
+ attr_accessor :dev_build_name_proc
12
16
  attr_accessor :environment_name
17
+ attr_accessor :interactive
13
18
  attr_accessor :interactive_logger
14
- attr_accessor :logger
19
+ attr_accessor :parameter_overrides
20
+ attr_accessor :parameters
15
21
  attr_accessor :parent_stacks
22
+ attr_accessor :default_parameter_source
23
+ attr_accessor :parameter_sources
16
24
  attr_accessor :plugins
25
+ attr_accessor :project_root
17
26
  attr_accessor :show_all_stack_events
18
- attr_accessor :parameter_strategy
19
- attr_accessor :ssh_config
20
- attr_accessor :ssh_command
21
27
  attr_accessor :ssh_auto_scaling_group_name
28
+ attr_accessor :ssh_command
29
+ attr_accessor :ssh_config
22
30
  attr_accessor :ssh_instance
23
31
 
24
32
  def initialize
25
- @auto_prefix_stack = true
26
- @interactive_logger = InteractiveLogger.new
27
- @logger = Logger.new(STDOUT)
28
- @parent_stacks = []
29
- @plugins = []
30
- @show_all_stack_events = false
31
- @parameter_strategy = Moonshot::ParameterStrategy::DefaultStrategy.new
32
- @ssh_config = SSHConfig.new
33
+ @default_parameter_source = AskUserSource.new
34
+ @interactive = true
35
+ @interactive_logger = InteractiveLogger.new
36
+ @parameter_overrides = {}
37
+ @parameter_sources = {}
38
+ @parameters = ParameterCollection.new
39
+ @parent_stacks = []
40
+ @plugins = []
41
+ @project_root = Dir.pwd
42
+ @show_all_stack_events = false
43
+ @ssh_config = SSHConfig.new
44
+
45
+ @dev_build_name_proc = lambda do |c|
46
+ ['dev', c.app_name, c.environment_name, Time.now.to_i].join('/')
47
+ end
48
+
49
+ user = ENV.fetch('USER', 'default-user').gsub(/\W/, '')
50
+ @environment_name = "dev-#{user}"
33
51
  end
34
52
  end
35
53
  end
@@ -66,6 +66,9 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
66
66
  end
67
67
 
68
68
  def deploy_hook(artifact_repo, version_name)
69
+ success = true
70
+ deployment_id = nil
71
+
69
72
  ilog.start_threaded 'Creating Deployment' do |s|
70
73
  res = cd_client.create_deployment(
71
74
  application_name: app_name,
@@ -76,8 +79,10 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
76
79
  )
77
80
  deployment_id = res.deployment_id
78
81
  s.continue "Created Deployment #{deployment_id.blue}."
79
- wait_for_deployment(deployment_id, s)
82
+ success = wait_for_deployment(deployment_id, s)
80
83
  end
84
+
85
+ handle_deployment_failure(deployment_id) unless success
81
86
  end
82
87
 
83
88
  def post_delete_hook
@@ -159,13 +164,13 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
159
164
  @asg_logical_ids.each do |asg_logical_id|
160
165
  asg_name = stack.physical_id_for(asg_logical_id)
161
166
  unless asg_name
162
- raise Thor::Error, "Could not find #{asg_logical_id} resource in Stack."
167
+ raise "Could not find #{asg_logical_id} resource in Stack."
163
168
  end
164
169
 
165
170
  groups = as_client.describe_auto_scaling_groups(
166
171
  auto_scaling_group_names: [asg_name])
167
172
  if groups.auto_scaling_groups.empty?
168
- raise Thor::Error, "Could not find ASG #{asg_name}."
173
+ raise "Could not find ASG #{asg_name}."
169
174
  end
170
175
 
171
176
  autoscaling_groups.push(groups.auto_scaling_groups.first)
@@ -219,7 +224,7 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
219
224
  def role
220
225
  iam_client.get_role(role_name: @codedeploy_role).role
221
226
  rescue Aws::IAM::Errors::NoSuchEntity
222
- raise Thor::Error, "Did not find an IAM Role: #{@codedeploy_role}"
227
+ raise "Did not find an IAM Role: #{@codedeploy_role}"
223
228
  end
224
229
 
225
230
  def delete_deployment_group
@@ -240,7 +245,7 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
240
245
  end
241
246
 
242
247
  def wait_for_asg_capacity
243
- ilog.start 'Waiting for AutoScaling Group(s) to reach capacity...' do |s|
248
+ ilog.start_threaded 'Waiting for AutoScaling Group(s) to reach capacity...' do |s|
244
249
  loop do
245
250
  asgs_at_capacity = 0
246
251
  asgs = load_auto_scaling_groups
@@ -260,6 +265,8 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
260
265
  end
261
266
 
262
267
  def wait_for_deployment(id, step)
268
+ success = true
269
+
263
270
  loop do
264
271
  sleep 5
265
272
  info = cd_client.get_deployment(deployment_id: id).deployment_info
@@ -273,9 +280,12 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
273
280
  break
274
281
  when 'Failed', 'Stopped'
275
282
  step.failure "Deployment #{id.blue} failed with status '#{status}'"
276
- handle_deployment_failure(id)
283
+ success = false
284
+ break
277
285
  end
278
286
  end
287
+
288
+ success
279
289
  end
280
290
 
281
291
  def handle_deployment_failure(deployment_id)
@@ -298,7 +308,7 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
298
308
  end
299
309
  end
300
310
 
301
- raise Thor::Error, 'Deployment was unsuccessful!'
311
+ raise 'Deployment was unsuccessful!'
302
312
  end
303
313
 
304
314
  def revision_for_artifact_repo(artifact_repo, version_name)
@@ -0,0 +1,17 @@
1
+ require 'json'
2
+ require_relative 'stack_template'
3
+
4
+ module Moonshot
5
+ # Handles JSON formatted AWS template files.
6
+ class JsonStackTemplate < StackTemplate
7
+ def body
8
+ template_body.to_json
9
+ end
10
+
11
+ private
12
+
13
+ def template_body
14
+ @template_body ||= JSON.parse(File.read(@filename))
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,50 @@
1
+ module Moonshot
2
+ # A Rigid Hash-like structure that only accepts manipulation of
3
+ # parameters defined in the Stack template. Anything else results in
4
+ # an exception.
5
+ class ParameterCollection
6
+ extend Forwardable
7
+
8
+ def_delegators :@hash, :key?, :fetch, :[], :keys, :values
9
+
10
+ def self.from_template(template)
11
+ obj = new
12
+
13
+ template.parameters.each do |stack_parameter|
14
+ obj.add(stack_parameter)
15
+ end
16
+
17
+ obj
18
+ end
19
+
20
+ def initialize
21
+ @hash = {}
22
+ end
23
+
24
+ def []=(key, value)
25
+ raise "Invalid StackParameter #{key}!" unless @hash.key?(key)
26
+
27
+ @hash[key].set(value)
28
+ end
29
+
30
+ def add(parameter)
31
+ raise ArgumentError, 'Can only add StackParameters!' unless parameter.is_a?(StackParameter)
32
+
33
+ @hash[parameter.name] = parameter
34
+ end
35
+
36
+ # What parameters are missing for a CreateStack call, where
37
+ # UsePreviousValue has no meaning.
38
+ def missing_for_create
39
+ # If we haven't set a value, and there is no default, we can't
40
+ # create the stack.
41
+ @hash.values.select { |v| !v.set? && !v.default? }
42
+ end
43
+
44
+ def missing_for_update
45
+ # If we don't have a previous value to use, we haven't set a
46
+ # value, and there is no default, we can't update a stack.
47
+ @hash.values.select { |v| !v.set? && !v.default? && !v.use_previous? }
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,51 @@
1
+ module Moonshot
2
+ class ParentStackParameterLoader
3
+ def initialize(config)
4
+ @config = config
5
+ end
6
+
7
+ def load!
8
+ @config.parent_stacks.each do |stack_name|
9
+ count = 0
10
+
11
+ resp = cf_client.describe_stacks(stack_name: stack_name)
12
+ raise "Parent Stack #{stack_name} not found!" unless resp.stacks.size == 1
13
+
14
+ # If there is an input parameters matching a stack output, pass it.
15
+ resp.stacks[0].outputs.each do |output|
16
+ next unless @config.parameters.key?(output.output_key)
17
+ # Our Stack has a Parameter matching this output. Set it's
18
+ # value to the Output's value.
19
+ count += 1
20
+ @config.parameters.fetch(output.output_key).set(output.output_value)
21
+ end
22
+
23
+ puts "Imported #{count} parameters from parent stack #{stack_name.blue}!" if count > 0
24
+ end
25
+ end
26
+
27
+ def load_missing_only!
28
+ @config.parent_stacks.each do |stack_name|
29
+ resp = cf_client.describe_stacks(stack_name: stack_name)
30
+ raise "Parent Stack #{stack_name} not found!" unless resp.stacks.size == 1
31
+
32
+ # If there is an input parameters matching a stack output, pass it.
33
+ resp.stacks[0].outputs.each do |output|
34
+ next unless @config.parameters.key?(output.output_key)
35
+ # Our Stack has a Parameter matching this output. Set it's
36
+ # value to the Output's value, but only if we don't already
37
+ # have a previous value we're using.
38
+ unless @config.parameters.fetch(output.output_key).use_previous?
39
+ @config.parameters.fetch(output.output_key).set(output.output_value)
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def cf_client
48
+ Aws::CloudFormation::Client.new
49
+ end
50
+ end
51
+ end
@@ -2,12 +2,12 @@ module Moonshot
2
2
  # Resources is a dependency container that holds references to instances
3
3
  # provided to a Mechanism (build, deploy, etc.).
4
4
  class Resources
5
- attr_reader :log, :stack, :ilog
5
+ attr_reader :stack, :ilog, :controller
6
6
 
7
- def initialize(log:, stack:, ilog:)
8
- @log = log
7
+ def initialize(stack:, ilog:, controller:)
9
8
  @stack = stack
10
9
  @ilog = ilog
10
+ @controller = controller
11
11
  end
12
12
  end
13
13
  end
@@ -6,9 +6,9 @@ module Moonshot
6
6
 
7
7
  private
8
8
 
9
+ # TODO: Deprecate this interface.
9
10
  def log
10
- raise 'Resources not provided to Mechanism!' unless @resources
11
- @resources.log
11
+ @log ||= Logger.new(STDOUT)
12
12
  end
13
13
 
14
14
  def stack
@@ -0,0 +1,31 @@
1
+ require 'thor'
2
+
3
+ module Moonshot
4
+ # A SSHCommand that is automatically registered with the
5
+ # Moonshot::CommandLine, including options for SSH access.
6
+ class SSHCommand < Moonshot::Command
7
+ def parser
8
+ parser = super
9
+ parser.on('-l', '--user USER', 'User to log into remote machine as') do |v|
10
+ Moonshot.config.ssh_config.ssh_user = v
11
+ end
12
+
13
+ parser.on('-i', '--identity-file FILE', 'SSH Private Key to authenticate with') do |v|
14
+ Moonshot.config.ssh_config.ssh_identity_file = v
15
+ end
16
+
17
+ parser.on('-s', '--instance INSTANCE_ID', 'Specific AWS EC2 ID to connect through') do |v|
18
+ Moonshot.config.ssh_instance = v
19
+ end
20
+
21
+ parser.on('-c', '--command COMMAND', 'Command to execute on the remote host') do |v|
22
+ Moonshot.config.ssh_command = v
23
+ end
24
+
25
+ parser.on('-g', '--auto-scaling-group ASG_NAME',
26
+ 'The logical ID of the ASG to SSH into, required for multiple stacks') do |v|
27
+ Moonshot.config.ssh_auto_scaling_group_name = v
28
+ end
29
+ end
30
+ end
31
+ end
@@ -1,5 +1,5 @@
1
1
  module Moonshot
2
- class SSHConfig # rubocop:disable Documentation
2
+ class SSHConfig
3
3
  attr_accessor :ssh_identity_file
4
4
  attr_accessor :ssh_user
5
5
  end
@@ -1,7 +1,8 @@
1
1
  require_relative 'creds_helper'
2
2
  require_relative 'doctor_helper'
3
3
 
4
- require_relative 'stack_template'
4
+ require_relative 'yaml_stack_template'
5
+ require_relative 'json_stack_template'
5
6
  require_relative 'stack_parameter_printer'
6
7
  require_relative 'stack_output_printer'
7
8
  require_relative 'stack_asg_printer'
@@ -20,19 +21,15 @@ module Moonshot
20
21
  attr_reader :app_name
21
22
  attr_reader :name
22
23
 
23
- # TODO: Refactor more of these parameters into the config object.
24
- def initialize(name, app_name:, log:, ilog:, config: StackConfig.new)
25
- @name = name
26
- @app_name = app_name
27
- @log = log
28
- @ilog = ilog
24
+ def initialize(config)
29
25
  @config = config
26
+ @ilog = config.interactive_logger
27
+ @name = [@config.app_name, @config.environment_name].join('-')
28
+
30
29
  yield @config if block_given?
31
30
  end
32
31
 
33
32
  def create
34
- import_parent_parameters
35
-
36
33
  should_wait = true
37
34
  @ilog.start "Creating #{stack_name}." do |s|
38
35
  if stack_exists?
@@ -48,7 +45,7 @@ module Moonshot
48
45
  end
49
46
 
50
47
  def update
51
- raise Thor::Error, "No stack found #{@name.blue}!" unless stack_exists?
48
+ raise "No stack found #{@name.blue}!" unless stack_exists?
52
49
 
53
50
  should_wait = true
54
51
  @ilog.start "Updating #{stack_name}." do |s|
@@ -61,7 +58,7 @@ module Moonshot
61
58
  end
62
59
 
63
60
  success = should_wait ? wait_for_stack_state(:stack_update_complete, 'updated') : true
64
- raise Thor::Error, 'Failed to update the CloudFormation Stack.' unless success
61
+ raise 'Failed to update the CloudFormation Stack.' unless success
65
62
  success
66
63
  end
67
64
 
@@ -147,31 +144,32 @@ module Moonshot
147
144
  # Return a Hash of the default values defined in the stack template.
148
145
  def default_values
149
146
  h = {}
150
- JSON.parse(template.body).fetch('Parameters', {}).map do |k, v|
151
- h[k] = v['Default']
147
+ template.parameters.each do |p|
148
+ h[p.name] = h.default
152
149
  end
153
150
  h
154
151
  end
155
152
 
156
153
  def template
157
- @template ||= StackTemplate.new(template_file, log: @log)
154
+ @template ||= load_template_file
158
155
  end
159
156
 
160
157
  # @return [String] the path to the template file.
161
158
  def template_file
162
- File.join(Dir.pwd, 'cloud_formation', "#{@app_name}.json")
159
+ json = json_template_path
160
+ yaml = yaml_template_path
161
+
162
+ @template_file ||= Dir[json].first || Dir[yaml].first
163
+
164
+ raise 'CloudFormation template not found at'\
165
+ "#{json} or #{yaml}!" unless @template_file
166
+
167
+ @template_file
163
168
  end
164
169
 
165
170
  # @return [String] the path to the parameters file.
166
171
  def parameters_file
167
- File.join(Dir.pwd, 'cloud_formation', 'parameters', "#{@name}.yml")
168
- end
169
-
170
- def add_parameter_overrides(hash)
171
- new_overrides = hash.merge(overrides)
172
- File.open(parameters_file, 'w') do |f|
173
- YAML.dump(new_overrides, f)
174
- end
172
+ File.join(@config.project_root, 'cloud_formation', 'parameters', "#{@name}.yml")
175
173
  end
176
174
 
177
175
  private
@@ -180,63 +178,46 @@ module Moonshot
180
178
  "CloudFormation Stack #{@name.blue}"
181
179
  end
182
180
 
183
- def load_parameters_file
184
- @ilog.msg "Loading stack parameters file '#{parameters_file}'."
185
- result = stack_parameter_overrides
186
-
187
- if result.empty?
188
- @ilog.msg "No parameters file for #{@name.blue}, using defaults."
189
- return result
190
- end
191
-
192
- @ilog.msg 'Setting stack parameter overrides:'
193
- result.each do |e|
194
- @ilog.msg " #{e[:parameter_key]}: #{e[:parameter_value]}"
195
- end
181
+ def json_template_path
182
+ "#{raw_template_file_name}.json"
196
183
  end
197
184
 
198
- def stack_parameter_overrides
199
- overrides.map do |k, v|
200
- { parameter_key: k, parameter_value: v.to_s }
201
- end
185
+ def yaml_template_path
186
+ "#{raw_template_file_name}.yml"
202
187
  end
203
188
 
204
- def stack_parameters
205
- @stack_parameters ||= JSON.parse(template.body).fetch('Parameters', {}).keys
189
+ # @return [String] the path to the template file without extension.
190
+ def raw_template_file_name
191
+ @raw_template_file_name ||=
192
+ File.join(@config.project_root, 'cloud_formation', @config.app_name)
206
193
  end
207
194
 
208
- def import_parent_parameters
209
- add_parameter_overrides(parent_stack_outputs)
210
- end
211
-
212
- # Return a Hash of parent stack outputs that match parameter names for this
213
- # stack.
214
- def parent_stack_outputs
215
- result = {}
216
-
217
- @config.parent_stacks.each do |stack_name|
218
- resp = cf_client.describe_stacks(stack_name: stack_name)
219
- raise "Parent Stack #{stack_name} not found!" unless resp.stacks.size == 1
220
-
221
- # If there is an input parameters matching a stack output, pass it.
222
- resp.stacks[0].outputs.each do |output|
223
- if stack_parameters.include?(output.output_key)
224
- result[output.output_key] = output.output_value
225
- end
226
- end
195
+ def load_template_file
196
+ json_template = JsonStackTemplate.new(json_template_path)
197
+ yaml_template = YamlStackTemplate.new(yaml_template_path)
198
+ case
199
+ when json_template.exist?
200
+ json_template
201
+ when yaml_template.exist?
202
+ yaml_template
203
+ else
204
+ raise "CloudFormation template not found at #{json_template_path} "\
205
+ "or #{yaml_template_path}!" unless @template_file
227
206
  end
207
+ end
228
208
 
229
- result
209
+ def stack_parameters
210
+ template.parameters.map(&:name)
230
211
  end
231
212
 
232
213
  # @return [Aws::CloudFormation::Types::Stack]
233
214
  def get_stack(name)
234
215
  stacks = cf_client.describe_stacks(stack_name: name).stacks
235
- raise Thor::Error, "Could not describe stack: #{name}" if stacks.empty?
216
+ raise "Could not describe stack: #{name}" if stacks.empty?
236
217
 
237
218
  stacks.first
238
219
  rescue Aws::CloudFormation::Errors::ValidationError
239
- raise Thor::Error, "Could not describe stack: #{name}"
220
+ raise "Could not describe stack: #{name}"
240
221
  end
241
222
 
242
223
  def create_stack
@@ -244,13 +225,11 @@ module Moonshot
244
225
  stack_name: @name,
245
226
  template_body: template.body,
246
227
  capabilities: ['CAPABILITY_IAM'],
247
- parameters: load_parameters_file,
248
- tags: [
249
- { key: 'ah_stage', value: @name }
250
- ]
228
+ parameters: @config.parameters.values.map(&:to_cf),
229
+ tags: make_tags
251
230
  )
252
231
  rescue Aws::CloudFormation::Errors::AccessDenied
253
- raise Thor::Error, 'You are not authorized to perform create_stack calls.'
232
+ raise 'You are not authorized to perform create_stack calls.'
254
233
  end
255
234
 
256
235
  # @return [Boolean]
@@ -260,27 +239,24 @@ module Moonshot
260
239
  stack_name: @name,
261
240
  template_body: template.body,
262
241
  capabilities: ['CAPABILITY_IAM'],
263
- parameters: @config.parameter_strategy.parameters(
264
- overrides,
265
- parameters,
266
- template
267
- )
242
+ parameters: @config.parameters.values.map(&:to_cf),
243
+ tags: make_tags
268
244
  )
269
245
  true
270
246
  rescue Aws::CloudFormation::Errors::ValidationError => e
271
- raise Thor::Error, e.message unless
247
+ raise e.message unless
272
248
  e.message == 'No updates are to be performed.'
273
249
  false
274
250
  end
275
251
 
276
252
  # TODO: Refactor this into it's own class.
277
- def wait_for_stack_state(wait_target, past_tense_verb) # rubocop:disable AbcSize
253
+ def wait_for_stack_state(wait_target, past_tense_verb)
278
254
  result = true
279
255
 
280
256
  stack_id = get_stack(@name).stack_id
281
257
 
282
258
  events = StackEventsPoller.new(cf_client, stack_id)
283
- events.show_only_errors unless @config.show_all_events
259
+ events.show_only_errors unless @config.show_all_stack_events
284
260
 
285
261
  @ilog.start_threaded "Waiting for #{stack_name} to be successfully #{past_tense_verb}." do |s|
286
262
  begin
@@ -318,6 +294,19 @@ module Moonshot
318
294
  result
319
295
  end
320
296
 
297
+ def make_tags
298
+ default_tags = [
299
+ { key: 'moonshot_application', value: @config.app_name },
300
+ { key: 'moonshot_environment', value: @config.environment_name }
301
+ ]
302
+
303
+ if @config.additional_tag
304
+ default_tags << { key: @config.additional_tag, value: @name }
305
+ end
306
+
307
+ default_tags
308
+ end
309
+
321
310
  def format_event(event)
322
311
  str = case event.resource_status
323
312
  when /FAILED/