moonshot 2.0.0.beta6 → 3.0.4

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.
Files changed (80) hide show
  1. checksums.yaml +5 -5
  2. data/bin/moonshot +4 -1
  3. data/lib/default/bin/build.sh +0 -0
  4. data/lib/moonshot/account_context.rb +2 -0
  5. data/lib/moonshot/always_use_default_source.rb +5 -4
  6. data/lib/moonshot/artifact_repository/s3_bucket.rb +11 -5
  7. data/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +7 -7
  8. data/lib/moonshot/ask_user_source.rb +2 -0
  9. data/lib/moonshot/build_mechanism/github_release.rb +9 -2
  10. data/lib/moonshot/build_mechanism/script.rb +8 -7
  11. data/lib/moonshot/build_mechanism/travis_deploy.rb +5 -5
  12. data/lib/moonshot/build_mechanism/version_proxy.rb +13 -0
  13. data/lib/moonshot/change_set.rb +24 -34
  14. data/lib/moonshot/command.rb +3 -1
  15. data/lib/moonshot/command_line.rb +12 -9
  16. data/lib/moonshot/command_line_dispatcher.rb +5 -7
  17. data/lib/moonshot/commands/build.rb +6 -0
  18. data/lib/moonshot/commands/console.rb +2 -0
  19. data/lib/moonshot/commands/create.rb +16 -2
  20. data/lib/moonshot/commands/delete.rb +9 -0
  21. data/lib/moonshot/commands/deploy.rb +4 -8
  22. data/lib/moonshot/commands/doctor.rb +2 -0
  23. data/lib/moonshot/commands/generate_template.rb +46 -0
  24. data/lib/moonshot/commands/interactive_command.rb +15 -0
  25. data/lib/moonshot/commands/list.rb +2 -0
  26. data/lib/moonshot/commands/new.rb +5 -2
  27. data/lib/moonshot/commands/parameter_arguments.rb +5 -4
  28. data/lib/moonshot/commands/parent_stack_option.rb +2 -0
  29. data/lib/moonshot/commands/push.rb +2 -0
  30. data/lib/moonshot/commands/show_all_events_option.rb +2 -0
  31. data/lib/moonshot/commands/ssh.rb +4 -0
  32. data/lib/moonshot/commands/status.rb +2 -0
  33. data/lib/moonshot/commands/tag_arguments.rb +20 -0
  34. data/lib/moonshot/commands/update.rb +8 -1
  35. data/lib/moonshot/commands/version.rb +2 -0
  36. data/lib/moonshot/controller.rb +28 -13
  37. data/lib/moonshot/controller_config.rb +13 -27
  38. data/lib/moonshot/creds_helper.rb +2 -0
  39. data/lib/moonshot/deployment_mechanism/code_deploy.rb +44 -37
  40. data/lib/moonshot/doctor_helper.rb +14 -15
  41. data/lib/moonshot/dynamic_template.rb +76 -0
  42. data/lib/moonshot/interactive_logger_proxy.rb +4 -4
  43. data/lib/moonshot/json_stack_template.rb +3 -0
  44. data/lib/moonshot/parameter_collection.rb +3 -0
  45. data/lib/moonshot/parent_stack_parameter_loader.rb +7 -3
  46. data/lib/moonshot/resources.rb +2 -0
  47. data/lib/moonshot/resources_helper.rb +5 -1
  48. data/lib/moonshot/shell.rb +8 -8
  49. data/lib/moonshot/ssh_command.rb +2 -0
  50. data/lib/moonshot/ssh_command_builder.rb +3 -1
  51. data/lib/moonshot/ssh_config.rb +3 -2
  52. data/lib/moonshot/ssh_fork_executor.rb +2 -0
  53. data/lib/moonshot/ssh_target_selector.rb +3 -1
  54. data/lib/moonshot/stack.rb +73 -55
  55. data/lib/moonshot/stack_asg_printer.rb +14 -12
  56. data/lib/moonshot/stack_config.rb +3 -2
  57. data/lib/moonshot/stack_events_poller.rb +3 -1
  58. data/lib/moonshot/stack_list_printer.rb +2 -0
  59. data/lib/moonshot/stack_lister.rb +6 -2
  60. data/lib/moonshot/stack_output_printer.rb +2 -0
  61. data/lib/moonshot/stack_parameter.rb +5 -9
  62. data/lib/moonshot/stack_parameter_printer.rb +3 -1
  63. data/lib/moonshot/stack_template.rb +2 -0
  64. data/lib/moonshot/task.rb +3 -0
  65. data/lib/moonshot/tools/asg_rollout/asg.rb +22 -21
  66. data/lib/moonshot/tools/asg_rollout/asg_instance.rb +2 -0
  67. data/lib/moonshot/tools/asg_rollout/hook_exec_environment.rb +2 -0
  68. data/lib/moonshot/tools/asg_rollout/instance_health.rb +2 -0
  69. data/lib/moonshot/tools/asg_rollout.rb +16 -14
  70. data/lib/moonshot/tools/asg_rollout_config.rb +2 -0
  71. data/lib/moonshot/unicode_table.rb +5 -3
  72. data/lib/moonshot/yaml_stack_template.rb +2 -0
  73. data/lib/moonshot.rb +13 -1
  74. data/lib/plugins/backup.rb +24 -30
  75. data/lib/plugins/code_deploy_setup.rb +4 -2
  76. data/lib/plugins/dynamic_template.rb +36 -0
  77. data/lib/plugins/encrypted_parameters/kms_key.rb +26 -5
  78. data/lib/plugins/encrypted_parameters/parameter_encrypter.rb +2 -0
  79. data/lib/plugins/encrypted_parameters.rb +6 -2
  80. metadata +189 -51
@@ -1,4 +1,5 @@
1
- # -*- coding: utf-8 -*-
1
+ # frozen_string_literal: true
2
+
2
3
  require 'colorize'
3
4
 
4
5
  module Moonshot
@@ -19,18 +20,16 @@ module Moonshot
19
20
  puts
20
21
  puts self.class.name.split('::').last
21
22
  private_methods.each do |meth|
22
- begin
23
- send(meth) if meth =~ /^doctor_check_/
24
- rescue DoctorCritical
25
- # Stop running checks in this Mechanism.
26
- success = false
27
- break
28
- rescue => e
29
- success = false
30
- print ' ✗ '.red
31
- puts "Exception while running check: #{e.class}: #{e.message.lines.first}"
32
- break
33
- end
23
+ send(meth) if meth =~ /^doctor_check_/
24
+ rescue DoctorCritical
25
+ # Stop running checks in this Mechanism.
26
+ success = false
27
+ break
28
+ rescue StandardError => e
29
+ success = false
30
+ print ' ✗ '.red
31
+ puts "Exception while running check: #{e.class}: #{e.message.lines.first}"
32
+ break
34
33
  end
35
34
 
36
35
  success
@@ -44,13 +43,13 @@ module Moonshot
44
43
  def warning(str, additional_info = nil)
45
44
  print ' ? '.yellow
46
45
  puts str
47
- additional_info.lines.each { |l| puts " #{l}" } if additional_info
46
+ additional_info&.lines&.each { |l| puts " #{l}" }
48
47
  end
49
48
 
50
49
  def critical(str, additional_info = nil)
51
50
  print ' ✗ '.red
52
51
  puts str
53
- additional_info.lines.each { |l| puts " #{l}" } if additional_info
52
+ additional_info&.lines&.each { |l| puts " #{l}" }
54
53
  raise DoctorCritical
55
54
  end
56
55
  end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+
5
+ module Moonshot
6
+ class TemplateExists < StandardError; end
7
+ class InvalidTemplate < StandardError; end
8
+
9
+ class DynamicTemplate
10
+ # A class to encapsulate template parameters, passing a hash to `process` is
11
+ # only available from Ruby 2.5.
12
+ class Parameters
13
+ def initialize(parameters)
14
+ parameters.each do |k, v|
15
+ instance_variable_set("@#{k}".to_sym, v)
16
+ # Adding an attribute reader for flexibility, this way you can add
17
+ # either `@parameter` or just `parameter` to your template.
18
+ self.class.send(:attr_reader, k.to_sym)
19
+ end
20
+ end
21
+
22
+ def expose_binding
23
+ binding
24
+ end
25
+ end
26
+
27
+ attr_writer :destination
28
+
29
+ def initialize(source:, parameters:, destination:)
30
+ @source = File.read(source)
31
+ @parameters = parameters
32
+ @destination = destination
33
+ end
34
+
35
+ def parameters_obj
36
+ @parameters_obj ||= Parameters.new(parameters_file)
37
+ end
38
+
39
+ def parameters_file
40
+ env_name = Moonshot.config.environment_name
41
+ @parameters.respond_to?(:call) ? @parameters.call(env_name) : @parameters
42
+ end
43
+
44
+ def process
45
+ validate_destination_exists
46
+ new_template = generate_template
47
+
48
+ validate_template(new_template)
49
+ write_output(new_template)
50
+ end
51
+
52
+ private
53
+
54
+ def validate_destination_exists
55
+ return unless File.file?(@destination)
56
+
57
+ raise TemplateExists, "Output file '#{@destination}' already exists."
58
+ end
59
+
60
+ def validate_template(template)
61
+ Aws::CloudFormation::Client.new.validate_template(
62
+ template_body: template
63
+ )
64
+ rescue Aws::CloudFormation::Errors::ValidationError => e
65
+ raise InvalidTemplate, "Invalid template:\n#{e}"
66
+ end
67
+
68
+ def generate_template
69
+ ERB.new(@source).result(parameters_obj.expose_binding)
70
+ end
71
+
72
+ def write_output(content)
73
+ File.write(@destination, content)
74
+ end
75
+ end
76
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'forwardable'
2
4
 
3
5
  module Moonshot
@@ -10,8 +12,7 @@ module Moonshot
10
12
  @logger = logger
11
13
  end
12
14
 
13
- def blank
14
- end
15
+ def blank; end
15
16
 
16
17
  def continue(str = nil)
17
18
  @logger.info(str) if str
@@ -21,8 +22,7 @@ module Moonshot
21
22
  @logger.error(str)
22
23
  end
23
24
 
24
- def repaint
25
- end
25
+ def repaint; end
26
26
 
27
27
  def success(str = 'Success')
28
28
  @logger.info(str)
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'json'
4
+ require_relative 'stack_template'
2
5
 
3
6
  module Moonshot
4
7
  # Handles JSON formatted AWS template files.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  # A Rigid Hash-like structure that only accepts manipulation of
3
5
  # parameters defined in the Stack template. Anything else results in
@@ -6,6 +8,7 @@ module Moonshot
6
8
  extend Forwardable
7
9
 
8
10
  def_delegators :@hash, :key?, :fetch, :[], :keys, :values
11
+ attr_reader :hash
9
12
 
10
13
  def self.from_template(template)
11
14
  obj = new
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  class ParentStackParameterLoader
3
5
  def initialize(config)
@@ -8,30 +10,32 @@ module Moonshot
8
10
  @config.parent_stacks.each do |stack_name|
9
11
  count = 0
10
12
 
11
- resp = cf_client.describe_stacks(stack_name: stack_name)
13
+ resp = cf_client.describe_stacks(stack_name:)
12
14
  raise "Parent Stack #{stack_name} not found!" unless resp.stacks.size == 1
13
15
 
14
16
  # If there is an input parameters matching a stack output, pass it.
15
17
  resp.stacks[0].outputs.each do |output|
16
18
  next unless @config.parameters.key?(output.output_key)
19
+
17
20
  # Our Stack has a Parameter matching this output. Set it's
18
21
  # value to the Output's value.
19
22
  count += 1
20
23
  @config.parameters.fetch(output.output_key).set(output.output_value)
21
24
  end
22
25
 
23
- puts "Imported #{count} parameters from parent stack #{stack_name.blue}!" if count > 0
26
+ puts "Imported #{count} parameters from parent stack #{stack_name.blue}!" if count.positive?
24
27
  end
25
28
  end
26
29
 
27
30
  def load_missing_only!
28
31
  @config.parent_stacks.each do |stack_name|
29
- resp = cf_client.describe_stacks(stack_name: stack_name)
32
+ resp = cf_client.describe_stacks(stack_name:)
30
33
  raise "Parent Stack #{stack_name} not found!" unless resp.stacks.size == 1
31
34
 
32
35
  # If there is an input parameters matching a stack output, pass it.
33
36
  resp.stacks[0].outputs.each do |output|
34
37
  next unless @config.parameters.key?(output.output_key)
38
+
35
39
  # Our Stack has a Parameter matching this output. Set it's
36
40
  # value to the Output's value, but only if we don't already
37
41
  # have a previous value we're using.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  # Resources is a dependency container that holds references to instances
3
5
  # provided to a Mechanism (build, deploy, etc.).
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  # Provides shorthand methods for accessing resources provided by the Resources
3
5
  # container.
@@ -8,16 +10,18 @@ module Moonshot
8
10
 
9
11
  # TODO: Deprecate this interface.
10
12
  def log
11
- @log ||= Logger.new(STDOUT)
13
+ @log ||= Logger.new($stdout)
12
14
  end
13
15
 
14
16
  def stack
15
17
  raise 'Resources not provided to Mechanism!' unless @resources
18
+
16
19
  @resources.stack
17
20
  end
18
21
 
19
22
  def ilog
20
23
  raise 'Resources not provided to Mechanism!' unless @resources
24
+
21
25
  @resources.ilog
22
26
  end
23
27
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
  require 'retriable'
3
5
 
@@ -17,7 +19,7 @@ module Moonshot::Shell
17
19
 
18
20
  # Run a command, returning stdout. Stderr is suppressed unless the command
19
21
  # returns non-zero.
20
- def sh_out(cmd, fail: true, stdin: '')
22
+ def sh_out(cmd, fail = true, stdin = '')
21
23
  r_in, w_in = IO.pipe
22
24
  r_out, w_out = IO.pipe
23
25
  r_err, w_err = IO.pipe
@@ -53,13 +55,11 @@ module Moonshot::Shell
53
55
  define_method(meth) { |*args| shell.public_send(meth, *args) }
54
56
  end
55
57
 
56
- def sh_step(cmd, args = {})
58
+ def sh_step(cmd, **args)
57
59
  msg = args.delete(:msg) || cmd
58
- if msg.length > (terminal_width - 18)
59
- msg = "#{msg[0..(terminal_width - 22)]}..."
60
- end
60
+ msg = "#{msg[0..(terminal_width - 22)]}..." if msg.length > (terminal_width - 18)
61
61
  ilog.start_threaded(msg) do |step|
62
- out = sh_out(cmd, args)
62
+ out = sh_out(cmd, **args)
63
63
  yield step, out if block_given?
64
64
  step.success
65
65
  end
@@ -73,9 +73,9 @@ module Moonshot::Shell
73
73
  # @param opts [Hash] Options for retriable.
74
74
  #
75
75
  # @return [String] Stdout form the command.
76
- def sh_retry(cmd, fail: true, stdin: '', opts: {})
76
+ def sh_retry(cmd, fail = true, stdin = '', opts: {})
77
77
  Retriable.retriable(DEFAULT_RETRY_OPTIONS.merge(opts)) do
78
- out = sh_out(cmd, stdin: stdin)
78
+ out = sh_out(cmd, stdin:)
79
79
  yield out if block_given?
80
80
  out
81
81
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'thor'
2
4
 
3
5
  module Moonshot
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'shellwords'
2
4
 
3
5
  module Moonshot
@@ -25,7 +27,7 @@ module Moonshot
25
27
  @instance_ip ||= Aws::EC2::Client.new
26
28
  .describe_instances(instance_ids: [@instance_id])
27
29
  .reservations.first.instances.first.public_ip_address
28
- rescue
30
+ rescue StandardError
29
31
  raise "Failed to determine public IP address for instance #{@instance_id}!"
30
32
  end
31
33
  end
@@ -1,7 +1,8 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  class SSHConfig
3
- attr_accessor :ssh_identity_file
4
- attr_accessor :ssh_user
5
+ attr_accessor :ssh_identity_file, :ssh_user
5
6
 
6
7
  def initialize
7
8
  @ssh_identity_file = ENV['MOONSHOT_SSH_KEY_FILE']
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'open3'
2
4
 
3
5
  module Moonshot
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  # Choose a publically accessible instance to run commands on, given a Moonshot::Stack.
3
5
  class SSHTargetSelector
@@ -23,7 +25,7 @@ module Moonshot
23
25
  Aws::AutoScaling::Client.new.describe_auto_scaling_groups(
24
26
  auto_scaling_group_names: [asg.physical_resource_id]
25
27
  ).auto_scaling_groups.first.instances.map(&:instance_id).first
26
- rescue => e
28
+ rescue StandardError => e
27
29
  raise "Failed to select an instance from the Auto Scaling Group: #{e.message}"
28
30
  end
29
31
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'yaml'
2
4
 
3
5
  module Moonshot
@@ -5,17 +7,36 @@ module Moonshot
5
7
  # stores the state of the active stack running on AWS, but contains a
6
8
  # reference to the StackTemplate that would be applied with an update
7
9
  # action.
8
- class Stack # rubocop:disable ClassLength
10
+ class Stack # rubocop:disable Metrics/ClassLength
9
11
  include CredsHelper
10
12
  include DoctorHelper
11
13
 
12
- attr_reader :app_name
13
- attr_reader :name
14
+ attr_reader :app_name, :name
15
+
16
+ class << self
17
+ def generate_name(config)
18
+ [config.app_name, config.environment_name].join('-')
19
+ end
20
+
21
+ def make_tags(config)
22
+ default_tags = [
23
+ { key: 'moonshot_application', value: config.app_name },
24
+ { key: 'moonshot_environment', value: config.environment_name }
25
+ ]
26
+ name = generate_name(config)
27
+
28
+ if config.additional_tag
29
+ default_tags << { key: config.additional_tag, value: name }
30
+ end
31
+
32
+ default_tags + config.extra_tags
33
+ end
34
+ end
14
35
 
15
36
  def initialize(config)
16
37
  @config = config
17
38
  @ilog = config.interactive_logger
18
- @name = [@config.app_name, @config.environment_name].join('-')
39
+ @name = self.class.generate_name(@config)
19
40
 
20
41
  yield @config if block_given?
21
42
  end
@@ -111,7 +132,7 @@ module Moonshot
111
132
  resource_summary = resource_summaries.find do |r|
112
133
  r.logical_resource_id == logical_id
113
134
  end
114
- resource_summary.physical_resource_id if resource_summary
135
+ resource_summary&.physical_resource_id
115
136
  end
116
137
 
117
138
  # @return [Array<Aws::CloudFormation::Types::StackResourceSummary>]
@@ -152,13 +173,23 @@ module Moonshot
152
173
 
153
174
  # Support the legacy file location from Moonshot 1.0.
154
175
  YamlStackTemplate.new(
155
- File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.yml")),
176
+ File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.yml")
177
+ ),
156
178
  JsonStackTemplate.new(
157
- File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.json"))
179
+ File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.json")
180
+ )
158
181
  ]
159
182
 
183
+ # If a template file has been specified in the config, look there first.
184
+ if @config.template_file
185
+ templates.unshift YamlStackTemplate.new(@config.template_file)
186
+ templates.unshift JsonStackTemplate.new(@config.template_file)
187
+ end
188
+
160
189
  template = templates.find(&:exist?)
190
+
161
191
  raise 'No template found in moonshot/template.{yml,json}!' unless template
192
+
162
193
  template
163
194
  end
164
195
 
@@ -177,9 +208,7 @@ module Moonshot
177
208
  end
178
209
 
179
210
  def upload_template_to_s3
180
- unless @config.template_s3_bucket
181
- raise 'The S3 bucket to store the template in is not configured.'
182
- end
211
+ raise 'The S3 bucket to store the template in is not configured.' unless @config.template_s3_bucket
183
212
 
184
213
  s3_object_key = "#{@name}-#{Time.now.getutc.to_i}-#{File.basename(template.filename)}"
185
214
  template_url = "http://#{@config.template_s3_bucket}.s3.amazonaws.com/#{s3_object_key}"
@@ -199,7 +228,7 @@ module Moonshot
199
228
  def create_stack
200
229
  parameters = {
201
230
  stack_name: @name,
202
- capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM),
231
+ capabilities: %w[CAPABILITY_IAM CAPABILITY_NAMED_IAM],
203
232
  parameters: @config.parameters.values.map(&:to_cf),
204
233
  tags: make_tags
205
234
  }
@@ -221,11 +250,12 @@ module Moonshot
221
250
  ].join('-')
222
251
 
223
252
  parameters = {
224
- change_set_name: change_set_name,
253
+ change_set_name:,
225
254
  description: "Moonshot update command for application '#{Moonshot.config.app_name}'",
226
255
  stack_name: @name,
227
- capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM),
228
- parameters: @config.parameters.values.map(&:to_cf)
256
+ capabilities: %w(CAPABILITY_IAM CAPABILITY_NAMED_IAM), # rubocop:disable Layout/HashAlignment,Style/PercentLiteralDelimiters
257
+ parameters: @config.parameters.values.map(&:to_cf),
258
+ tags: make_tags
229
259
  }
230
260
  if @config.template_s3_bucket
231
261
  parameters[:template_url] = upload_template_to_s3
@@ -248,52 +278,38 @@ module Moonshot
248
278
  events.show_only_errors unless @config.show_all_stack_events
249
279
 
250
280
  @ilog.start_threaded "Waiting for #{stack_name} to be successfully #{past_tense_verb}." do |s|
251
- begin
252
- cf_client.wait_until(wait_target, stack_name: stack_id) do |w|
253
- w.delay = 10
254
- w.max_attempts = 360 # 60 minutes.
255
- w.before_wait do |attempt, resp|
256
- begin
257
- events.latest_events.each { |e| @ilog.error(format_event(e)) }
258
- # rubocop:disable Lint/HandleExceptions
259
- rescue Aws::CloudFormation::Errors::ValidationError
260
- # Do nothing. The above event logging block may result in
261
- # a ValidationError while waiting for a stack to delete.
262
- end
263
- # rubocop:enable Lint/HandleExceptions
264
-
265
- if attempt == w.max_attempts - 1
266
- s.failure "#{stack_name} was not #{past_tense_verb} after 30 minutes."
267
- result = false
268
-
269
- # We don't want the interactive logger to catch an exception.
270
- throw :success
271
- end
272
- s.continue "Waiting for CloudFormation Stack to be successfully #{past_tense_verb}, current status '#{resp.stacks.first.stack_status}'." # rubocop:disable LineLength
281
+ cf_client.wait_until(wait_target, stack_name: stack_id) do |w|
282
+ w.delay = 10
283
+ w.max_attempts = 360 # 60 minutes.
284
+ w.before_wait do |attempt, resp|
285
+ begin
286
+ events.latest_events.each { |e| @ilog.error(format_event(e)) }
287
+ rescue Aws::CloudFormation::Errors::ValidationError
288
+ # Do nothing. The above event logging block may result in
289
+ # a ValidationError while waiting for a stack to delete.
273
290
  end
274
- end
291
+ if attempt == w.max_attempts - 1
292
+ s.failure "#{stack_name} was not #{past_tense_verb} after 30 minutes."
293
+ result = false
275
294
 
276
- s.success "#{stack_name} successfully #{past_tense_verb}." if result
277
- rescue Aws::Waiters::Errors::FailureStateError
278
- result = false
279
- s.failure "#{stack_name} failed to update."
295
+ # We don't want the interactive logger to catch an exception.
296
+ throw :success
297
+ end
298
+ s.continue "Waiting for CloudFormation Stack to be successfully #{past_tense_verb}, current status '#{resp.stacks.first.stack_status}'." # rubocop:disable Layout/LineLength
299
+ end
280
300
  end
301
+
302
+ s.success "#{stack_name} successfully #{past_tense_verb}." if result
303
+ rescue Aws::Waiters::Errors::FailureStateError
304
+ result = false
305
+ s.failure "#{stack_name} failed to update."
281
306
  end
282
307
 
283
308
  result
284
309
  end
285
310
 
286
311
  def make_tags
287
- default_tags = [
288
- { key: 'moonshot_application', value: @config.app_name },
289
- { key: 'moonshot_environment', value: @config.environment_name }
290
- ]
291
-
292
- if @config.additional_tag
293
- default_tags << { key: @config.additional_tag, value: @name }
294
- end
295
-
296
- default_tags
312
+ self.class.make_tags(@config)
297
313
  end
298
314
 
299
315
  def format_event(event)
@@ -320,14 +336,15 @@ module Moonshot
320
336
  end
321
337
 
322
338
  def doctor_check_template_against_aws
339
+ validate_params = {}
323
340
  if @config.template_s3_bucket
324
- parameters[:template_url] = upload_template_to_s3
341
+ validate_params[:template_url] = upload_template_to_s3
325
342
  else
326
- parameters[:template_body] = template.body
343
+ validate_params[:template_body] = template.body
327
344
  end
328
- cf_client.validate_template(parameters)
345
+ cf_client.validate_template(validate_params)
329
346
  success('CloudFormation template is valid.')
330
- rescue => e
347
+ rescue StandardError => e
331
348
  critical('Invalid CloudFormation template!', e.message)
332
349
  end
333
350
 
@@ -351,6 +368,7 @@ module Moonshot
351
368
 
352
369
  success = wait_for_stack_state(:stack_update_complete, 'updated')
353
370
  raise 'Failed to update the CloudFormation Stack.' unless success
371
+
354
372
  success
355
373
  end
356
374
  end
@@ -1,4 +1,5 @@
1
- # coding: utf-8
1
+ # frozen_string_literal: true
2
+
2
3
  require 'colorize'
3
4
  require 'ruby-duration'
4
5
 
@@ -16,8 +17,8 @@ module Moonshot
16
17
  def print
17
18
  asgs.each do |asg|
18
19
  asg_info = as_client.describe_auto_scaling_groups(
19
- auto_scaling_group_names: [asg.physical_resource_id])
20
- .auto_scaling_groups.first
20
+ auto_scaling_group_names: [asg.physical_resource_id]
21
+ ).auto_scaling_groups.first
21
22
  t_asg_info = @table.add_leaf("ASG: #{asg.logical_resource_id}")
22
23
 
23
24
  add_asg_info(t_asg_info, asg_info)
@@ -70,7 +71,7 @@ module Moonshot
70
71
 
71
72
  # Get additional information about instances not returned by the ASG API.
72
73
  def get_addl_info(instance_ids)
73
- resp = ec2_client.describe_instances(instance_ids: instance_ids)
74
+ resp = ec2_client.describe_instances(instance_ids:)
74
75
 
75
76
  data = {}
76
77
  resp.reservations.map(&:instances).flatten.each do |instance|
@@ -85,7 +86,7 @@ module Moonshot
85
86
 
86
87
  hc = asg_info.health_check_type.blue
87
88
  gp = (asg_info.health_check_grace_period.to_s << 's').blue
88
- table.add_line "Using #{hc} health checks, with a #{gp} health check grace period." # rubocop:disable LineLength
89
+ table.add_line "Using #{hc} health checks, with a #{gp} health check grace period."
89
90
 
90
91
  dc = asg_info.desired_capacity.to_s.blue
91
92
  min = asg_info.min_size.to_s.blue
@@ -93,7 +94,7 @@ module Moonshot
93
94
  table.add_line "Desired Capacity is #{dc} (Min: #{min}, Max: #{max})."
94
95
 
95
96
  lbs = asg_info.load_balancer_names
96
- table.add_line "Has #{lbs.count.to_s.blue} Load Balancer(s): #{lbs.map(&:blue).join(', ')}" # rubocop:disable LineLength
97
+ table.add_line "Has #{lbs.count.to_s.blue} Load Balancer(s): #{lbs.map(&:blue).join(', ')}"
97
98
  end
98
99
 
99
100
  def create_instance_table(asg_info)
@@ -112,11 +113,11 @@ module Moonshot
112
113
 
113
114
  def instance_row(asg_instance, ec2_instance)
114
115
  if ec2_instance
115
- if ec2_instance.public_ip_address
116
- ip_address = "#{ec2_instance.public_ip_address} (#{ec2_instance.private_ip_address})"
117
- else
118
- ip_address = "#{ec2_instance.private_ip_address} (PRV)"
119
- end
116
+ ip_address = if ec2_instance.public_ip_address
117
+ "#{ec2_instance.public_ip_address} (#{ec2_instance.private_ip_address})"
118
+ else
119
+ "#{ec2_instance.private_ip_address} (PRV)"
120
+ end
120
121
  uptime = uptime_format(ec2_instance.launch_time)
121
122
  else
122
123
  # We've seen race conditions where ASG tells us about instances that EC2 is no longer
@@ -142,7 +143,8 @@ module Moonshot
142
143
  def add_recent_activity_leaf(table, asg_name)
143
144
  recent = table.add_leaf('Recent Activity')
144
145
  resp = as_client.describe_scaling_activities(
145
- auto_scaling_group_name: asg_name).activities
146
+ auto_scaling_group_name: asg_name
147
+ ).activities
146
148
 
147
149
  rows = resp.sort_by(&:start_time).reverse.first(10).map do |activity|
148
150
  row_for_activity(activity)
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  # Configuration for the Moonshot::Stack class.
3
5
  class StackConfig
4
- attr_accessor :parent_stacks
5
- attr_accessor :show_all_events
6
+ attr_accessor :parent_stacks, :show_all_events
6
7
 
7
8
  def initialize
8
9
  @parent_stacks = []
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Moonshot
2
4
  # The StackEventsPoller queries DescribeStackEvents every time #latest_events
3
5
  # is invoked, filtering out events that have already been returned. It can
@@ -32,7 +34,7 @@ module Moonshot
32
34
  def filter_events(events)
33
35
  if @errors_only
34
36
  events.select do |event|
35
- %w(CREATE_FAILED UPDATE_FAILED DELETE_FAILED).include?(event.resource_status)
37
+ %w[CREATE_FAILED UPDATE_FAILED DELETE_FAILED].include?(event.resource_status)
36
38
  end
37
39
  else
38
40
  events