moonshot 0.7.0
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 +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
|