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.
@@ -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