moonshot 1.0.0 → 1.1.0.beta1

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.
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