moonshot 1.0.0 → 1.1.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 6f5bce9f3b85c4023e62a5d250b4133c9ddf0de5
4
- data.tar.gz: c1d4db11985435018417485dccac98d9063af19c
3
+ metadata.gz: 2940b0c43a48274eb8368400726fc3269ff6fdf9
4
+ data.tar.gz: fb6dfbf21127e8c55533bf848361f81c07f2b0d7
5
5
  SHA512:
6
- metadata.gz: 9027ac025f8594b769147582b37b0aa09aeb8e4ecf35d6c0cd7ba975ffa8df25cceb52cc1b4a0ba352b4f27851c76f7b2d3b5e6def8e1c7bec52b76faf508326
7
- data.tar.gz: c662be99a3702a0d5a35d1ee9911b4eba129cff211a3378e378493788577a24724f07cbb0e8276fb9ca67f4cc49475b2e23952957065968d39fd251b13f75c26
6
+ metadata.gz: 146242a237abcccc8229257e0c73310ff539ba1483a57695cc1d6ccbc534778ac015354a9b91fa7ee09e83a8c2e921c3e61946dfd7a5ce0169b04ef005b37376
7
+ data.tar.gz: aaf546412e380adc74bc823e7e2a7aef2e1ae31eafb35c2fe284e075b714940eb3d88b7c72dfa53a890f1bf9a62b0fb2108691f52eca510aa205bc1eccf67af2
data/lib/moonshot.rb CHANGED
@@ -24,55 +24,6 @@ module Moonshot
24
24
  end
25
25
  end
26
26
 
27
- [
28
- # Helpers
29
- 'creds_helper',
30
- 'doctor_helper',
31
- 'resources',
32
- 'resources_helper',
33
-
34
- # Core
35
- 'interactive_logger_proxy',
36
- 'command_line',
37
- 'command',
38
- 'ssh_command',
39
- 'commands/build',
40
- 'commands/console',
41
- 'commands/create',
42
- 'commands/delete',
43
- 'commands/deploy',
44
- 'commands/doctor',
45
- 'commands/list',
46
- 'commands/push',
47
- 'commands/ssh',
48
- 'commands/status',
49
- 'commands/update',
50
- 'commands/version',
51
- 'controller',
52
- 'controller_config',
53
- 'stack',
54
- 'stack_config',
55
- 'stack_lister',
56
- 'stack_events_poller',
57
- 'merge_strategy',
58
- 'default_strategy',
59
- 'ask_user_source',
60
- 'always_use_default_source',
61
-
62
- # Built-in mechanisms
63
- 'artifact_repository/s3_bucket',
64
- 'artifact_repository/s3_bucket_via_github_releases',
65
- 'build_mechanism/script',
66
- 'build_mechanism/github_release',
67
- 'build_mechanism/travis_deploy',
68
- 'build_mechanism/version_proxy',
69
- 'deployment_mechanism/code_deploy',
70
-
71
- # Core Tools
72
- 'tools/asg_rollout'
73
- ].each { |f| require_relative "moonshot/#{f}" }
74
-
75
- # Bundled plugins
76
- [
77
- 'backup'
78
- ].each { |p| require_relative "plugins/#{p}" }
27
+ require 'require_all'
28
+ require_rel 'moonshot'
29
+ require_rel 'plugins'
@@ -0,0 +1,19 @@
1
+ module Moonshot
2
+ module AccountContext
3
+ def self.get
4
+ @account ||= determine_account_name
5
+ end
6
+
7
+ def self.set(account_name)
8
+ @account = account_name
9
+ end
10
+
11
+ def self.reset
12
+ @account = nil
13
+ end
14
+
15
+ def self.determine_account_name
16
+ Aws::IAM::Client.new.list_account_aliases.account_aliases.first
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,100 @@
1
+ module Moonshot
2
+ class ChangeSet
3
+ attr_reader :name
4
+ attr_reader :stack_name
5
+
6
+ def initialize(name, stack_name)
7
+ @name = name
8
+ @stack_name = stack_name
9
+ @change_set = nil
10
+ @cf_client = Aws::CloudFormation::Client.new
11
+ end
12
+
13
+ def confirm?
14
+ unless Moonshot.config.interactive
15
+ raise 'Cannot confirm ChangeSet when interactive mode is disabled!'
16
+ end
17
+
18
+ loop do
19
+ print 'Apply changes? '
20
+ resp = gets.chomp.downcase
21
+
22
+ return true if resp == 'yes'
23
+ return false if resp == 'no'
24
+ puts "Please enter 'yes' or 'no'!"
25
+ end
26
+ end
27
+
28
+ def valid?
29
+ @change_set.status == 'CREATE_COMPLETE'
30
+ end
31
+
32
+ def invalid_reason
33
+ @change_set.status_reason
34
+ end
35
+
36
+ def display_changes
37
+ wait_for_change_set unless @change_set
38
+
39
+ @change_set.changes.map(&:resource_change).each do |c|
40
+ puts "* #{c.action} #{c.logical_resource_id} (#{c.resource_type})"
41
+
42
+ if c.replacement == 'True'
43
+ puts ' - Will be replaced'
44
+ elsif c.replacement == 'Conditional'
45
+ puts ' - May be replaced (Conditional)'
46
+ end
47
+
48
+ c.details.each do |d|
49
+ case d.change_source
50
+ when 'ResourceReference', 'ParameterReference'
51
+ puts " - Caused by #{d.causing_entity.blue} (#{d.change_source})"
52
+ when 'DirectModification'
53
+ puts " - Caused by template change (#{d.target.attribute}: #{d.target.name})"
54
+ end
55
+ end
56
+ end
57
+ end
58
+
59
+ def execute
60
+ wait_for_change_set unless @change_set
61
+ @cf_client.execute_change_set(
62
+ change_set_name: @name,
63
+ stack_name: @stack_name)
64
+ end
65
+
66
+ def delete
67
+ wait_for_change_set unless @change_set
68
+ @cf_client.delete_change_set(
69
+ change_set_name: @name,
70
+ stack_name: @stack_name)
71
+ rescue Aws::CloudFormation::Errors::InvalidChangeSetStatus
72
+ sleep 1
73
+ retry
74
+ end
75
+
76
+ # NOTE: At the time of this patch, AWS-SDK native Waiters do not
77
+ # have support for ChangeSets. Once they add this, we can make
78
+ # this code much better.
79
+ def wait_for_change_set
80
+ start = Time.now.to_i
81
+
82
+ loop do
83
+ resp = @cf_client.describe_change_set(
84
+ change_set_name: @name,
85
+ stack_name: @stack_name)
86
+
87
+ if %w(CREATE_COMPLETE FAILED).include?(resp.status)
88
+ @change_set = resp
89
+ return
90
+ end
91
+
92
+ if Time.now.to_i > start + 30
93
+ raise 'ChangeSet did not complete creation within 30 seconds!'
94
+ end
95
+
96
+ sleep 0.25 # http://bit.ly/1qY1ZXJ
97
+ end
98
+ end
99
+ end
100
+ end
@@ -5,7 +5,7 @@ module Moonshot
5
5
  class Command
6
6
  module ClassMethods
7
7
  # TODO: Can we auto-generate usage for commands with no positional arguments, at least?
8
- attr_accessor :usage, :description
8
+ attr_accessor :usage, :description, :only_in_account
9
9
  end
10
10
 
11
11
  def self.inherited(base)
@@ -30,15 +30,6 @@ module Moonshot
30
30
  o.on('--[no-]interactive-logger', TrueClass, 'Enable or disable fancy logging') do |v|
31
31
  @use_interactive_logger = v
32
32
  end
33
-
34
- o.on('--[no-]show-all-events', FalseClass, 'Show all stack events during update') do |v|
35
- Moonshot.config.show_all_stack_events = v
36
- end
37
-
38
- o.on('-pPARENT_STACK', '--parent=PARENT_STACK',
39
- 'Parent stack to import parameters from') do |v|
40
- Moonshot.config.parent_stacks = [v]
41
- end
42
33
  end
43
34
  end
44
35
 
@@ -46,10 +37,9 @@ module Moonshot
46
37
 
47
38
  # Build a Moonshot::Controller from the CLI options.
48
39
  def controller
49
- controller = Moonshot::Controller.new
50
-
51
- # Apply CLI options to configuration defined by Moonfile.
52
- controller.config = Moonshot.config
40
+ config = Moonshot.config
41
+ config.update_for_account!
42
+ controller = Moonshot::Controller.new(config)
53
43
 
54
44
  # Degrade to a more compatible logger if the terminal seems outdated,
55
45
  # or at the users request.
@@ -1,5 +1,3 @@
1
- require 'thor'
2
-
3
1
  module Moonshot
4
2
  # This class implements the command-line `moonshot` tool.
5
3
  class CommandLine
@@ -12,7 +10,7 @@ module Moonshot
12
10
  @classes || []
13
11
  end
14
12
 
15
- def run! # rubocop:disable AbcSize, CyclomaticComplexity, MethodLength, PerceivedComplexity
13
+ def run! # rubocop:disable CyclomaticComplexity, MethodLength, PerceivedComplexity
16
14
  # Commands defined as Moonshot::Commands require a properly
17
15
  # configured Moonshot.rb and supporting files. Without them, we only
18
16
  # support `--help` and `new`.
@@ -65,15 +63,9 @@ module Moonshot
65
63
  raise "Command not found '#{command}'"
66
64
  end
67
65
 
68
- handler = @commands[command].new
69
- handler.parser.parse!
70
-
71
- unless ARGV.size == handler.method(:execute).arity
72
- warn handler.parser.help
73
- raise "Invalid command line for '#{command}'."
74
- end
66
+ command_class = @commands[command]
75
67
 
76
- handler.execute(*ARGV)
68
+ CommandLineDispatcher.new(command, command_class, ARGV).dispatch!
77
69
  end
78
70
 
79
71
  def load_plugins(moonfile_dir)
@@ -137,7 +129,6 @@ module Moonshot
137
129
  ARGV.delete_at(0)
138
130
  ARGV.push('-h')
139
131
  elsif ARGV[0] == 'new'
140
- require_relative 'commands/new'
141
132
  app_name = ARGV[1]
142
133
  ::Moonshot::Commands::New.run!(app_name)
143
134
  return true
@@ -0,0 +1,71 @@
1
+ module Moonshot
2
+ class CommandLineDispatcher
3
+ def initialize(command, klass, args)
4
+ @command = command
5
+ @klass = klass
6
+ @args = args
7
+ end
8
+
9
+ def dispatch!
10
+ # Look to see if we're allowed only to run in certain accounts, or
11
+ # not allowed to run in specific accounts.
12
+ check_account_restrictions
13
+
14
+ # Allow any mechanisms or plugins to hook into this CLI command.
15
+ handler = @klass.new
16
+ parser = build_parser(handler)
17
+ parser.parse!
18
+
19
+ unless @args.size == handler.method(:execute).arity
20
+ warn handler.parser.help
21
+ raise "Invalid command line for '#{@command}'."
22
+ end
23
+
24
+ handler.execute(*@args)
25
+ end
26
+
27
+ private
28
+
29
+ def check_account_restrictions
30
+ this_account = Moonshot::AccountContext.get
31
+
32
+ return if @klass.only_in_account.nil? ||
33
+ Array(@klass.only_in_account).any? { |a| a == this_account }
34
+
35
+ warn "'#{@command}' can only be run in the following accounts:"
36
+ Array(@klass.only_in_account).each do |account_name|
37
+ warn "- #{account_name}"
38
+ end
39
+
40
+ raise 'Command account restriction violation.'
41
+ end
42
+
43
+ def build_parser(handler)
44
+ parser = handler.parser
45
+
46
+ # Each mechanism / plugin may manipulate the OptionParser object
47
+ # associated with this command.
48
+ [:build_mechanism, :deployment_mechanism, :artifact_repository].each do |prov|
49
+ provider = Moonshot.config.send(prov)
50
+
51
+ if provider.respond_to?(hook_func_name(@command))
52
+ parser = provider.send(hook_func_name(@command), parser)
53
+ end
54
+ end
55
+
56
+ Moonshot.config.plugins.each do |plugin|
57
+ if plugin.respond_to?(hook_func_name(@command))
58
+ parser = plugin.send(hook_func_name(@command), parser)
59
+ end
60
+ end
61
+
62
+ parser
63
+ end
64
+
65
+ # Name of the function a plugin or mechanism could define to manipulate
66
+ # the parser for a command.
67
+ def hook_func_name(command)
68
+ command.tr('-', '_') << '_cli_hook'
69
+ end
70
+ end
71
+ end
@@ -1,9 +1,9 @@
1
- require_relative 'parameter_arguments'
2
-
3
1
  module Moonshot
4
2
  module Commands
5
3
  class Create < Moonshot::Command
6
4
  include ParameterArguments
5
+ include ShowAllEventsOption
6
+ include ParentStackOption
7
7
 
8
8
  self.usage = 'create [options]'
9
9
  self.description = 'Create a new environment'
@@ -1,6 +1,8 @@
1
1
  module Moonshot
2
2
  module Commands
3
3
  class Delete < Moonshot::Command
4
+ include ShowAllEventsOption
5
+
4
6
  self.usage = 'delete [options]'
5
7
  self.description = 'Delete an existing environment'
6
8
 
@@ -1,6 +1,3 @@
1
- require_relative '../stack_lister'
2
- require_relative '../stack_list_printer'
3
-
4
1
  module Moonshot
5
2
  module Commands
6
3
  class List < Moonshot::Command
@@ -16,18 +16,12 @@ module Moonshot
16
16
 
17
17
  create_project_dir
18
18
  copy_defaults
19
- create_file(parameter_path)
20
- create_file(template_path)
21
19
  fill_moonfile
22
20
  print_success_message
23
21
  end
24
22
 
25
23
  private
26
24
 
27
- def cwd
28
- Dir.pwd
29
- end
30
-
31
25
  def create_project_dir
32
26
  raise "Directory '#{@application_name}' already exists!" \
33
27
  if Dir.exist?(project_path)
@@ -35,7 +29,7 @@ module Moonshot
35
29
  end
36
30
 
37
31
  def project_path
38
- @project_path ||= File.join(cwd, @application_name)
32
+ @project_path ||= File.join(Dir.pwd, @application_name)
39
33
  end
40
34
 
41
35
  def copy_defaults
@@ -43,55 +37,39 @@ module Moonshot
43
37
  FileUtils.cp_r(target_path, project_path)
44
38
  end
45
39
 
46
- def create_file(path)
47
- FileUtils.touch(path)
48
- end
49
-
50
- def moonfile_path
51
- File.join(project_path, 'Moonfile.rb')
52
- end
53
-
54
- def parameter_path
55
- File.join(cf_dir, 'parameters', "#{@application_name}.yml")
56
- end
57
-
58
- def template_path
59
- File.join(cf_dir, "#{@application_name}.json")
60
- end
61
-
62
- def cf_dir
63
- File.join(project_path, 'cloud_formation')
64
- end
65
-
66
40
  def fill_moonfile
67
- File.open(moonfile_path, 'w') { |f| f.write generate_moonfile }
41
+ File.open(File.join(project_path, 'Moonfile.rb'), 'w') { |f| f.write generate_moonfile }
68
42
  end
69
43
 
70
44
  def generate_moonfile
71
45
  <<-EOF
72
- Moonshot.config do |m|
73
- m.app_name = '#{@application_name}'
74
- m.artifact_repository = S3Bucket.new('<your_bucket>')
75
- m.build_mechanism = Script.new('bin/build.sh')
76
- m.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
77
- end
78
- EOF
46
+ Moonshot.config do |m|
47
+ m.app_name = '#{@application_name}'
48
+ m.artifact_repository = S3Bucket.new('<your_bucket>')
49
+ m.build_mechanism = Script.new('bin/build.sh')
50
+ m.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
51
+ end
52
+ EOF
79
53
  end
80
54
 
81
55
  def print_success_message
82
- warn 'Your application is configured, the following changes have '\
83
- 'been made to your project directory:'
84
- warn ''
85
- warn '- Created a Moonfile.rb where you can configure your project.'
86
- warn '- Created moonshot/plugins where you can add hooks to core '\
87
- 'Moonshot actions.'
88
- warn '- Created moonshot/cli_extensions where you can create '\
89
- 'project-specific commands.'
90
- warn ''
91
- warn 'You will also need to ensure your Amazon account is configured'\
92
- ' for CodeDeploy, by creating a role that allows deployments. '\
93
- 'See: http://moonshot.readthedocs.io/en/latest/mechanisms/'\
94
- 'deployment/'
56
+ warn <<-EOF
57
+ Your application is configured, the following changes have been made
58
+ to your project directory:
59
+
60
+ * Created Moonfile.rb, where you can configure your project.
61
+ * Created moonshot/plugins, where you can place custom Ruby code
62
+ to add hooks to core Moonshot actions (create, update, delete, etc.)
63
+ * Created moonshot/cli_extensions, where you can place custom Ruby
64
+ code to add your own project-specific commands to Moonshot.
65
+ * Created moonshot/template.yml, where you can build your
66
+ CloudFormation template.
67
+
68
+ You will also need to ensure your Amazon account is configured for
69
+ CodeDeploy by creating a role that allows deployments.
70
+
71
+ See: http://moonshot.readthedocs.io/en/latest/mechanisms/deployment/
72
+ EOF
95
73
  end
96
74
  end
97
75
  end
@@ -0,0 +1,14 @@
1
+ module Moonshot
2
+ module Commands
3
+ module ParentStackOption
4
+ def parser
5
+ parser = super
6
+
7
+ parser.on('-pPARENT_STACK', '--parent=PARENT_STACK',
8
+ 'Parent stack to import parameters from') do |v|
9
+ Moonshot.config.parent_stacks = [v]
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ module Moonshot
2
+ module Commands
3
+ module ShowAllEventsOption
4
+ def parser
5
+ parser = super
6
+
7
+ parser.on('--[no-]show-all-events', TrueClass, 'Show all stack events during update') do |v|
8
+ Moonshot.config.show_all_stack_events = v
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -1,29 +1,29 @@
1
- require_relative 'parameter_arguments'
2
-
3
1
  module Moonshot
4
2
  module Commands
5
3
  class Update < Moonshot::Command
6
4
  include ParameterArguments
5
+ include ShowAllEventsOption
6
+ include ParentStackOption
7
7
 
8
8
  self.usage = 'update [options]'
9
9
  self.description = 'Update the CloudFormation stack within an environment.'
10
10
 
11
- def execute
12
- controller.update
13
- end
11
+ def parser
12
+ parser = super
14
13
 
15
- private
14
+ parser.on('--dry-run', TrueClass, 'Show the changes that would be applied, but do not execute them') do |v| # rubocop:disable LineLength
15
+ @dry_run = v
16
+ end
16
17
 
17
- def parameter_strategy_factory(value)
18
- case value.to_sym
19
- when :default
20
- Moonshot::ParameterStrategy::DefaultStrategy.new
21
- when :merge
22
- Moonshot::ParameterStrategy::MergeStrategy.new
23
- else
24
- raise "Unknown parameter strategy: #{value}"
18
+ parser.on('--force', '-f', TrueClass, 'Apply ChangeSet without confirmation') do |v|
19
+ @force = v
25
20
  end
26
21
  end
22
+
23
+ def execute
24
+ @force = true unless Moonshot.config.interactive
25
+ controller.update(dry_run: @dry_run, force: @force)
26
+ end
27
27
  end
28
28
  end
29
29
  end
@@ -1,18 +1,10 @@
1
- require_relative 'ssh_target_selector'
2
- require_relative 'ssh_command_builder'
3
-
4
- require_relative 'stack_parameter'
5
- require_relative 'parameter_collection'
6
- require_relative 'parent_stack_parameter_loader'
7
-
8
1
  module Moonshot
9
2
  # The Controller coordinates and performs all Moonshot actions.
10
3
  class Controller # rubocop:disable ClassLength
11
4
  attr_accessor :config
12
5
 
13
- def initialize
14
- @config = ControllerConfig.new
15
- yield @config if block_given?
6
+ def initialize(config)
7
+ @config = config
16
8
  end
17
9
 
18
10
  def list
@@ -71,7 +63,7 @@ module Moonshot
71
63
  end
72
64
  end
73
65
 
74
- def update # rubocop:disable AbcSize
66
+ def update(dry_run:, force:) # rubocop:disable AbcSize
75
67
  # Scan the template for all required parameters and configure
76
68
  # the ParameterCollection.
77
69
  @config.parameters = ParameterCollection.from_template(stack.template)
@@ -119,7 +111,7 @@ module Moonshot
119
111
  end
120
112
 
121
113
  run_hook(:deploy, :pre_update)
122
- stack.update
114
+ stack.update(dry_run: dry_run, force: force)
123
115
  run_hook(:deploy, :post_update)
124
116
  run_plugins(:post_update)
125
117
  end
@@ -1,11 +1,8 @@
1
- require_relative 'default_strategy'
2
- require_relative 'ssh_config'
3
- require_relative 'task'
4
- require_relative 'ask_user_source'
5
-
6
1
  module Moonshot
7
2
  # Holds configuration for Moonshot::Controller
8
3
  class ControllerConfig
4
+ attr_reader :account_alias
5
+
9
6
  attr_accessor :additional_tag
10
7
  attr_accessor :answer_file
11
8
  attr_accessor :app_name
@@ -37,6 +34,8 @@ module Moonshot
37
34
  @parameter_sources = {}
38
35
  @parameters = ParameterCollection.new
39
36
  @parent_stacks = []
37
+ @account_alias = nil
38
+ @per_account_config = {}
40
39
  @plugins = []
41
40
  @project_root = Dir.pwd
42
41
  @show_all_stack_events = false
@@ -49,5 +48,19 @@ module Moonshot
49
48
  user = ENV.fetch('USER', 'default-user').gsub(/\W/, '')
50
49
  @environment_name = "dev-#{user}"
51
50
  end
51
+
52
+ def in_account(name, &blk)
53
+ # Store account specific configs as lambdas, to be evaluated
54
+ # if the account name matches during controller execution.
55
+ @per_account_config[name] = blk
56
+ end
57
+
58
+ def update_for_account!
59
+ # Evaluated any account-specific configuration.
60
+ @account_alias = Moonshot::AccountContext.get
61
+ if @account_alias && @per_account_config.key?(account_alias)
62
+ @per_account_config[@account_alias].call(self)
63
+ end
64
+ end
52
65
  end
53
66
  end
@@ -40,6 +40,7 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
40
40
  @group_name = group_name
41
41
  @codedeploy_role = role
42
42
  @codedeploy_config = config_name
43
+ @ignore_app_stop_failures = false
43
44
  end
44
45
 
45
46
  def post_create_hook
@@ -70,13 +71,7 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
70
71
  deployment_id = nil
71
72
 
72
73
  ilog.start_threaded 'Creating Deployment' do |s|
73
- res = cd_client.create_deployment(
74
- application_name: app_name,
75
- deployment_group_name: group_name,
76
- revision: revision_for_artifact_repo(artifact_repo, version_name),
77
- deployment_config_name: @codedeploy_config,
78
- description: "Deploying version #{version_name}"
79
- )
74
+ res = create_deployment(artifact_repo, version_name)
80
75
  deployment_id = res.deployment_id
81
76
  s.continue "Created Deployment #{deployment_id.blue}."
82
77
  success = wait_for_deployment(deployment_id, s)
@@ -96,6 +91,17 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
96
91
  end
97
92
  end
98
93
 
94
+ def deploy_cli_hook(parser)
95
+ parser.on('--ignore-app-stop-failures', TrueClass, 'Continue deployment on ApplicationStop failures') do |v| # rubocop:disable LineLength
96
+ puts "ignore = #{v}"
97
+ @ignore_app_stop_failures = v
98
+ end
99
+
100
+ parser
101
+ end
102
+
103
+ alias push_cli_hook deploy_cli_hook
104
+
99
105
  private
100
106
 
101
107
  # By default, use the stack name as the application name, unless one has been
@@ -333,6 +339,17 @@ class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
333
339
  }
334
340
  end
335
341
 
342
+ def create_deployment(artifact_repo, version_name)
343
+ cd_client.create_deployment(
344
+ application_name: app_name,
345
+ deployment_group_name: group_name,
346
+ revision: revision_for_artifact_repo(artifact_repo, version_name),
347
+ deployment_config_name: @codedeploy_config,
348
+ description: "Deploying version #{version_name}",
349
+ ignore_application_stop_failures: @ignore_app_stop_failures
350
+ )
351
+ end
352
+
336
353
  def doctor_check_code_deploy_role
337
354
  iam_client.get_role(role_name: @codedeploy_role).role
338
355
  success("#{@codedeploy_role} exists.")
@@ -1,5 +1,4 @@
1
1
  require 'json'
2
- require_relative 'stack_template'
3
2
 
4
3
  module Moonshot
5
4
  # Handles JSON formatted AWS template files.
@@ -1,12 +1,3 @@
1
- require_relative 'creds_helper'
2
- require_relative 'doctor_helper'
3
-
4
- require_relative 'yaml_stack_template'
5
- require_relative 'json_stack_template'
6
- require_relative 'stack_parameter_printer'
7
- require_relative 'stack_output_printer'
8
- require_relative 'stack_asg_printer'
9
- require_relative 'unicode_table'
10
1
  require 'yaml'
11
2
 
12
3
  module Moonshot
@@ -44,22 +35,21 @@ module Moonshot
44
35
  should_wait ? wait_for_stack_state(:stack_create_complete, 'created') : true
45
36
  end
46
37
 
47
- def update
38
+ def update(dry_run:, force:)
48
39
  raise "No stack found #{@name.blue}!" unless stack_exists?
49
40
 
50
- should_wait = true
51
- @ilog.start "Updating #{stack_name}." do |s|
52
- if update_stack
53
- s.success "Initiated update for #{stack_name}."
54
- else
55
- s.success 'No Stack update required.'
56
- should_wait = false
57
- end
41
+ change_set = ChangeSet.new(new_change_set, @name)
42
+ wait_for_change_set(change_set)
43
+ return unless change_set.valid?
44
+
45
+ if dry_run
46
+ change_set.display_changes
47
+ elsif !force
48
+ change_set.display_changes
49
+ change_set.confirm? || raise('ChangeSet rejected!')
58
50
  end
59
51
 
60
- success = should_wait ? wait_for_stack_state(:stack_update_complete, 'updated') : true
61
- raise 'Failed to update the CloudFormation Stack.' unless success
62
- success
52
+ execute_change_set(change_set)
63
53
  end
64
54
 
65
55
  def delete
@@ -131,16 +121,6 @@ module Moonshot
131
121
  end
132
122
  end
133
123
 
134
- # Build a hash of overrides that would be applied to this stack by an
135
- # update.
136
- def overrides
137
- if File.exist?(parameters_file)
138
- YAML.load_file(parameters_file) || {}
139
- else
140
- {}
141
- end
142
- end
143
-
144
124
  # Return a Hash of the default values defined in the stack template.
145
125
  def default_values
146
126
  h = {}
@@ -151,25 +131,12 @@ module Moonshot
151
131
  end
152
132
 
153
133
  def template
154
- @template ||= load_template_file
134
+ load_template_file
155
135
  end
156
136
 
157
137
  # @return [String] the path to the template file.
158
138
  def template_file
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
168
- end
169
-
170
- # @return [String] the path to the parameters file.
171
- def parameters_file
172
- File.join(@config.project_root, 'cloud_formation', 'parameters', "#{@name}.yml")
139
+ load_template_file.filename
173
140
  end
174
141
 
175
142
  private
@@ -178,32 +145,21 @@ module Moonshot
178
145
  "CloudFormation Stack #{@name.blue}"
179
146
  end
180
147
 
181
- def json_template_path
182
- "#{raw_template_file_name}.json"
183
- end
184
-
185
- def yaml_template_path
186
- "#{raw_template_file_name}.yml"
187
- end
188
-
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)
193
- end
194
-
195
148
  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
206
- end
149
+ templates = [
150
+ YamlStackTemplate.new(File.join(@config.project_root, 'moonshot', 'template.yml')),
151
+ JsonStackTemplate.new(File.join(@config.project_root, 'moonshot', 'template.json')),
152
+
153
+ # Support the legacy file location from Moonshot 1.0.
154
+ YamlStackTemplate.new(
155
+ File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.yml")),
156
+ JsonStackTemplate.new(
157
+ File.join(@config.project_root, 'cloud_formation', "#{@config.app_name}.json"))
158
+ ]
159
+
160
+ template = templates.find(&:exist?)
161
+ raise 'No template found in moonshot/template.{yml,json}!' unless template
162
+ template
207
163
  end
208
164
 
209
165
  def stack_parameters
@@ -232,20 +188,23 @@ module Moonshot
232
188
  raise 'You are not authorized to perform create_stack calls.'
233
189
  end
234
190
 
235
- # @return [Boolean]
236
- # true if a stack update was required and initiated, false otherwise.
237
- def update_stack
238
- cf_client.update_stack(
191
+ def new_change_set
192
+ change_set_name = [
193
+ 'moonshot',
194
+ @name,
195
+ Time.now.utc.to_i.to_s
196
+ ].join('-')
197
+
198
+ cf_client.create_change_set(
199
+ change_set_name: change_set_name,
200
+ description: "Moonshot update command for application '#{Moonshot.config.app_name}'",
239
201
  stack_name: @name,
240
202
  template_body: template.body,
241
203
  capabilities: ['CAPABILITY_IAM'],
242
204
  parameters: @config.parameters.values.map(&:to_cf)
243
205
  )
244
- true
245
- rescue Aws::CloudFormation::Errors::ValidationError => e
246
- raise e.message unless
247
- e.message == 'No updates are to be performed.'
248
- false
206
+
207
+ change_set_name
249
208
  end
250
209
 
251
210
  # TODO: Refactor this into it's own class.
@@ -335,5 +294,28 @@ module Moonshot
335
294
  rescue => e
336
295
  critical('Invalid CloudFormation template!', e.message)
337
296
  end
297
+
298
+ def wait_for_change_set(change_set)
299
+ @ilog.start_threaded "Waiting for ChangeSet #{change_set.name.blue} to be created." do |s|
300
+ change_set.wait_for_change_set
301
+
302
+ if change_set.valid?
303
+ s.success "ChangeSet #{change_set.name.blue} ready!"
304
+ else
305
+ s.failure "ChangeSet failed to create: #{change_set.invalid_reason}"
306
+ end
307
+ end
308
+ end
309
+
310
+ def execute_change_set(change_set)
311
+ @ilog.start_threaded "Executing ChangeSet #{change_set.name.blue} for #{stack_name}." do |s|
312
+ change_set.execute
313
+ s.success "Executed ChangeSet #{change_set.name.blue} for #{stack_name}."
314
+ end
315
+
316
+ success = wait_for_stack_state(:stack_update_complete, 'updated')
317
+ raise 'Failed to update the CloudFormation Stack.' unless success
318
+ success
319
+ end
338
320
  end
339
321
  end
@@ -3,7 +3,6 @@ module Moonshot
3
3
  class StackConfig
4
4
  attr_accessor :parent_stacks
5
5
  attr_accessor :show_all_events
6
- attr_accessor :parameter_strategy
7
6
 
8
7
  def initialize
9
8
  @parent_stacks = []
@@ -1,9 +1,9 @@
1
- require_relative 'stack_parameter'
2
-
3
1
  module Moonshot
4
2
  # A StackTemplate loads the template from disk and stores information
5
3
  # about it.
6
4
  class StackTemplate
5
+ attr_reader :filename
6
+
7
7
  def initialize(filename)
8
8
  @filename = filename
9
9
  end
@@ -1,7 +1,3 @@
1
- require_relative 'asg_rollout_config'
2
- require_relative 'asg_rollout/asg'
3
- require_relative 'asg_rollout/hook_exec_environment'
4
-
5
1
  module Moonshot
6
2
  module Tools
7
3
  class ASGRollout
@@ -1,6 +1,3 @@
1
- require_relative 'asg_instance'
2
- require_relative 'instance_health'
3
-
4
1
  module Moonshot
5
2
  module Tools
6
3
  class ASGRollout
@@ -1,5 +1,4 @@
1
1
  require 'yaml'
2
- require_relative 'stack_template'
3
2
 
4
3
  module Moonshot
5
4
  # Handles YAML formatted AWS template files.
@@ -1,6 +1,5 @@
1
1
  require 'rubygems/package'
2
2
  require 'zlib'
3
- require_relative '../moonshot/creds_helper'
4
3
 
5
4
  module Moonshot
6
5
  module Plugins
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: moonshot
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.1.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cloud Engineering <engineering@acquia.com>
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-11-25 00:00:00.000000000 Z
11
+ date: 2016-11-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: aws-sdk
@@ -184,6 +184,20 @@ dependencies:
184
184
  - - ">="
185
185
  - !ruby/object:Gem::Version
186
186
  version: '0'
187
+ - !ruby/object:Gem::Dependency
188
+ name: require_all
189
+ requirement: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ type: :runtime
195
+ prerelease: false
196
+ version_requirements: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
187
201
  - !ruby/object:Gem::Dependency
188
202
  name: rspec
189
203
  requirement: !ruby/object:Gem::Requirement
@@ -236,6 +250,7 @@ files:
236
250
  - bin/moonshot
237
251
  - lib/default/Moonfile.rb
238
252
  - lib/moonshot.rb
253
+ - lib/moonshot/account_context.rb
239
254
  - lib/moonshot/always_use_default_source.rb
240
255
  - lib/moonshot/artifact_repository/s3_bucket.rb
241
256
  - lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb
@@ -244,8 +259,10 @@ files:
244
259
  - lib/moonshot/build_mechanism/script.rb
245
260
  - lib/moonshot/build_mechanism/travis_deploy.rb
246
261
  - lib/moonshot/build_mechanism/version_proxy.rb
262
+ - lib/moonshot/change_set.rb
247
263
  - lib/moonshot/command.rb
248
264
  - lib/moonshot/command_line.rb
265
+ - lib/moonshot/command_line_dispatcher.rb
249
266
  - lib/moonshot/commands/build.rb
250
267
  - lib/moonshot/commands/console.rb
251
268
  - lib/moonshot/commands/create.rb
@@ -255,7 +272,9 @@ files:
255
272
  - lib/moonshot/commands/list.rb
256
273
  - lib/moonshot/commands/new.rb
257
274
  - lib/moonshot/commands/parameter_arguments.rb
275
+ - lib/moonshot/commands/parent_stack_option.rb
258
276
  - lib/moonshot/commands/push.rb
277
+ - lib/moonshot/commands/show_all_events_option.rb
259
278
  - lib/moonshot/commands/ssh.rb
260
279
  - lib/moonshot/commands/status.rb
261
280
  - lib/moonshot/commands/update.rb
@@ -264,12 +283,10 @@ files:
264
283
  - lib/moonshot/controller.rb
265
284
  - lib/moonshot/controller_config.rb
266
285
  - lib/moonshot/creds_helper.rb
267
- - lib/moonshot/default_strategy.rb
268
286
  - lib/moonshot/deployment_mechanism/code_deploy.rb
269
287
  - lib/moonshot/doctor_helper.rb
270
288
  - lib/moonshot/interactive_logger_proxy.rb
271
289
  - lib/moonshot/json_stack_template.rb
272
- - lib/moonshot/merge_strategy.rb
273
290
  - lib/moonshot/parameter_collection.rb
274
291
  - lib/moonshot/parent_stack_parameter_loader.rb
275
292
  - lib/moonshot/resources.rb
@@ -315,9 +332,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
315
332
  version: '0'
316
333
  required_rubygems_version: !ruby/object:Gem::Requirement
317
334
  requirements:
318
- - - ">="
335
+ - - ">"
319
336
  - !ruby/object:Gem::Version
320
- version: '0'
337
+ version: 1.3.1
321
338
  requirements: []
322
339
  rubyforge_project:
323
340
  rubygems_version: 2.4.8
@@ -1,16 +0,0 @@
1
- module Moonshot
2
- module ParameterStrategy
3
- # Default strategy: use parameter defined in the parameter file
4
- class DefaultStrategy
5
- def parameters(params, _, _)
6
- params.map do |key, _|
7
- {
8
- parameter_key: key,
9
- parameter_value: params[key],
10
- use_previous_value: false
11
- }
12
- end
13
- end
14
- end
15
- end
16
- end
@@ -1,31 +0,0 @@
1
- require 'highline/import'
2
- require_relative 'unicode_table'
3
-
4
- module Moonshot
5
- module ParameterStrategy
6
- # Merge strategy: prefer parameter values defined in the parameter file,
7
- # otherwise use the previously set value on the existing stack.
8
- class MergeStrategy
9
- def parameters(params, stack_params, template)
10
- stack_keys = stack_params.keys.select do |k|
11
- template.parameters.any? { |p| p.name == k }
12
- end
13
-
14
- (params.keys + stack_keys).uniq.map do |key|
15
- if params[key]
16
- {
17
- parameter_key: key,
18
- parameter_value: params[key],
19
- use_previous_value: false
20
- }
21
- else
22
- {
23
- parameter_key: key,
24
- use_previous_value: true
25
- }
26
- end
27
- end
28
- end
29
- end
30
- end
31
- end