moonshot 0.7.7 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/bin/moonshot +11 -0
  3. data/lib/default/Moonfile.rb +0 -0
  4. data/lib/moonshot.rb +33 -6
  5. data/lib/moonshot/always_use_default_source.rb +17 -0
  6. data/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +140 -3
  7. data/lib/moonshot/ask_user_source.rb +38 -0
  8. data/lib/moonshot/build_mechanism/github_release.rb +1 -1
  9. data/lib/moonshot/build_mechanism/script.rb +1 -1
  10. data/lib/moonshot/command.rb +64 -0
  11. data/lib/moonshot/command_line.rb +150 -0
  12. data/lib/moonshot/commands/build.rb +12 -0
  13. data/lib/moonshot/commands/console.rb +19 -0
  14. data/lib/moonshot/commands/create.rb +37 -0
  15. data/lib/moonshot/commands/delete.rb +12 -0
  16. data/lib/moonshot/commands/deploy.rb +12 -0
  17. data/lib/moonshot/commands/doctor.rb +12 -0
  18. data/lib/moonshot/commands/list.rb +16 -0
  19. data/lib/moonshot/commands/new.rb +99 -0
  20. data/lib/moonshot/commands/parameter_arguments.rb +27 -0
  21. data/lib/moonshot/commands/push.rb +12 -0
  22. data/lib/moonshot/commands/ssh.rb +12 -0
  23. data/lib/moonshot/commands/status.rb +12 -0
  24. data/lib/moonshot/commands/update.rb +29 -0
  25. data/lib/moonshot/commands/version.rb +12 -0
  26. data/lib/moonshot/config.rb +0 -0
  27. data/lib/moonshot/controller.rb +106 -42
  28. data/lib/moonshot/controller_config.rb +31 -13
  29. data/lib/moonshot/deployment_mechanism/code_deploy.rb +17 -7
  30. data/lib/moonshot/json_stack_template.rb +17 -0
  31. data/lib/moonshot/parameter_collection.rb +50 -0
  32. data/lib/moonshot/parent_stack_parameter_loader.rb +51 -0
  33. data/lib/moonshot/resources.rb +3 -3
  34. data/lib/moonshot/resources_helper.rb +2 -2
  35. data/lib/moonshot/ssh_command.rb +31 -0
  36. data/lib/moonshot/ssh_config.rb +1 -1
  37. data/lib/moonshot/stack.rb +66 -77
  38. data/lib/moonshot/stack_list_printer.rb +21 -0
  39. data/lib/moonshot/stack_lister.rb +16 -6
  40. data/lib/moonshot/stack_parameter.rb +64 -0
  41. data/lib/moonshot/stack_parameter_printer.rb +3 -49
  42. data/lib/moonshot/stack_template.rb +13 -25
  43. data/lib/moonshot/task.rb +10 -0
  44. data/lib/moonshot/tools/asg_rollout.rb +1 -1
  45. data/lib/moonshot/tools/asg_rollout/instance_health.rb +1 -1
  46. data/lib/moonshot/tools/asg_rollout_config.rb +1 -1
  47. data/lib/moonshot/yaml_stack_template.rb +17 -0
  48. metadata +51 -9
  49. data/lib/moonshot/cli.rb +0 -220
  50. data/lib/moonshot/environment_parser.rb +0 -32
@@ -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