awx 0.2.0 → 0.3.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 +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
|