moonshot 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/moonshot/artifact_repository/s3_bucket.rb +60 -0
- data/lib/moonshot/artifact_repository/s3_bucket_via_github_releases.rb +89 -0
- data/lib/moonshot/build_mechanism/github_release.rb +148 -0
- data/lib/moonshot/build_mechanism/script.rb +84 -0
- data/lib/moonshot/build_mechanism/travis_deploy.rb +70 -0
- data/lib/moonshot/build_mechanism/version_proxy.rb +55 -0
- data/lib/moonshot/cli.rb +146 -0
- data/lib/moonshot/controller.rb +151 -0
- data/lib/moonshot/controller_config.rb +25 -0
- data/lib/moonshot/creds_helper.rb +28 -0
- data/lib/moonshot/deployment_mechanism/code_deploy.rb +303 -0
- data/lib/moonshot/doctor_helper.rb +57 -0
- data/lib/moonshot/environment_parser.rb +32 -0
- data/lib/moonshot/interactive_logger_proxy.rb +49 -0
- data/lib/moonshot/resources.rb +13 -0
- data/lib/moonshot/resources_helper.rb +24 -0
- data/lib/moonshot/shell.rb +52 -0
- data/lib/moonshot/stack.rb +345 -0
- data/lib/moonshot/stack_asg_printer.rb +151 -0
- data/lib/moonshot/stack_config.rb +12 -0
- data/lib/moonshot/stack_events_poller.rb +56 -0
- data/lib/moonshot/stack_lister.rb +20 -0
- data/lib/moonshot/stack_output_printer.rb +16 -0
- data/lib/moonshot/stack_parameter_printer.rb +73 -0
- data/lib/moonshot/stack_template.rb +35 -0
- data/lib/moonshot/unicode_table.rb +63 -0
- data/lib/moonshot.rb +41 -0
- metadata +239 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
module Moonshot
|
2
|
+
# The Controller coordinates and performs all Moonshot actions.
|
3
|
+
class Controller # rubocop:disable ClassLength
|
4
|
+
def initialize
|
5
|
+
@config = ControllerConfig.new
|
6
|
+
yield @config if block_given?
|
7
|
+
end
|
8
|
+
|
9
|
+
def list
|
10
|
+
Moonshot::StackLister.new(
|
11
|
+
@config.app_name, log: @config.logger).list
|
12
|
+
end
|
13
|
+
|
14
|
+
def create
|
15
|
+
run_plugins(:pre_create)
|
16
|
+
run_hook(:deploy, :pre_create)
|
17
|
+
stack_ok = stack.create
|
18
|
+
if stack_ok # rubocop:disable GuardClause
|
19
|
+
run_hook(:deploy, :post_create)
|
20
|
+
run_plugins(:post_create)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def update
|
25
|
+
run_plugins(:pre_update)
|
26
|
+
run_hook(:deploy, :pre_update)
|
27
|
+
stack.update
|
28
|
+
run_hook(:deploy, :post_update)
|
29
|
+
run_plugins(:post_update)
|
30
|
+
end
|
31
|
+
|
32
|
+
def status
|
33
|
+
run_plugins(:status)
|
34
|
+
run_hook(:deploy, :status)
|
35
|
+
stack.status
|
36
|
+
end
|
37
|
+
|
38
|
+
def deploy_code
|
39
|
+
version = "#{stack_name}-#{Time.now.to_i}"
|
40
|
+
build_version(version)
|
41
|
+
deploy_version(version)
|
42
|
+
end
|
43
|
+
|
44
|
+
def build_version(version_name)
|
45
|
+
run_plugins(:pre_build)
|
46
|
+
run_hook(:build, :pre_build, version_name)
|
47
|
+
run_hook(:build, :build, version_name)
|
48
|
+
run_hook(:build, :post_build, version_name)
|
49
|
+
run_plugins(:post_build)
|
50
|
+
run_hook(:repo, :store, @config.build_mechanism, version_name)
|
51
|
+
end
|
52
|
+
|
53
|
+
def deploy_version(version_name)
|
54
|
+
run_plugins(:pre_deploy)
|
55
|
+
run_hook(:deploy, :deploy, @config.artifact_repository, version_name)
|
56
|
+
run_plugins(:post_deploy)
|
57
|
+
end
|
58
|
+
|
59
|
+
def delete
|
60
|
+
run_plugins(:pre_delete)
|
61
|
+
run_hook(:deploy, :pre_delete)
|
62
|
+
stack.delete
|
63
|
+
run_hook(:deploy, :post_delete)
|
64
|
+
run_plugins(:post_delete)
|
65
|
+
end
|
66
|
+
|
67
|
+
def doctor
|
68
|
+
# @todo use #run_hook when Stack becomes an InfrastructureProvider
|
69
|
+
success = true
|
70
|
+
success &&= stack.doctor_hook
|
71
|
+
success &&= run_hook(:build, :doctor)
|
72
|
+
success &&= run_hook(:repo, :doctor)
|
73
|
+
success &&= run_hook(:deploy, :doctor)
|
74
|
+
results = run_plugins(:doctor)
|
75
|
+
|
76
|
+
success = false if results.value?(false)
|
77
|
+
success
|
78
|
+
end
|
79
|
+
|
80
|
+
def stack
|
81
|
+
@stack ||= Stack.new(stack_name,
|
82
|
+
app_name: @config.app_name,
|
83
|
+
log: @config.logger,
|
84
|
+
ilog: @config.interactive_logger) do |config|
|
85
|
+
config.parent_stacks = @config.parent_stacks
|
86
|
+
config.show_all_events = @config.show_all_stack_events
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
def default_stack_name
|
93
|
+
user = ENV.fetch('USER').gsub(/\W/, '')
|
94
|
+
"#{@config.app_name}-dev-#{user}"
|
95
|
+
end
|
96
|
+
|
97
|
+
def ensure_prefix(name)
|
98
|
+
if name.start_with?(@config.app_name + '-')
|
99
|
+
name
|
100
|
+
else
|
101
|
+
@config.app_name + "-#{name}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def stack_name
|
106
|
+
name = @config.environment_name || default_stack_name
|
107
|
+
if @config.auto_prefix_stack == false
|
108
|
+
name
|
109
|
+
else
|
110
|
+
ensure_prefix(name)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def resources
|
115
|
+
@resources ||=
|
116
|
+
Resources.new(stack: stack, log: @config.logger,
|
117
|
+
ilog: @config.interactive_logger)
|
118
|
+
end
|
119
|
+
|
120
|
+
def run_hook(type, name, *args)
|
121
|
+
mech = get_mechanism(type)
|
122
|
+
name = name.to_s << '_hook'
|
123
|
+
|
124
|
+
@config.logger.debug("Calling hook=#{name} on mech=#{mech.class}")
|
125
|
+
return unless mech && mech.respond_to?(name)
|
126
|
+
|
127
|
+
mech.resources = resources
|
128
|
+
mech.send(name, *args)
|
129
|
+
end
|
130
|
+
|
131
|
+
def run_plugins(type)
|
132
|
+
results = {}
|
133
|
+
@config.plugins.each do |plugin|
|
134
|
+
next unless plugin.respond_to?(type)
|
135
|
+
results[plugin] = plugin.send(type, resources)
|
136
|
+
end
|
137
|
+
|
138
|
+
results
|
139
|
+
end
|
140
|
+
|
141
|
+
def get_mechanism(type)
|
142
|
+
case type
|
143
|
+
when :build then @config.build_mechanism
|
144
|
+
when :repo then @config.artifact_repository
|
145
|
+
when :deploy then @config.deployment_mechanism
|
146
|
+
else
|
147
|
+
raise "Unknown hook type: #{type}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Moonshot
|
2
|
+
# Holds configuration for Moonshot::Controller
|
3
|
+
class ControllerConfig
|
4
|
+
attr_accessor :app_name
|
5
|
+
attr_accessor :artifact_repository
|
6
|
+
attr_accessor :auto_prefix_stack
|
7
|
+
attr_accessor :build_mechanism
|
8
|
+
attr_accessor :deployment_mechanism
|
9
|
+
attr_accessor :environment_name
|
10
|
+
attr_accessor :interactive_logger
|
11
|
+
attr_accessor :logger
|
12
|
+
attr_accessor :parent_stacks
|
13
|
+
attr_accessor :plugins
|
14
|
+
attr_accessor :show_all_stack_events
|
15
|
+
|
16
|
+
def initialize
|
17
|
+
@auto_prefix_stack = true
|
18
|
+
@interactive_logger = InteractiveLogger.new
|
19
|
+
@logger = Logger.new(STDOUT)
|
20
|
+
@parent_stacks = []
|
21
|
+
@plugins = []
|
22
|
+
@show_all_stack_events = false
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Moonshot
|
2
|
+
# Create convenience methods for various AWS client creation.
|
3
|
+
module CredsHelper
|
4
|
+
def cf_client
|
5
|
+
Aws::CloudFormation::Client.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def cd_client
|
9
|
+
Aws::CodeDeploy::Client.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def ec2_client
|
13
|
+
Aws::EC2::Client.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def iam_client
|
17
|
+
Aws::IAM::Client.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_client
|
21
|
+
Aws::AutoScaling::Client.new
|
22
|
+
end
|
23
|
+
|
24
|
+
def s3_client
|
25
|
+
Aws::S3::Client.new
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,303 @@
|
|
1
|
+
require 'colorize'
|
2
|
+
|
3
|
+
# This mechanism is used to deploy software to an auto-scaling group within
|
4
|
+
# a stack. It currently only works with the S3Bucket ArtifactRepository.
|
5
|
+
#
|
6
|
+
# Usage:
|
7
|
+
# class MyApp < Moonshot::CLI
|
8
|
+
# self.artifact_repository = S3Bucket.new('foobucket')
|
9
|
+
# self.deployment_mechanism = CodeDeploy.new(asg: 'AutoScalingGroup')
|
10
|
+
# end
|
11
|
+
class Moonshot::DeploymentMechanism::CodeDeploy # rubocop:disable ClassLength
|
12
|
+
include Moonshot::ResourcesHelper
|
13
|
+
include Moonshot::CredsHelper
|
14
|
+
include Moonshot::DoctorHelper
|
15
|
+
|
16
|
+
# @param asg [String]
|
17
|
+
# The logical name of the AutoScalingGroup to create and manage a Deployment
|
18
|
+
# Group for in CodeDeploy.
|
19
|
+
# @param app_name [String, nil] (nil)
|
20
|
+
# The name of the CodeDeploy Application and Deployment Group. By default,
|
21
|
+
# this is the same as the stack name, and probably what you want. If you
|
22
|
+
# have multiple deployments in a single Stack, they must have unique names.
|
23
|
+
def initialize(asg:, app_name: nil)
|
24
|
+
@asg_logical_id = asg
|
25
|
+
@app_name = app_name
|
26
|
+
end
|
27
|
+
|
28
|
+
def post_create_hook
|
29
|
+
create_application_if_needed
|
30
|
+
create_deployment_group_if_needed
|
31
|
+
|
32
|
+
wait_for_asg_capacity
|
33
|
+
end
|
34
|
+
|
35
|
+
def post_update_hook
|
36
|
+
post_create_hook
|
37
|
+
|
38
|
+
unless deployment_group_ok? # rubocop:disable GuardClause
|
39
|
+
delete_deployment_group
|
40
|
+
create_deployment_group_if_needed
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def status_hook
|
45
|
+
t = Moonshot::UnicodeTable.new('')
|
46
|
+
application = t.add_leaf("CodeDeploy Application: #{app_name}")
|
47
|
+
application.add_line(code_deploy_status_msg)
|
48
|
+
t.draw_children
|
49
|
+
end
|
50
|
+
|
51
|
+
def deploy_hook(artifact_repo, version_name)
|
52
|
+
ilog.start_threaded 'Creating Deployment' do |s|
|
53
|
+
res = cd_client.create_deployment(
|
54
|
+
application_name: app_name,
|
55
|
+
deployment_group_name: app_name,
|
56
|
+
revision: revision_for_artifact_repo(artifact_repo, version_name),
|
57
|
+
deployment_config_name: 'CodeDeployDefault.OneAtATime',
|
58
|
+
description: "Deploying version #{version_name}"
|
59
|
+
)
|
60
|
+
deployment_id = res.deployment_id
|
61
|
+
s.continue "Created Deployment #{deployment_id.blue}."
|
62
|
+
wait_for_deployment(deployment_id, s)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def post_delete_hook
|
67
|
+
ilog.start 'Cleaning up CodeDeploy Application' do |s|
|
68
|
+
if application_exists?
|
69
|
+
cd_client.delete_application(application_name: app_name)
|
70
|
+
s.success "Deleted CodeDeploy Application '#{app_name}'."
|
71
|
+
else
|
72
|
+
s.success "CodeDeploy Application '#{app_name}' does not exist."
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
# By default, use the stack name as the application and deployment group
|
80
|
+
# names, unless one has been provided.
|
81
|
+
def app_name
|
82
|
+
@app_name || stack.name
|
83
|
+
end
|
84
|
+
|
85
|
+
def pretty_app_name
|
86
|
+
"CodeDeploy Application #{app_name.blue}"
|
87
|
+
end
|
88
|
+
|
89
|
+
def pretty_deploy_group
|
90
|
+
"CodeDeploy Deployment Group #{app_name.blue}"
|
91
|
+
end
|
92
|
+
|
93
|
+
def create_application_if_needed
|
94
|
+
ilog.start "Creating #{pretty_app_name}." do |s|
|
95
|
+
if application_exists?
|
96
|
+
s.success "#{pretty_app_name} already exists."
|
97
|
+
else
|
98
|
+
cd_client.create_application(application_name: app_name)
|
99
|
+
s.success "Created #{pretty_app_name}."
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def create_deployment_group_if_needed
|
105
|
+
ilog.start "Creating #{pretty_deploy_group}." do |s|
|
106
|
+
if deployment_group_exists?
|
107
|
+
s.success "CodeDeploy #{pretty_deploy_group} already exists."
|
108
|
+
else
|
109
|
+
create_deployment_group
|
110
|
+
s.success "Created #{pretty_deploy_group}."
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def code_deploy_status_msg
|
116
|
+
case [application_exists?, deployment_group_exists?, deployment_group_ok?]
|
117
|
+
when [true, true, true]
|
118
|
+
'Application and Deployment Group are configured correctly.'.green
|
119
|
+
when [true, true, false]
|
120
|
+
'Deployment Group exists, but not associated with the correct '\
|
121
|
+
"Auto-Scaling Group, try running #{'update'.yellow}."
|
122
|
+
when [true, false, false]
|
123
|
+
"Deployment Group does not exist, try running #{'create'.yellow}."
|
124
|
+
when [false, false, false]
|
125
|
+
'Application and Deployment Group do not exist, try running'\
|
126
|
+
" #{'create'.yellow}."
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def auto_scaling_group
|
131
|
+
@auto_scaling_group ||= load_auto_scaling_group
|
132
|
+
end
|
133
|
+
|
134
|
+
def load_auto_scaling_group
|
135
|
+
asg_name = stack.physical_id_for(@asg_logical_id)
|
136
|
+
unless asg_name
|
137
|
+
raise Thor::Error, "Could not find #{@asg_logical_id} resource in Stack."
|
138
|
+
end
|
139
|
+
|
140
|
+
groups = as_client.describe_auto_scaling_groups(
|
141
|
+
auto_scaling_group_names: [asg_name])
|
142
|
+
if groups.auto_scaling_groups.empty?
|
143
|
+
raise Thor::Error, "Could not find ASG #{asg_name}."
|
144
|
+
end
|
145
|
+
|
146
|
+
groups.auto_scaling_groups.first
|
147
|
+
end
|
148
|
+
|
149
|
+
def asg_name
|
150
|
+
auto_scaling_group.auto_scaling_group_name
|
151
|
+
end
|
152
|
+
|
153
|
+
def application_exists?
|
154
|
+
cd_client.get_application(application_name: app_name)
|
155
|
+
true
|
156
|
+
rescue Aws::CodeDeploy::Errors::ApplicationDoesNotExistException
|
157
|
+
false
|
158
|
+
end
|
159
|
+
|
160
|
+
def deployment_group
|
161
|
+
cd_client.get_deployment_group(
|
162
|
+
application_name: app_name, deployment_group_name: app_name)
|
163
|
+
.deployment_group_info
|
164
|
+
end
|
165
|
+
|
166
|
+
def deployment_group_exists?
|
167
|
+
cd_client.get_deployment_group(
|
168
|
+
application_name: app_name, deployment_group_name: app_name)
|
169
|
+
true
|
170
|
+
rescue Aws::CodeDeploy::Errors::ApplicationDoesNotExistException,
|
171
|
+
Aws::CodeDeploy::Errors::DeploymentGroupDoesNotExistException
|
172
|
+
false
|
173
|
+
end
|
174
|
+
|
175
|
+
def deployment_group_ok?
|
176
|
+
return false unless deployment_group_exists?
|
177
|
+
asg = deployment_group.auto_scaling_groups.first
|
178
|
+
return false unless asg
|
179
|
+
asg.name == auto_scaling_group.auto_scaling_group_name
|
180
|
+
end
|
181
|
+
|
182
|
+
def role
|
183
|
+
iam_client.get_role(role_name: 'CodeDeployRole').role
|
184
|
+
rescue Aws::IAM::Errors::NoSuchEntity
|
185
|
+
raise Thor::Error, 'Did not find an IAM Role: CodeDeployRole'
|
186
|
+
end
|
187
|
+
|
188
|
+
def delete_deployment_group
|
189
|
+
ilog.start "Deleting #{pretty_deploy_group}." do |s|
|
190
|
+
cd_client.delete_deployment_group(
|
191
|
+
application_name: app_name,
|
192
|
+
deployment_group_name: app_name)
|
193
|
+
s.success
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def create_deployment_group
|
198
|
+
cd_client.create_deployment_group(
|
199
|
+
application_name: app_name,
|
200
|
+
deployment_group_name: app_name,
|
201
|
+
service_role_arn: role.arn,
|
202
|
+
auto_scaling_groups: [asg_name])
|
203
|
+
end
|
204
|
+
|
205
|
+
def wait_for_asg_capacity
|
206
|
+
ilog.start 'Waiting for AutoScaling Group to reach capacity...' do |s|
|
207
|
+
loop do
|
208
|
+
asg = load_auto_scaling_group
|
209
|
+
count = asg.instances.count { |i| i.lifecycle_state == 'InService' }
|
210
|
+
break if asg.desired_capacity == count
|
211
|
+
s.continue "DesiredCapacity is #{asg.desired_capacity}, currently #{count} instance(s) are InService." # rubocop:disable LineLength
|
212
|
+
sleep 5
|
213
|
+
end
|
214
|
+
|
215
|
+
s.success 'AutoScaling Group up to capacity!'
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def wait_for_deployment(id, step)
|
220
|
+
loop do
|
221
|
+
sleep 5
|
222
|
+
info = cd_client.get_deployment(deployment_id: id).deployment_info
|
223
|
+
status = info.status
|
224
|
+
|
225
|
+
case status
|
226
|
+
when 'Created', 'Queued', 'InProgress'
|
227
|
+
step.continue "Waiting for Deployment #{id.blue} to complete, current status is '#{status}'." # rubocop:disable LineLength
|
228
|
+
when 'Succeeded'
|
229
|
+
step.success "Deployment #{id.blue} completed successfully!"
|
230
|
+
break
|
231
|
+
when 'Failed', 'Stopped'
|
232
|
+
step.failure "Deployment #{id.blue} failed with status '#{status}'"
|
233
|
+
handle_deployment_failure(id)
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def handle_deployment_failure(deployment_id) # rubocop:disable AbcSize
|
239
|
+
instances = cd_client.list_deployment_instances(deployment_id: deployment_id)
|
240
|
+
.instances_list.map do |instance_id|
|
241
|
+
cd_client.get_deployment_instance(deployment_id: deployment_id,
|
242
|
+
instance_id: instance_id)
|
243
|
+
end
|
244
|
+
|
245
|
+
instances.map(&:instance_summary).each do |inst_summary|
|
246
|
+
next unless inst_summary.status == 'Failed'
|
247
|
+
|
248
|
+
inst_summary.lifecycle_events.each do |event|
|
249
|
+
next unless event.status == 'Failed'
|
250
|
+
|
251
|
+
ilog.error(event.diagnostics.message)
|
252
|
+
event.diagnostics.log_tail.each_line do |line|
|
253
|
+
ilog.error(line)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
raise Thor::Error, 'Deployment was unsuccessful!'
|
259
|
+
end
|
260
|
+
|
261
|
+
def revision_for_artifact_repo(artifact_repo, version_name)
|
262
|
+
case artifact_repo
|
263
|
+
when Moonshot::ArtifactRepository::S3Bucket
|
264
|
+
s3_revision_for(artifact_repo, version_name)
|
265
|
+
when NilClass
|
266
|
+
raise 'Must specify an ArtifactRepository with CodeDeploy. Take a look at the S3Bucket example.' # rubocop:disable LineLength
|
267
|
+
else
|
268
|
+
raise "Cannot use #{artifact_repo.class} to deploy with CodeDeploy."
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def s3_revision_for(artifact_repo, version_name)
|
273
|
+
{
|
274
|
+
revision_type: 'S3',
|
275
|
+
s3_location: {
|
276
|
+
bucket: artifact_repo.bucket_name,
|
277
|
+
key: artifact_repo.filename_for_version(version_name),
|
278
|
+
bundle_type: 'tgz'
|
279
|
+
}
|
280
|
+
}
|
281
|
+
end
|
282
|
+
|
283
|
+
def doctor_check_code_deploy_role
|
284
|
+
iam_client.get_role(role_name: 'CodeDeployRole').role
|
285
|
+
success('CodeDeployRole exists.')
|
286
|
+
rescue => e
|
287
|
+
help = <<-EOF
|
288
|
+
Error: #{e.message}
|
289
|
+
|
290
|
+
For information on provisioning an account for use with CodeDeploy, see:
|
291
|
+
http://docs.aws.amazon.com/codedeploy/latest/userguide/how-to-create-service-role.html
|
292
|
+
EOF
|
293
|
+
critical('Could not find CodeDeployRole, ', help)
|
294
|
+
end
|
295
|
+
|
296
|
+
def doctor_check_auto_scaling_resource_defined
|
297
|
+
if stack.template.resource_names.include?(@asg_logical_id)
|
298
|
+
success("Resource '#{@asg_logical_id}' exists in the CloudFormation template.") # rubocop:disable LineLength
|
299
|
+
else
|
300
|
+
critical("Resource '#{@asg_logical_id}' does not exist in the CloudFormation template!") # rubocop:disable LineLength
|
301
|
+
end
|
302
|
+
end
|
303
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
require 'colorize'
|
3
|
+
|
4
|
+
module Moonshot
|
5
|
+
DoctorCritical = Class.new(RuntimeError)
|
6
|
+
|
7
|
+
#
|
8
|
+
# A series of methods for adding "doctor" checks to a mechanism.
|
9
|
+
#
|
10
|
+
module DoctorHelper
|
11
|
+
def doctor_hook
|
12
|
+
run_all_checks
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def run_all_checks
|
18
|
+
success = true
|
19
|
+
puts
|
20
|
+
puts self.class.name.split('::').last
|
21
|
+
private_methods.each do |meth|
|
22
|
+
begin
|
23
|
+
send(meth) if meth =~ /^doctor_check_/
|
24
|
+
rescue DoctorCritical
|
25
|
+
# Stop running checks in this Mechanism.
|
26
|
+
success = false
|
27
|
+
break
|
28
|
+
rescue => e
|
29
|
+
success = false
|
30
|
+
print ' ✗ '.red
|
31
|
+
puts "Exception while running check: #{e.class}: #{e.message.lines.first}"
|
32
|
+
break
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
success
|
37
|
+
end
|
38
|
+
|
39
|
+
def success(str)
|
40
|
+
print ' ✓ '.green
|
41
|
+
puts str
|
42
|
+
end
|
43
|
+
|
44
|
+
def warning(str, additional_info = nil)
|
45
|
+
print ' ? '.yellow
|
46
|
+
puts str
|
47
|
+
additional_info.lines.each { |l| puts " #{l}" } if additional_info
|
48
|
+
end
|
49
|
+
|
50
|
+
def critical(str, additional_info = nil)
|
51
|
+
print ' ✗ '.red
|
52
|
+
puts str
|
53
|
+
additional_info.lines.each { |l| puts " #{l}" } if additional_info
|
54
|
+
raise DoctorCritical
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Moonshot
|
4
|
+
# This module supports massaging of the incoming environment.
|
5
|
+
module EnvironmentParser
|
6
|
+
def self.parse(log)
|
7
|
+
log.debug('Starting to parse environment.')
|
8
|
+
|
9
|
+
# Ops Bastion servers export AWS_CREDENTIAL_FILE, instead of key and
|
10
|
+
# secret keys, so we support both here. We then set them as environment
|
11
|
+
# variables which will be respected by aws-sdk.
|
12
|
+
parse_credentials_file if ENV.key?('AWS_CREDENTIAL_FILE')
|
13
|
+
|
14
|
+
# Ensure the aws-sdk is able to find a set of credentials.
|
15
|
+
creds = Aws::CredentialProviderChain.new(OpenStruct.new).resolve
|
16
|
+
|
17
|
+
raise 'Unable to find AWS credentials!' unless creds
|
18
|
+
|
19
|
+
log.debug('Environment parsing complete.')
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.parse_credentials_file
|
23
|
+
File.open(ENV.fetch('AWS_CREDENTIAL_FILE')).each_line do |line|
|
24
|
+
key, val = line.chomp.split('=')
|
25
|
+
case key
|
26
|
+
when 'AWSAccessKeyId' then ENV['AWS_ACCESS_KEY_ID'] = val
|
27
|
+
when 'AWSSecretKey' then ENV['AWS_SECRET_ACCESS_KEY'] = val
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Moonshot
|
4
|
+
# This class pretends to be an InteractiveLogger for systems that are
|
5
|
+
# non-interactive.
|
6
|
+
class InteractiveLoggerProxy
|
7
|
+
# Non-interactive version of InteractiveLogger::Step.
|
8
|
+
class Step
|
9
|
+
def initialize(logger)
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def blank
|
14
|
+
end
|
15
|
+
|
16
|
+
def continue(str = nil)
|
17
|
+
@logger.info(str) if str
|
18
|
+
end
|
19
|
+
|
20
|
+
def failure(str = 'Failure')
|
21
|
+
@logger.error(str)
|
22
|
+
end
|
23
|
+
|
24
|
+
def repaint
|
25
|
+
end
|
26
|
+
|
27
|
+
def success(str = 'Success')
|
28
|
+
@logger.info(str)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
extend Forwardable
|
33
|
+
|
34
|
+
def_delegator :@debug, :itself, :debug?
|
35
|
+
def_delegators :@logger, :debug, :error, :info
|
36
|
+
alias msg info
|
37
|
+
|
38
|
+
def initialize(logger, debug: false)
|
39
|
+
@debug = debug
|
40
|
+
@logger = logger
|
41
|
+
end
|
42
|
+
|
43
|
+
def start(str)
|
44
|
+
@logger.info(str)
|
45
|
+
yield Step.new(@logger)
|
46
|
+
end
|
47
|
+
alias start_threaded start
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Moonshot
|
2
|
+
# Resources is a dependency container that holds references to instances
|
3
|
+
# provided to a Mechanism (build, deploy, etc.).
|
4
|
+
class Resources
|
5
|
+
attr_reader :log, :stack, :ilog
|
6
|
+
|
7
|
+
def initialize(log:, stack:, ilog:)
|
8
|
+
@log = log
|
9
|
+
@stack = stack
|
10
|
+
@ilog = ilog
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Moonshot
|
2
|
+
# Provides shorthand methods for accessing resources provided by the Resources
|
3
|
+
# container.
|
4
|
+
module ResourcesHelper
|
5
|
+
attr_writer :resources
|
6
|
+
|
7
|
+
private
|
8
|
+
|
9
|
+
def log
|
10
|
+
raise 'Resources not provided to Mechanism!' unless @resources
|
11
|
+
@resources.log
|
12
|
+
end
|
13
|
+
|
14
|
+
def stack
|
15
|
+
raise 'Resources not provided to Mechanism!' unless @resources
|
16
|
+
@resources.stack
|
17
|
+
end
|
18
|
+
|
19
|
+
def ilog
|
20
|
+
raise 'Resources not provided to Mechanism!' unless @resources
|
21
|
+
@resources.ilog
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|