awx 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- # TODO - IMPLEMENT
35
-
36
- raise RuntimeError, 'Not yet implemented! Detect Drift.'
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