moonshot 0.7.7 → 1.0.0.rc1

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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/moonshot +11 -0
  3. data/lib/default/Moonfile.rb +0 -0
  4. data/lib/moonshot.rb +33 -6
  5. data/lib/moonshot/always_use_default_source.rb +17 -0
  6. data/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +140 -3
  7. data/lib/moonshot/ask_user_source.rb +38 -0
  8. data/lib/moonshot/build_mechanism/github_release.rb +1 -1
  9. data/lib/moonshot/build_mechanism/script.rb +1 -1
  10. data/lib/moonshot/command.rb +64 -0
  11. data/lib/moonshot/command_line.rb +150 -0
  12. data/lib/moonshot/commands/build.rb +12 -0
  13. data/lib/moonshot/commands/console.rb +19 -0
  14. data/lib/moonshot/commands/create.rb +37 -0
  15. data/lib/moonshot/commands/delete.rb +12 -0
  16. data/lib/moonshot/commands/deploy.rb +12 -0
  17. data/lib/moonshot/commands/doctor.rb +12 -0
  18. data/lib/moonshot/commands/list.rb +16 -0
  19. data/lib/moonshot/commands/new.rb +99 -0
  20. data/lib/moonshot/commands/parameter_arguments.rb +27 -0
  21. data/lib/moonshot/commands/push.rb +12 -0
  22. data/lib/moonshot/commands/ssh.rb +12 -0
  23. data/lib/moonshot/commands/status.rb +12 -0
  24. data/lib/moonshot/commands/update.rb +29 -0
  25. data/lib/moonshot/commands/version.rb +12 -0
  26. data/lib/moonshot/config.rb +0 -0
  27. data/lib/moonshot/controller.rb +106 -42
  28. data/lib/moonshot/controller_config.rb +31 -13
  29. data/lib/moonshot/deployment_mechanism/code_deploy.rb +17 -7
  30. data/lib/moonshot/json_stack_template.rb +17 -0
  31. data/lib/moonshot/parameter_collection.rb +50 -0
  32. data/lib/moonshot/parent_stack_parameter_loader.rb +51 -0
  33. data/lib/moonshot/resources.rb +3 -3
  34. data/lib/moonshot/resources_helper.rb +2 -2
  35. data/lib/moonshot/ssh_command.rb +31 -0
  36. data/lib/moonshot/ssh_config.rb +1 -1
  37. data/lib/moonshot/stack.rb +66 -77
  38. data/lib/moonshot/stack_list_printer.rb +21 -0
  39. data/lib/moonshot/stack_lister.rb +16 -6
  40. data/lib/moonshot/stack_parameter.rb +64 -0
  41. data/lib/moonshot/stack_parameter_printer.rb +3 -49
  42. data/lib/moonshot/stack_template.rb +13 -25
  43. data/lib/moonshot/task.rb +10 -0
  44. data/lib/moonshot/tools/asg_rollout.rb +1 -1
  45. data/lib/moonshot/tools/asg_rollout/instance_health.rb +1 -1
  46. data/lib/moonshot/tools/asg_rollout_config.rb +1 -1
  47. data/lib/moonshot/yaml_stack_template.rb +17 -0
  48. metadata +51 -9
  49. data/lib/moonshot/cli.rb +0 -220
  50. data/lib/moonshot/environment_parser.rb +0 -32
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Build < Moonshot::Command
4
+ self.usage = 'build VERSION'
5
+ self.description = 'Build a release artifact, ready for deployment'
6
+
7
+ def execute(version_name)
8
+ controller.build_version(version_name)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,19 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Console < Moonshot::Command
4
+ self.usage = 'console [options]'
5
+ self.description = 'Launch a interactive Ruby console with configured access to AWS'
6
+
7
+ def execute
8
+ controller
9
+
10
+ ec2 = Aws::EC2::Client.new
11
+ iam = Aws::IAM::Client.new
12
+ autoscaling = Aws::AutoScaling::Client.new
13
+ cf = Aws::CloudFormation::Client.new
14
+
15
+ Pry.start binding, backtrace: nil
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,37 @@
1
+ require_relative 'parameter_arguments'
2
+
3
+ module Moonshot
4
+ module Commands
5
+ class Create < Moonshot::Command
6
+ include ParameterArguments
7
+
8
+ self.usage = 'create [options]'
9
+ self.description = 'Create a new environment'
10
+
11
+ attr_reader :version, :deploy
12
+
13
+ def parser
14
+ @deploy = true
15
+
16
+ parser = super
17
+ parser.on('-d', '--[no-]deploy', TrueClass, 'Choose if code should be deployed immediately after the stack is created') do |v| # rubocop:disable LineLength
18
+ @deploy = v
19
+ end
20
+
21
+ parser.on('--version VERSION_NAME', 'Version for initial deployment. If unset, a new development build is created from the local directory') do |v| # rubocop:disable LineLength
22
+ @version = v
23
+ end
24
+ end
25
+
26
+ def execute
27
+ controller.create
28
+
29
+ if @deploy && @version.nil?
30
+ controller.push
31
+ elsif @deploy
32
+ controller.deploy_version(@version)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Delete < Moonshot::Command
4
+ self.usage = 'delete [options]'
5
+ self.description = 'Delete an existing environment'
6
+
7
+ def execute
8
+ controller.delete
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Deploy < Moonshot::Command
4
+ self.usage = 'deploy VERSION'
5
+ self.description = 'Deploy a versioned release to the environment'
6
+
7
+ def execute(version_name)
8
+ controller.deploy_version(version_name)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Doctor < Moonshot::Command
4
+ self.usage = 'doctor [options]'
5
+ self.description = 'Run configuration checks against the local environment'
6
+
7
+ def execute
8
+ controller.doctor || raise('One or more checks failed.')
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,16 @@
1
+ require_relative '../stack_lister'
2
+ require_relative '../stack_list_printer'
3
+
4
+ module Moonshot
5
+ module Commands
6
+ class List < Moonshot::Command
7
+ self.usage = 'list [options]'
8
+ self.description = 'List stacks for this application'
9
+
10
+ def execute
11
+ stacks = StackLister.new(controller.config.app_name).list
12
+ StackListPrinter.new(stacks).print
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,99 @@
1
+ module Moonshot
2
+ module Commands
3
+ class New < Moonshot::Command
4
+ self.usage = 'new [options]'
5
+ self.description = 'Creates a new Moonshot project.'
6
+
7
+ DEFAULT_DIRECTORY = File.join(__dir__, '..', '..', 'default').freeze
8
+
9
+ def execute
10
+ warn 'Looks like your project is already set up!'
11
+ end
12
+
13
+ class << self
14
+ def run!(application_name)
15
+ @application_name = application_name
16
+
17
+ create_project_dir
18
+ copy_defaults
19
+ create_file(parameter_path)
20
+ create_file(template_path)
21
+ fill_moonfile
22
+ print_success_message
23
+ end
24
+
25
+ private
26
+
27
+ def cwd
28
+ Dir.pwd
29
+ end
30
+
31
+ def create_project_dir
32
+ raise "Directory '#{@application_name}' already exists!" \
33
+ if Dir.exist?(project_path)
34
+ Dir.mkdir(project_path)
35
+ end
36
+
37
+ def project_path
38
+ @project_path ||= File.join(cwd, @application_name)
39
+ end
40
+
41
+ def copy_defaults
42
+ target_path = File.join(DEFAULT_DIRECTORY.dup, '.')
43
+ FileUtils.cp_r(target_path, project_path)
44
+ end
45
+
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
+ def fill_moonfile
67
+ File.open(moonfile_path, 'w') { |f| f.write generate_moonfile }
68
+ end
69
+
70
+ def generate_moonfile
71
+ <<-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
79
+ end
80
+
81
+ 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/'
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,27 @@
1
+ # rubocop:disable LineLength
2
+ module Moonshot
3
+ module Commands
4
+ module ParameterArguments
5
+ def parser
6
+ parser = super
7
+
8
+ parser.on('--[no-]interactive', TrueClass, 'Use interactive prompts for gathering missing configuration.') do |v|
9
+ Moonshot.config.interactive = v
10
+ end
11
+
12
+ parser.on('--answer-file FILE', '-aFILE', 'Load Stack Parameters from a YAML file') do |v|
13
+ Moonshot.config.answer_file = File.expand_path(v)
14
+ end
15
+
16
+ parser.on('--parameter KEY=VALUE', '-PKEY=VALUE', 'Specify Stack Parameter on the command line') do |v|
17
+ data = v.split('=', 2)
18
+ unless data.size == 2
19
+ raise "Invalid parameter format '#{v}', expected KEY=VALUE (e.g. MyStackParameter=12)"
20
+ end
21
+
22
+ Moonshot.config.parameter_overrides[data[0]] = data[1]
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Push < Moonshot::Command
4
+ self.usage = 'push [options]'
5
+ self.description = 'Build and deploy a development artifact from the working directory'
6
+
7
+ def execute
8
+ controller.push
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Ssh < Moonshot::SSHCommand
4
+ self.usage = 'ssh [options]'
5
+ self.description = 'SSH into the first (or specified) instance on the stack'
6
+
7
+ def execute
8
+ controller.ssh
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Status < Moonshot::Command
4
+ self.usage = 'status [options]'
5
+ self.description = 'Show the status of an existing environment'
6
+
7
+ def execute
8
+ controller.status
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ require_relative 'parameter_arguments'
2
+
3
+ module Moonshot
4
+ module Commands
5
+ class Update < Moonshot::Command
6
+ include ParameterArguments
7
+
8
+ self.usage = 'update [options]'
9
+ self.description = 'Update the CloudFormation stack within an environment.'
10
+
11
+ def execute
12
+ controller.update
13
+ end
14
+
15
+ private
16
+
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}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,12 @@
1
+ module Moonshot
2
+ module Commands
3
+ class Version < Moonshot::Command
4
+ self.usage = 'version'
5
+ self.description = 'Display the version of Moonshot'
6
+
7
+ def execute
8
+ puts Gem.loaded_specs['moonshot'].version
9
+ end
10
+ end
11
+ end
12
+ end
File without changes
@@ -1,10 +1,14 @@
1
1
  require_relative 'ssh_target_selector'
2
2
  require_relative 'ssh_command_builder'
3
3
 
4
+ require_relative 'stack_parameter'
5
+ require_relative 'parameter_collection'
6
+ require_relative 'parent_stack_parameter_loader'
7
+
4
8
  module Moonshot
5
9
  # The Controller coordinates and performs all Moonshot actions.
6
10
  class Controller # rubocop:disable ClassLength
7
- attr_reader :config
11
+ attr_accessor :config
8
12
 
9
13
  def initialize
10
14
  @config = ControllerConfig.new
@@ -12,22 +16,108 @@ module Moonshot
12
16
  end
13
17
 
14
18
  def list
15
- Moonshot::StackLister.new(
16
- @config.app_name, log: @config.logger).list
19
+ Moonshot::StackLister.new(@config.app_name).list
17
20
  end
18
21
 
19
- def create
22
+ def create # rubocop:disable AbcSize
23
+ # Scan the template for all required parameters and configure
24
+ # the ParameterCollection.
25
+ @config.parameters = ParameterCollection.from_template(stack.template)
26
+
27
+ # Import all Outputs from parent stacks as Parameters on this
28
+ # stack.
29
+ ParentStackParameterLoader.new(@config).load!
30
+
31
+ # If there is an answer file, use it to populate parameters.
32
+ if @config.answer_file
33
+ YAML.load_file(@config.answer_file).each do |key, value|
34
+ @config.parameters[key] = value
35
+ end
36
+ end
37
+
38
+ # Apply any overrides configured, such as from the CLI -p option.
39
+ @config.parameter_overrides.each do |key, value|
40
+ @config.parameters[key] = value
41
+ end
42
+
43
+ # Interview the user for missing parameters, using the
44
+ # appropriate prompts.
45
+ @config.parameters.values.each do |sp|
46
+ next if sp.set?
47
+
48
+ parameter_source = @config.parameter_sources.fetch(sp.name,
49
+ @config.default_parameter_source)
50
+ parameter_source.get(sp)
51
+ end
52
+
53
+ # Plugins get the final say on parameters before create,
54
+ # allowing them to manipulate user supplied input and answers
55
+ # file content.
20
56
  run_plugins(:pre_create)
57
+
58
+ # Fail if any parameters are still missing without defaults.
59
+ missing_parameters = @config.parameters.missing_for_create
60
+ unless missing_parameters.empty?
61
+ raise "The following parameters were not provided: #{missing_parameters.map(&:name).join(', ')}" # rubocop:disable LineLength
62
+ end
63
+
21
64
  run_hook(:deploy, :pre_create)
22
65
  stack_ok = stack.create
23
66
  if stack_ok # rubocop:disable GuardClause
24
67
  run_hook(:deploy, :post_create)
25
68
  run_plugins(:post_create)
69
+ else
70
+ raise 'Stack creation failed!'
26
71
  end
27
72
  end
28
73
 
29
- def update
74
+ def update # rubocop:disable AbcSize
75
+ # Scan the template for all required parameters and configure
76
+ # the ParameterCollection.
77
+ @config.parameters = ParameterCollection.from_template(stack.template)
78
+
79
+ # Set all values already provided by the stack to UsePreviousValue.
80
+ stack.parameters.each do |key, value|
81
+ @config.parameters[key].use_previous!(value) if @config.parameters.key?(key)
82
+ end
83
+
84
+ # Import all Outputs from parent stacks as Parameters on this
85
+ # stack.
86
+ ParentStackParameterLoader.new(@config).load_missing_only!
87
+
88
+ # If there is an answer file, use it to populate parameters.
89
+ if @config.answer_file
90
+ YAML.load_file(@config.answer_file).each do |key, value|
91
+ @config.parameters[key] = value
92
+ end
93
+ end
94
+
95
+ # Apply any overrides configured, such as from the CLI -p option.
96
+ @config.parameter_overrides.each do |key, value|
97
+ @config.parameters[key] = value
98
+ end
99
+
100
+ # Interview the user for missing parameters, using the
101
+ # appropriate prompts.
102
+ @config.parameters.values.each do |sp|
103
+ next if sp.set?
104
+
105
+ parameter_source = @config.parameter_sources.fetch(sp.name,
106
+ @config.default_parameter_source)
107
+ parameter_source.get(sp)
108
+ end
109
+
110
+ # Plugins get the final say on parameters before create,
111
+ # allowing them to manipulate user supplied input and answers
112
+ # file content.
30
113
  run_plugins(:pre_update)
114
+
115
+ # Fail if any parameters are still missing without defaults.
116
+ missing_parameters = @config.parameters.missing_for_update
117
+ unless missing_parameters.empty?
118
+ raise "The following parameters were not provided: #{missing_parameters.map(&:name).join(', ')}" # rubocop:disable LineLength
119
+ end
120
+
31
121
  run_hook(:deploy, :pre_update)
32
122
  stack.update
33
123
  run_hook(:deploy, :post_update)
@@ -41,8 +131,8 @@ module Moonshot
41
131
  run_plugins(:post_status)
42
132
  end
43
133
 
44
- def deploy_code
45
- version = "#{stack_name}-#{Time.now.to_i}"
134
+ def push
135
+ version = @config.dev_build_name_proc.call(@config)
46
136
  build_version(version)
47
137
  deploy_version(version)
48
138
  end
@@ -63,6 +153,12 @@ module Moonshot
63
153
  end
64
154
 
65
155
  def delete
156
+ # Populate the current values of parameters, for use by plugins.
157
+ @config.parameters = ParameterCollection.from_template(stack.template)
158
+ stack.parameters.each do |key, value|
159
+ @config.parameters[key].use_previous!(value) if @config.parameters.key?(key)
160
+ end
161
+
66
162
  run_plugins(:pre_delete)
67
163
  run_hook(:deploy, :pre_delete)
68
164
  stack.delete
@@ -71,7 +167,6 @@ module Moonshot
71
167
  end
72
168
 
73
169
  def doctor
74
- # @todo use #run_hook when Stack becomes an InfrastructureProvider
75
170
  success = true
76
171
  success &&= stack.doctor_hook
77
172
  success &&= run_hook(:build, :doctor)
@@ -90,56 +185,25 @@ module Moonshot
90
185
  cb = SSHCommandBuilder.new(@config.ssh_config, @config.ssh_instance)
91
186
  result = cb.build(@config.ssh_command)
92
187
 
93
- puts "Opening SSH connection to #{@config.ssh_instance} (#{result.ip})..."
188
+ warn "Opening SSH connection to #{@config.ssh_instance} (#{result.ip})..."
94
189
  exec(result.cmd)
95
190
  end
96
191
 
97
192
  def stack
98
- @stack ||= Stack.new(stack_name,
99
- app_name: @config.app_name,
100
- log: @config.logger,
101
- ilog: @config.interactive_logger) do |config|
102
- config.parent_stacks = @config.parent_stacks
103
- config.show_all_events = @config.show_all_stack_events
104
- config.parameter_strategy = @config.parameter_strategy
105
- end
193
+ @stack ||= Stack.new(@config)
106
194
  end
107
195
 
108
196
  private
109
197
 
110
- def default_stack_name
111
- user = ENV.fetch('USER').gsub(/\W/, '')
112
- "#{@config.app_name}-dev-#{user}"
113
- end
114
-
115
- def ensure_prefix(name)
116
- if name.start_with?(@config.app_name + '-')
117
- name
118
- else
119
- @config.app_name + "-#{name}"
120
- end
121
- end
122
-
123
- def stack_name
124
- name = @config.environment_name || default_stack_name
125
- if @config.auto_prefix_stack == false
126
- name
127
- else
128
- ensure_prefix(name)
129
- end
130
- end
131
-
132
198
  def resources
133
199
  @resources ||=
134
- Resources.new(stack: stack, log: @config.logger,
135
- ilog: @config.interactive_logger)
200
+ Resources.new(stack: stack, ilog: @config.interactive_logger, controller: self)
136
201
  end
137
202
 
138
203
  def run_hook(type, name, *args)
139
204
  mech = get_mechanism(type)
140
205
  name = name.to_s << '_hook'
141
206
 
142
- @config.logger.debug("Calling hook=#{name} on mech=#{mech.class}")
143
207
  return unless mech && mech.respond_to?(name)
144
208
 
145
209
  mech.resources = resources