awx 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/aws/aws.rb +9 -0
- data/lib/aws/aws_cli.rb +36 -30
- data/lib/aws/aws_cloudformation.rb +59 -36
- data/lib/aws/aws_outputter.rb +33 -2
- data/lib/aws/aws_profile.rb +116 -0
- data/lib/aws/aws_reports.rb +47 -26
- data/lib/awx.rb +98 -45
- data/lib/core/opt.rb +1 -1
- data/lib/routes/aws_cloudformation_create.rb +447 -192
- data/lib/routes/aws_cloudformation_detect_drift.rb +33 -7
- data/lib/routes/aws_deploy.rb +486 -0
- data/lib/routes/aws_dynamo_db.rb +43 -0
- data/lib/routes/aws_list.rb +17 -11
- data/lib/routes/aws_switch.rb +76 -0
- data/lib/version.rb +1 -1
- data/opt/{yml/aws-reports.yml → awx/reports.yml} +15 -12
- data/opt/config/schema.yml +63 -0
- data/opt/config/template.yml +21 -0
- metadata +25 -23
- data/lib/aws/aws_config.rb +0 -39
- data/lib/core/config.rb +0 -127
- data/lib/core/config_unique.rb +0 -64
- data/lib/routes/aws_lambda.rb +0 -122
- data/lib/routes/setup.rb +0 -31
@@ -2,6 +2,8 @@ module AppCommand
|
|
2
2
|
|
3
3
|
class AWSCloudFormationDetectDrift < ::Convoy::ActionCommand::Base
|
4
4
|
|
5
|
+
TMP_OUTPUT = '/tmp/execute-output'
|
6
|
+
|
5
7
|
def execute
|
6
8
|
|
7
9
|
begin
|
@@ -30,13 +32,37 @@ module AppCommand
|
|
30
32
|
end
|
31
33
|
|
32
34
|
def opts_routing
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
35
|
+
threads = []
|
36
|
+
puts
|
37
|
+
stacks = App::AWSCloudFormation::get_stacks
|
38
|
+
puts
|
39
|
+
system("rm #{TMP_OUTPUT}")
|
40
|
+
system("touch #{TMP_OUTPUT}")
|
41
|
+
stacks.each do |stack|
|
42
|
+
sleep(0.01)
|
43
|
+
threads << Thread.new {
|
44
|
+
cmd = "aws cloudformation detect-stack-drift --stack-name #{stack[:name]} --region #{stack[:region]} --profile #{App::AWSProfile::get_profile_name} >> #{TMP_OUTPUT} 2>&1"
|
45
|
+
App::AWSOutputter::output_cli_command(cmd)
|
46
|
+
`#{cmd}`
|
47
|
+
}
|
48
|
+
end
|
49
|
+
sleep(0.1)
|
50
|
+
puts
|
51
|
+
# Display spinner while waiting for threads to finish.
|
52
|
+
Blufin::Terminal::execute_proc("AWS \xe2\x80\x94 Detecting Drift...", Proc.new {
|
53
|
+
threads.each { |thread| thread.join }
|
54
|
+
})
|
55
|
+
output = `cat #{TMP_OUTPUT}`
|
56
|
+
output.split("\n").each do |line|
|
57
|
+
line = line.strip
|
58
|
+
next if line == ''
|
59
|
+
next if line =~ /^\{$/
|
60
|
+
next if line =~ /^\}$/
|
61
|
+
next if line =~ /"StackDriftDetectionId":\s*"[A-Za-z0-9-]+"/
|
62
|
+
puts
|
63
|
+
puts "\x1B[38;5;196m#{line}\x1B[0m"
|
64
|
+
end
|
65
|
+
puts
|
40
66
|
end
|
41
67
|
|
42
68
|
end
|
@@ -0,0 +1,486 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module AppCommand
|
4
|
+
|
5
|
+
class AWSDeploy < ::Convoy::ActionCommand::Base
|
6
|
+
|
7
|
+
VALID_STACK_STATUSES = %w(CREATE_COMPLETE UPDATE_COMPLETE UPDATE_COMPLETE_CLEANUP_IN_PROGRESS)
|
8
|
+
MATCHERS_KEY_MAP = {
|
9
|
+
'Parameters' => %w(ParameterKey ParameterValue),
|
10
|
+
'Project' => nil, # Special matcher used for system values.
|
11
|
+
'Tags' => %w(Key Value),
|
12
|
+
'Outputs' => %w(OutputKey OutputValue)
|
13
|
+
}
|
14
|
+
|
15
|
+
def execute
|
16
|
+
|
17
|
+
begin
|
18
|
+
|
19
|
+
@opts = command_options
|
20
|
+
@args = arguments
|
21
|
+
|
22
|
+
@projects = nil
|
23
|
+
@project = nil
|
24
|
+
@profile = App::AWSProfile::get_profile
|
25
|
+
|
26
|
+
Blufin::Projects::init(@profile, App::CONFIG_FILE)
|
27
|
+
|
28
|
+
opts_validate
|
29
|
+
opts_routing
|
30
|
+
|
31
|
+
rescue => e
|
32
|
+
|
33
|
+
Blufin::Terminal::print_exception(e)
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def opts_validate
|
40
|
+
|
41
|
+
@projects = Blufin::Projects::get_projects
|
42
|
+
|
43
|
+
# Throw Exception if there are no deployments. The script should never get this far.
|
44
|
+
raise RuntimeError, 'There are currently no deployments. In this case, the deploy option should be hidden from the main menu.' unless @projects.any?
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
def opts_routing
|
49
|
+
|
50
|
+
# Select project.
|
51
|
+
system('clear')
|
52
|
+
Blufin::Terminal::custom('DEPLOY', 55, 'Select the Project you want to deploy:')
|
53
|
+
project_name = Blufin::Terminal::prompt_select('Select Project:', @projects.keys)
|
54
|
+
puts
|
55
|
+
project_id = Blufin::Terminal::prompt_select('Select Project ID (codebase):', @projects[project_name].keys)
|
56
|
+
|
57
|
+
@project = @projects[project_name][project_id]
|
58
|
+
|
59
|
+
[# If missing required properties, bomb-out!
|
60
|
+
Blufin::Projects::BUILD,
|
61
|
+
Blufin::Projects::DEPLOY,
|
62
|
+
Blufin::Projects::SERVERS,
|
63
|
+
Blufin::Projects::TARGETS
|
64
|
+
].each do |required_property|
|
65
|
+
unless @project.has_key?(required_property)
|
66
|
+
|
67
|
+
# Lambda projects do not need Build properties.
|
68
|
+
next if [Blufin::Projects::BUILD].include?(required_property) && @project[Blufin::Projects::TYPE] == Blufin::Projects::TYPE_LAMBDA
|
69
|
+
|
70
|
+
project_yaml = @project.to_yaml.split("\n")
|
71
|
+
project_yaml.shift
|
72
|
+
has_content = false
|
73
|
+
project_yaml.each { |x| has_content = true if x.strip.length > 0 }
|
74
|
+
Blufin::Terminal::warning("This project cannot be deployed because the #{Blufin::Terminal::format_highlight(required_property)} property is #{Blufin::Terminal::format_invalid('EMPTY')}.", has_content ? project_yaml : nil)
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
case @project[Blufin::Projects::TYPE]
|
80
|
+
when Blufin::Projects::TYPE_ALEXA
|
81
|
+
|
82
|
+
# TODO - Finish this.
|
83
|
+
raise RuntimeError, 'Not yet implemented!'
|
84
|
+
|
85
|
+
when Blufin::Projects::TYPE_API
|
86
|
+
|
87
|
+
# TODO - Finish this.
|
88
|
+
raise RuntimeError, 'Not yet implemented!'
|
89
|
+
|
90
|
+
when Blufin::Projects::TYPE_LAMBDA
|
91
|
+
deploy_lambda(project_name, @project, @profile)
|
92
|
+
when Blufin::Projects::TYPE_UI
|
93
|
+
deploy_ui(project_name, @project, @profile)
|
94
|
+
else
|
95
|
+
raise RuntimeError, "Unhandled project type: #{@project[Blufin::Projects::TYPE]}"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Lambda Deployments.
|
100
|
+
# # @return void
|
101
|
+
def deploy_lambda(project_name, project, profile)
|
102
|
+
|
103
|
+
project_id = project[Blufin::Projects::DEPLOYMENT_ID]
|
104
|
+
|
105
|
+
deployment_options = []
|
106
|
+
project[Blufin::Projects::TARGETS].each do |environment, data|
|
107
|
+
region = data[:region]
|
108
|
+
data[:environment] = environment
|
109
|
+
deployment_id = "#{region}-#{environment}"
|
110
|
+
deployment_options << {
|
111
|
+
:text => deployment_id,
|
112
|
+
:value => data
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
puts
|
117
|
+
|
118
|
+
# Select which Stack(s) to deploy to.
|
119
|
+
targets = []
|
120
|
+
targets = Blufin::Terminal::prompt_multi_select('Select Target Environment(s):', deployment_options) until targets.any?
|
121
|
+
|
122
|
+
deploy_script, deploy_commands = get_deploy_commands(project)
|
123
|
+
|
124
|
+
# Get final commands (with region/environment correctly inserted).
|
125
|
+
dcmd_final = {}
|
126
|
+
targets.each do |target|
|
127
|
+
converted_commands = convert_commands(deploy_commands, target, profile, target[:region], Blufin::Projects::TYPE_LAMBDA)
|
128
|
+
dcmd_final[Digest::SHA2.hexdigest(converted_commands.inspect.to_s)] = {
|
129
|
+
:description => project[Blufin::Projects::DEPLOYMENT_ID],
|
130
|
+
:commands => converted_commands
|
131
|
+
}
|
132
|
+
end
|
133
|
+
|
134
|
+
# Build deploy-descriptions array (after targets are selected).
|
135
|
+
deploy_descriptions = []
|
136
|
+
targets.each { |x| deploy_descriptions << "#{project[Blufin::Projects::DEPLOYMENT_ID]} \xe2\x80\x94 #{Blufin::Terminal::format_highlight(x[:environment])}" }
|
137
|
+
|
138
|
+
# Show prompt and perform deployment.
|
139
|
+
perform_deployment({}, nil, dcmd_final, deploy_descriptions, deploy_script['Path'], project, project_id, project_name, verbose: true, async: false, pbl: false)
|
140
|
+
|
141
|
+
end
|
142
|
+
|
143
|
+
# UI Deployments.
|
144
|
+
# # @return void
|
145
|
+
def deploy_ui(project_name, project, profile)
|
146
|
+
|
147
|
+
project_id = project[Blufin::Projects::DEPLOYMENT_ID]
|
148
|
+
has_valid_option = false
|
149
|
+
|
150
|
+
# Make call to AWS to get available stacks.
|
151
|
+
deployments = AppCommand::AWSDeploy::get_deployments(project, profile, silent: false)
|
152
|
+
|
153
|
+
# Build deployment option(s).
|
154
|
+
deployment_options = []
|
155
|
+
deployments.each do |deployment_id, deployment_arr|
|
156
|
+
if deployment_arr.is_a?(Array)
|
157
|
+
deployment_arr.each do |deployment|
|
158
|
+
if deployment.is_a?(Hash)
|
159
|
+
disabled = nil
|
160
|
+
deployment_option = {
|
161
|
+
:text => deployment['Description'],
|
162
|
+
:value => deployment
|
163
|
+
}
|
164
|
+
if deployment.has_key?('StackStatus') && !VALID_STACK_STATUSES.include?(deployment['StackStatus'])
|
165
|
+
disabled = deployment['StackStatus']
|
166
|
+
else
|
167
|
+
has_valid_option = true
|
168
|
+
end
|
169
|
+
deployment_option[:disabled] = disabled unless disabled.nil?
|
170
|
+
deployment_options << deployment_option
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# If no active stacks, bomb-out!
|
177
|
+
if !has_valid_option || !deployment_options.any?
|
178
|
+
error_output = []
|
179
|
+
deployment_options.each do |dep_opt|
|
180
|
+
error_output << "#{dep_opt[:text]} \xe2\x80\x94 \x1B[38;5;196m#{dep_opt[:disabled]}"
|
181
|
+
end
|
182
|
+
Blufin::Terminal::error("Cannot #{Blufin::Terminal::format_action('deploy')} because there currently are no available/active Stack(s).", error_output.any? ? error_output : nil, true, false)
|
183
|
+
end
|
184
|
+
|
185
|
+
# Select which Stack(s) to deploy to.
|
186
|
+
targets = []
|
187
|
+
targets = Blufin::Terminal::prompt_multi_select('Select Target Environment(s):', deployment_options) until targets.any?
|
188
|
+
|
189
|
+
# Get build/deploy command(s).
|
190
|
+
build_script, build_commands = get_build_commands(project)
|
191
|
+
deploy_script, deploy_commands = get_deploy_commands(project)
|
192
|
+
|
193
|
+
bcmd_final = {}
|
194
|
+
dcmd_final = {}
|
195
|
+
# Loop targets and start building/deploying stuff.
|
196
|
+
targets.each do |target|
|
197
|
+
# Extract region from StackId, IE: arn:aws:cloudformation:us-west-2:255332876236:stack/ui-s3-route53-cloudfront...
|
198
|
+
raise RuntimeError, 'Could not find key: StackId' unless target.has_key?('StackId')
|
199
|
+
region = target['StackId'].gsub(/^arn:aws:cloudformation:/, '').split(':')[0]
|
200
|
+
build_commands_converted = convert_commands(build_commands, target, profile['Profile'], region, Blufin::Projects::TYPE_UI)
|
201
|
+
deploy_commands_converted = convert_commands(deploy_commands, target, profile['Profile'], region, Blufin::Projects::TYPE_UI)
|
202
|
+
# Convert script to Hash (to identify duplicates).
|
203
|
+
bc_key = Digest::SHA2.hexdigest(build_commands_converted.inspect.to_s)
|
204
|
+
dc_key = Digest::SHA2.hexdigest(deploy_commands_converted.inspect.to_s)
|
205
|
+
# Add to build/deploy hashes only if not duplicate.
|
206
|
+
bcmd_final[bc_key] = {
|
207
|
+
:description => target['Description'],
|
208
|
+
:commands => build_commands_converted,
|
209
|
+
} unless bcmd_final.has_key?(bc_key)
|
210
|
+
dcmd_final[dc_key] = {
|
211
|
+
:description => target['Description'],
|
212
|
+
:commands => deploy_commands_converted,
|
213
|
+
} unless dcmd_final.has_key?(dc_key)
|
214
|
+
end
|
215
|
+
|
216
|
+
deploy_descriptions = []
|
217
|
+
dcmd_final.each { |x| deploy_descriptions << x[1][:description] }
|
218
|
+
|
219
|
+
# Show prompt and perform deployment.
|
220
|
+
perform_deployment(bcmd_final, build_script, dcmd_final, deploy_descriptions, deploy_script['Path'], project, project_id, project_name)
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
# Final confirmation prompt before the deploys run.
|
225
|
+
# @return void
|
226
|
+
def perform_deployment(bcmd_final, build_script, dcmd_final, deploy_descriptions, deploy_path, project, project_id, project_name, verbose: false, async: true, pbl: true)
|
227
|
+
if Blufin::Terminal::prompt_yes_no("You're about to deploy the following: #{App::AWSOutputter::render_selection(project_name, project_id)}", deploy_descriptions, 'Deploy Project(s)?')
|
228
|
+
begin
|
229
|
+
# Execute the build(s).
|
230
|
+
if @opts[:skip_build] || build_script.nil?
|
231
|
+
# Only display this message if we specifically skipped the build.
|
232
|
+
Blufin::Terminal::info('Skipping build...', nil, false) if @opts[:skip_build]
|
233
|
+
else
|
234
|
+
bcmd_final.each do |x|
|
235
|
+
path = "#{File.expand_path(project[Blufin::Projects::REPO_ROOT])}/#{Blufin::Strings::remove_surrounding_slashes(project[Blufin::Projects::REPO_PATH])}/#{Blufin::Strings::remove_surrounding_slashes(build_script['Path'])}".gsub(/\/$/, '')
|
236
|
+
x[1][:commands].each do |command|
|
237
|
+
raise RuntimeError, "Command failed: #{command}" unless Blufin::Terminal::command(command, path, pbl: true, tbl: true)
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
# Execute the deploys(s).
|
242
|
+
dcmd_descriptions = []
|
243
|
+
dcmd_threads = []
|
244
|
+
dcmd_semaphore = Mutex.new
|
245
|
+
dcmd_final.each_with_index do |x, idx1|
|
246
|
+
path = "#{File.expand_path(project[Blufin::Projects::REPO_ROOT])}/#{Blufin::Strings::remove_surrounding_slashes(project[Blufin::Projects::REPO_PATH])}/#{Blufin::Strings::remove_surrounding_slashes(deploy_path)}".gsub(/\/$/, '')
|
247
|
+
if async
|
248
|
+
sleep(0.01)
|
249
|
+
dcmd_threads << Thread.new {
|
250
|
+
dcmd_descriptions << x[1][:description]
|
251
|
+
dcmd_semaphore.synchronize do
|
252
|
+
x[1][:commands].each do |command|
|
253
|
+
App::AWSOutputter::output_cli_command(command, path)
|
254
|
+
end
|
255
|
+
end
|
256
|
+
x[1][:commands].each do |command|
|
257
|
+
raise RuntimeError, "Command failed: #{command}" unless Blufin::Terminal::execute(command, path, verbose: verbose)
|
258
|
+
end
|
259
|
+
}
|
260
|
+
else
|
261
|
+
x[1][:commands].each_with_index do |command, idx2|
|
262
|
+
# This basically fixes spacing.
|
263
|
+
last_in_loop = idx1 == (dcmd_final.length - 1) && idx2 == (x[1][:commands].length - 1)
|
264
|
+
tbl = !pbl && last_in_loop ? false : true
|
265
|
+
raise RuntimeError, "Command failed: #{command}" unless Blufin::Terminal::command(command, path, pbl: true, tbl: tbl)[0]
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
sleep(0.1)
|
270
|
+
puts
|
271
|
+
if async
|
272
|
+
# Display spinner while waiting for threads to finish.
|
273
|
+
Blufin::Terminal::execute_proc('Waiting for deploy(s) to finish...', Proc.new {
|
274
|
+
dcmd_threads.each { |thread| thread.join }
|
275
|
+
})
|
276
|
+
end
|
277
|
+
# Success message.
|
278
|
+
Blufin::Terminal::success('Deploy(s) were successful.', nil, pbl)
|
279
|
+
rescue => e
|
280
|
+
Blufin::Terminal::error('Something went wrong.', e.message, true, false)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
end
|
284
|
+
|
285
|
+
# Select which build script to use. Displays prompt if multiple.
|
286
|
+
# @return Hash
|
287
|
+
def get_build_commands(project)
|
288
|
+
build_scripts = project['Build']
|
289
|
+
build_script_text = 'Select Build Script:'
|
290
|
+
puts
|
291
|
+
if build_scripts.length > 1
|
292
|
+
build_script_options = []
|
293
|
+
build_scripts.each do |script|
|
294
|
+
build_script_options << {
|
295
|
+
:text => script['Script'],
|
296
|
+
:value => script
|
297
|
+
}
|
298
|
+
end
|
299
|
+
build_script = Blufin::Terminal::prompt_select(build_script_text, build_script_options)
|
300
|
+
else
|
301
|
+
build_script = build_scripts[0]
|
302
|
+
puts Blufin::Terminal::display_prompt_text(build_script_text, build_scripts[0]['Script'])
|
303
|
+
end
|
304
|
+
|
305
|
+
# Extract commands (and throw Exceptions if none exist as this should never happen at this point in the script).
|
306
|
+
build_commands = Blufin::Projects::get_scripts[Blufin::Projects::BUILD_SCRIPTS][build_script['Script']]
|
307
|
+
raise RuntimeError, "Invalid build_commands: #{build_commands['Commands'].inspect}" unless build_commands['Commands'].is_a?(Array) && build_commands['Commands'].any?
|
308
|
+
return build_script, build_commands
|
309
|
+
end
|
310
|
+
|
311
|
+
# Select which Deploy Script to use. Displays prompt if multiple.
|
312
|
+
# @return Hash
|
313
|
+
def get_deploy_commands(project)
|
314
|
+
deploy_scripts = project['Deploy']
|
315
|
+
deploy_script_text = 'Select Deploy Script:'
|
316
|
+
puts
|
317
|
+
if deploy_scripts.length > 1
|
318
|
+
deploy_script_options = []
|
319
|
+
deploy_scripts.each do |script|
|
320
|
+
deploy_script_options << {
|
321
|
+
:text => script['Script'],
|
322
|
+
:value => script
|
323
|
+
}
|
324
|
+
end
|
325
|
+
deploy_script = Blufin::Terminal::prompt_select(deploy_script_text, deploy_script_options)
|
326
|
+
else
|
327
|
+
deploy_script = deploy_scripts[0]
|
328
|
+
puts Blufin::Terminal::display_prompt_text(deploy_script_text, deploy_scripts[0]['Script'])
|
329
|
+
end
|
330
|
+
deploy_commands = Blufin::Projects::get_scripts[Blufin::Projects::DEPLOY_SCRIPTS][deploy_script['Script']]
|
331
|
+
raise RuntimeError, "Invalid deploy_commands: #{deploy_commands['Commands'].inspect}" unless deploy_commands['Commands'].is_a?(Array) && deploy_commands['Commands'].any?
|
332
|
+
return deploy_script, deploy_commands
|
333
|
+
end
|
334
|
+
|
335
|
+
# Makes a call to AWS and gets deployments.
|
336
|
+
# @return Hash
|
337
|
+
def self.get_deployments(project, profile, silent: false)
|
338
|
+
calls = {}
|
339
|
+
deployments = {}
|
340
|
+
project_id = project[Blufin::Projects::DEPLOYMENT_ID]
|
341
|
+
project_name = project[Blufin::Projects::PROJECT]
|
342
|
+
project[Blufin::Projects::TARGETS].each do |environment, data|
|
343
|
+
region = data[:region]
|
344
|
+
stack = data[:stack]
|
345
|
+
deployment_id = "#{region}-#{environment}-#{stack}"
|
346
|
+
raise RuntimeError, "Duplicate deployment ID: #{deployment_id}. This should never happen." if deployments.has_key?(deployment_id)
|
347
|
+
deployments[deployment_id] = nil
|
348
|
+
calls[region] = [] unless calls.has_key?(region)
|
349
|
+
calls[region] << {
|
350
|
+
:deployment_id => deployment_id,
|
351
|
+
:environment => environment,
|
352
|
+
:region => region,
|
353
|
+
:stack => stack
|
354
|
+
}
|
355
|
+
end
|
356
|
+
profile = profile[App::AWSProfile::PROFILE]
|
357
|
+
threads = []
|
358
|
+
semaphore = Mutex.new
|
359
|
+
puts unless silent
|
360
|
+
calls.each do |region, data|
|
361
|
+
data.each do |deployment|
|
362
|
+
deployment_id = deployment[:deployment_id]
|
363
|
+
sleep(0.01)
|
364
|
+
threads << Thread.new {
|
365
|
+
cmd = <<TEMPLATE
|
366
|
+
aws resourcegroupstaggingapi get-resources --resource-type-filters cloudformation --tag-filters '[{"Key":"Project","Values":["#{project_name}"]},{"Key":"ProjectId","Values":["#{project_id}"]},{"Key":"Environment","Values":["#{deployment[:environment]}"]},{"Key":"DeploymentStack","Values":["#{deployment[:stack]}"]}]' --profile #{profile} --region #{region}
|
367
|
+
TEMPLATE
|
368
|
+
App::AWSOutputter::output_cli_command(cmd) unless silent
|
369
|
+
res_one = `#{cmd}`
|
370
|
+
begin
|
371
|
+
if res_one.strip != ''
|
372
|
+
data = JSON.parse(res_one)
|
373
|
+
if data.has_key?('ResourceTagMappingList')
|
374
|
+
stacks = data['ResourceTagMappingList']
|
375
|
+
if stacks.is_a?(Array) && stacks.any?
|
376
|
+
stacks_yaml = stacks.to_yaml.split("\n")
|
377
|
+
stacks_yaml.shift
|
378
|
+
stacks.each do |x|
|
379
|
+
raise RuntimeError, "Deployments Array is missing key: #{deployment_id}" unless deployments.has_key?(deployment_id)
|
380
|
+
raise RuntimeError, 'Stack is missing property: ResourceARN' unless x.has_key?('ResourceARN')
|
381
|
+
stack_arn = x['ResourceARN']
|
382
|
+
stack_name = stack_arn.to_s.strip.gsub(/^arn:aws:cloudformation:(.)*:[0-9]{10,14}:stack\//, '')
|
383
|
+
stack_name = stack_name.to_s.strip.gsub(/\/[a-z0-9\-]+$/, '')
|
384
|
+
cmd = <<TEMPLATE
|
385
|
+
aws cloudformation describe-stacks --stack-name #{stack_name} --profile #{profile} --region #{region}
|
386
|
+
TEMPLATE
|
387
|
+
res_two = `#{cmd}`
|
388
|
+
begin
|
389
|
+
semaphore.synchronize do
|
390
|
+
if res_two.strip != ''
|
391
|
+
data = JSON.parse(res_two)
|
392
|
+
if data.is_a?(Hash)
|
393
|
+
if data.has_key?('Stacks')
|
394
|
+
res_stacks = data['Stacks']
|
395
|
+
if res_stacks.length > 1
|
396
|
+
puts res_stacks.to_yaml
|
397
|
+
raise RuntimeError, "Response had more than one item for 'Stacks'."
|
398
|
+
end
|
399
|
+
deployments[deployment_id] = [] unless deployments[deployment_id].is_a?(Array)
|
400
|
+
deployments[deployment_id] << res_stacks[0]
|
401
|
+
end
|
402
|
+
end
|
403
|
+
end
|
404
|
+
end
|
405
|
+
rescue => e
|
406
|
+
puts res_two.inspect
|
407
|
+
raise RuntimeError, "JSON parsing (for: #{cmd}) failed:\n\n#{e.message}"
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
end
|
413
|
+
rescue => e
|
414
|
+
puts res_one.inspect
|
415
|
+
raise RuntimeError, "JSON parsing (for: #{cmd}) failed:\n\n#{e.message}"
|
416
|
+
end
|
417
|
+
}
|
418
|
+
end
|
419
|
+
end
|
420
|
+
sleep(0.1)
|
421
|
+
# Display spinner while waiting for threads to finish.
|
422
|
+
Blufin::Terminal::execute_proc("AWS \xe2\x80\x94 Fetching CloudFormation Stack(s) for: #{App::AWSOutputter::render_selection(project_name, project_id)}", Proc.new {
|
423
|
+
threads.each { |thread| thread.join }
|
424
|
+
}, verbose: !silent)
|
425
|
+
puts unless silent
|
426
|
+
deployments
|
427
|
+
end
|
428
|
+
|
429
|
+
# Takes the commands from project.yml and replaces all the dynamic stuff.
|
430
|
+
# @return Array (of final commands)
|
431
|
+
def convert_commands(original_commands, target, profile, region, deployment_type)
|
432
|
+
final_commands = []
|
433
|
+
raise RuntimeError, "Invalid deployment_type: #{deployment_type}" unless Blufin::Projects::VALID_TYPES.include?(deployment_type)
|
434
|
+
original_commands['Commands'].each do |command|
|
435
|
+
matches = command.scan(/{{[A-Za-z0-9:]+}}/)
|
436
|
+
matches.each do |match|
|
437
|
+
match_clean = match.gsub(/^{{/, '').gsub(/}}$/, '')
|
438
|
+
ms = match_clean.split(':')
|
439
|
+
# Make sure the matcher is compliant :)
|
440
|
+
if ms.length != 2 || !MATCHERS_KEY_MAP.keys.include?(ms[0])
|
441
|
+
puts
|
442
|
+
puts " \x1B[38;5;240m#{command}\x1B[0m"
|
443
|
+
Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(original_commands['Id'])} \xe2\x80\x94 Expected matcher to contain exactly ONE colon (:), instead got \xe2\x86\x92 #{ms.length - 1}", match_clean, true) unless ms.length == 2
|
444
|
+
Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(original_commands['Id'])} \xe2\x80\x94 Invalid matcher key: #{Blufin::Terminal::format_invalid(ms[0])}. Valid keys are:", MATCHERS_KEY_MAP.keys, true) unless MATCHERS_KEY_MAP.keys.include?(ms[0])
|
445
|
+
end
|
446
|
+
extracted_value = nil
|
447
|
+
# Get value by looping through target array.
|
448
|
+
if ms[0] == 'Project'
|
449
|
+
case ms[1]
|
450
|
+
when 'Environment'
|
451
|
+
extracted_value = target[:environment]
|
452
|
+
when 'Profile'
|
453
|
+
extracted_value = profile['Profile']
|
454
|
+
else
|
455
|
+
Blufin::Terminal::error("Unrecognized #{Blufin::Terminal::format_highlight('Project')} matcher: #{Blufin::Terminal::format_invalid(v)}", "The defined matcher is currently not being handled in the AWX #{Blufin::Terminal::format_directory('deploy.rb')} file.", true)
|
456
|
+
end
|
457
|
+
else
|
458
|
+
k = MATCHERS_KEY_MAP[ms[0]][0]
|
459
|
+
v = MATCHERS_KEY_MAP[ms[0]][1]
|
460
|
+
target[ms[0]].each do |item|
|
461
|
+
if item[k] == ms[1]
|
462
|
+
extracted_value = item[v]
|
463
|
+
break
|
464
|
+
end
|
465
|
+
end
|
466
|
+
end
|
467
|
+
# Replace matcher/placeholder with extracted value.
|
468
|
+
if extracted_value.is_a?(String)
|
469
|
+
command = command.gsub(match, extracted_value)
|
470
|
+
else
|
471
|
+
Blufin::Terminal::error("#{Blufin::Terminal::format_highlight(original_commands['Id'])} \xe2\x80\x94 Unable to resolve string value for matcher: #{Blufin::Terminal::format_invalid(match_clean)}", command, false)
|
472
|
+
puts target.to_yaml
|
473
|
+
puts
|
474
|
+
exit
|
475
|
+
end
|
476
|
+
end
|
477
|
+
# If this is an AWS command we need to add --region and --profile flags.
|
478
|
+
command = "#{command.strip} --region #{region} --profile #{profile}" if command =~ /^aws/i
|
479
|
+
final_commands << command
|
480
|
+
end
|
481
|
+
final_commands
|
482
|
+
end
|
483
|
+
|
484
|
+
end
|
485
|
+
|
486
|
+
end
|