mux_tf 0.14.2 → 0.16.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 69ba3f3800b5025923625f5c71be014ea5f2689d7bef6a6c309453ba8c77d37e
4
- data.tar.gz: 64b7341a04b36e4daed8f63a4fca12e9a966d73269831ebb82faebfee4f4ee55
3
+ metadata.gz: 8f2bcb9ac31603cfab773175a4d81a7ab85e8cd44124dbdf7287234dd70537db
4
+ data.tar.gz: e50b7671fabc4ca94703452daef5fa3a0322f2f4fce103d2a1f02b05012e80a1
5
5
  SHA512:
6
- metadata.gz: 8836967a52fb32c981eee5e84945e4ac3c20873f2a3e11af1137bd7442f7631464a37713cd1621571a6fbceba6e35efb9984ae9ec0e2257b91ee263044105a66
7
- data.tar.gz: 5da9f6af5c83cc977c343650078cb27753ca6feeb82148d0662b3c9e7430a683547deda1a0c6411255c622664dbdb6d13a649d2fb91434eb3c2d3dd849fe69fe
6
+ metadata.gz: 5bf42a828264de913553c309f8acf9274175a7a2a2e9bc71f04534e91649ca6e91727ccf21677406f52d71331771935ead9f2011c4bb6b4780988174b7860767
7
+ data.tar.gz: abaaff54837201173a5c2ccaa796e2c501d48f4f8d72c46687b6275b6541037284be57dd8090c88d4d88c3eeee8983f68b03ce85e6a6bea4d98df914d1e62dbd
@@ -0,0 +1,123 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuxTf
4
+ module Cli
5
+ module Current
6
+ class PlanCommand
7
+ include TerraformHelpers
8
+ include PiotrbCliUtils::CriCommandSupport
9
+ extend PiotrbCliUtils::Util
10
+
11
+ def plan_cmd
12
+ define_cmd("plan", summary: "Re-run plan") do |_opts, _args, _cmd|
13
+ run_validate && run_plan
14
+ end
15
+ end
16
+
17
+ # returns boolean true if succeeded
18
+ def run_validate(level: 1)
19
+ Current.remedy_retry_helper(from: :validate, level: level) do
20
+ validation_info = validate
21
+ PlanFormatter.print_validation_errors(validation_info)
22
+ remedies = PlanFormatter.process_validation(validation_info)
23
+ [remedies, validation_info]
24
+ end
25
+ end
26
+
27
+ def run_plan(targets: [], level: 1, retry_count: 0)
28
+ plan_status, = Current.remedy_retry_helper(from: :plan, level: level, attempt: retry_count) {
29
+ @last_lock_info = nil
30
+
31
+ plan_filename = PlanFilenameGenerator.for_path
32
+
33
+ plan_status, meta = create_plan(plan_filename, targets: targets)
34
+
35
+ Current.print_errors_and_warnings(meta)
36
+
37
+ remedies = detect_remedies_from_plan(meta)
38
+
39
+ if remedies.include?(:unlock)
40
+ @last_lock_info = extract_lock_info(meta)
41
+ throw :abort, [plan_status, meta]
42
+ end
43
+
44
+ throw :abort, [plan_status, meta] if remedies.include?(:auth)
45
+
46
+ [remedies, plan_status, meta]
47
+ }
48
+
49
+ case plan_status
50
+ when :ok
51
+ log "no changes", depth: 1
52
+ when :error
53
+ log "something went wrong", depth: 1
54
+ when :changes
55
+ unless ENV["JSON_PLAN"]
56
+ log "Printing Plan Summary ...", depth: 1
57
+ plan_filename = PlanFilenameGenerator.for_path
58
+ pretty_plan_summary(plan_filename)
59
+ end
60
+ puts plan_summary_text if ENV["JSON_PLAN"]
61
+ when :unknown
62
+ # nothing
63
+ end
64
+
65
+ plan_status
66
+ end
67
+
68
+ private
69
+
70
+ def validate
71
+ log "Validating module ...", depth: 1
72
+ tf_validate # from Terraform Helpers
73
+ end
74
+
75
+ def create_plan(filename, targets: [])
76
+ log "Preparing Plan ...", depth: 1
77
+ exit_code, meta = PlanFormatter.pretty_plan(filename, targets: targets)
78
+ case exit_code
79
+ when 0
80
+ [:ok, meta]
81
+ when 1
82
+ [:error, meta]
83
+ when 2
84
+ [:changes, meta]
85
+ else
86
+ log pastel.yellow("terraform plan exited with an unknown exit code: #{exit_code}")
87
+ [:unknown, meta]
88
+ end
89
+ end
90
+
91
+ def detect_remedies_from_plan(meta)
92
+ remedies = Set.new
93
+ meta[:errors]&.each do |error|
94
+ remedies << :plan if error[:message].include?("timeout while waiting for plugin to start")
95
+ end
96
+ remedies << :unlock if lock_error?(meta)
97
+ remedies << :auth if meta[:need_auth]
98
+ remedies
99
+ end
100
+
101
+ def lock_error?(meta)
102
+ meta && meta["error"] == "lock"
103
+ end
104
+
105
+ def extract_lock_info(meta)
106
+ {
107
+ lock_id: meta["ID"],
108
+ operation: meta["Operation"],
109
+ who: meta["Who"],
110
+ created: meta["Created"]
111
+ }
112
+ end
113
+
114
+ def pretty_plan_summary(filename)
115
+ plan = PlanSummaryHandler.from_file(filename)
116
+ plan.simple_summary do |line|
117
+ log line, depth: 2
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
@@ -11,10 +11,19 @@ module MuxTf
11
11
  extend PiotrbCliUtils::CmdLoop
12
12
  include Coloring
13
13
 
14
- class << self # rubocop:disable Metrics/ClassLength
14
+ class << self
15
+ attr_accessor :plan_command
16
+
15
17
  def run(args)
16
18
  version_check
17
19
 
20
+ self.plan_command = PlanCommand.new
21
+
22
+ ENV["TF_IN_AUTOMATION"] = "1"
23
+ ENV["TF_INPUT"] = "0"
24
+ ENV["TERRAGRUNT_JSON_LOG"] = "1"
25
+ ENV["TERRAGRUNT_FORWARD_TF_STDOUT"] = "1"
26
+
18
27
  if args[0] == "cli"
19
28
  cmd_loop
20
29
  return
@@ -36,17 +45,14 @@ module MuxTf
36
45
  folder_name = File.basename(Dir.getwd)
37
46
  log "Processing #{pastel.cyan(folder_name)} ..."
38
47
 
39
- ENV["TF_IN_AUTOMATION"] = "1"
40
- ENV["TF_INPUT"] = "0"
41
-
42
- return launch_cmd_loop(:error) unless run_validate
48
+ return launch_cmd_loop(:error) unless @plan_command.run_validate
43
49
 
44
50
  if ENV["TF_UPGRADE"]
45
51
  upgrade_status, _upgrade_meta = run_upgrade
46
52
  return launch_cmd_loop(:error) unless upgrade_status == :ok
47
53
  end
48
54
 
49
- plan_status = run_plan
55
+ plan_status = @plan_command.run_plan
50
56
 
51
57
  case plan_status
52
58
  when :ok
@@ -60,23 +66,6 @@ module MuxTf
60
66
  end
61
67
  end
62
68
 
63
- def plan_filename
64
- PlanFilenameGenerator.for_path
65
- end
66
-
67
- private
68
-
69
- def version_check
70
- return unless VersionCheck.has_updates?
71
-
72
- log pastel.yellow("=" * 80)
73
- log "New version of #{pastel.cyan('mux_tf')} is available!"
74
- log "You are currently on version: #{pastel.yellow(VersionCheck.current_gem_version)}"
75
- log "Latest version found is: #{pastel.green(VersionCheck.latest_gem_version)}"
76
- log "Run `#{pastel.green('gem install mux_tf')}` to update!"
77
- log pastel.yellow("=" * 80)
78
- end
79
-
80
69
  # block is expected to return a touple, the first element is a list of remedies
81
70
  # the rest are any additional results
82
71
  def remedy_retry_helper(from:, level: 1, attempt: 0, &block)
@@ -86,24 +75,61 @@ module MuxTf
86
75
  remedies, *results = block.call
87
76
  return results if remedies.empty?
88
77
 
89
- remedy_status, _remedy_results = process_remedies(remedies, from: from, level: level)
78
+ remedy_status, remedy_results = process_remedies(remedies, from: from, level: level)
79
+ throw :abort, false if remedy_results[:user_error]
90
80
  return remedy_status if remedy_status
91
81
  end
92
82
  log "!! giving up because attempt: #{attempt}"
93
83
  end
94
84
  end
95
85
 
96
- # returns boolean true if succeeded
97
- def run_validate(level: 1)
98
- remedy_retry_helper(from: :validate, level: level) do
99
- validation_info = validate
100
- PlanFormatter.print_validation_errors(validation_info)
101
- remedies = PlanFormatter.process_validation(validation_info)
102
- [remedies, validation_info]
86
+ def print_errors_and_warnings(meta)
87
+ message = []
88
+ message << pastel.yellow("#{meta[:warnings].length} Warnings") if meta[:warnings]
89
+ message << pastel.red("#{meta[:errors].length} Errors") if meta[:errors]
90
+ if message.length.positive?
91
+ log ""
92
+ log "Encountered: #{message.join(' and ')}"
93
+ log ""
94
+ end
95
+
96
+ meta[:warnings]&.each do |warning|
97
+ log "-" * 20
98
+ log pastel.yellow("Warning: #{warning[:message]}")
99
+ warning[:body]&.each do |line|
100
+ log pastel.yellow(line), depth: 1
101
+ end
102
+ log ""
103
103
  end
104
+
105
+ meta[:errors]&.each do |error|
106
+ log "-" * 20
107
+ log pastel.red("Error: #{error[:message]}")
108
+ error[:body]&.each do |line|
109
+ log pastel.red(line), depth: 1
110
+ end
111
+ log ""
112
+ end
113
+
114
+ return unless message.length.positive?
115
+
116
+ log ""
117
+ end
118
+
119
+ private
120
+
121
+ def version_check
122
+ return unless VersionCheck.has_updates?
123
+
124
+ log pastel.yellow("=" * 80)
125
+ log "New version of #{pastel.cyan('mux_tf')} is available!"
126
+ log "You are currently on version: #{pastel.yellow(VersionCheck.current_gem_version)}"
127
+ log "Latest version found is: #{pastel.green(VersionCheck.latest_gem_version)}"
128
+ log "Run `#{pastel.green('gem install mux_tf')}` to update!"
129
+ log pastel.yellow("=" * 80)
104
130
  end
105
131
 
106
- def process_remedies(remedies, from: nil, level: 1, retry_count: 0) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
132
+ def process_remedies(remedies, from: nil, level: 1, retry_count: 0)
107
133
  remedies = remedies.dup
108
134
  remedy = nil
109
135
  wrap_log = lambda do |msg, color: nil|
@@ -136,7 +162,7 @@ module MuxTf
136
162
  if remedies.delete?(:plan)
137
163
  remedy = :plan
138
164
  log wrap_log["Running terraform plan ..."], depth: 2
139
- plan_status = run_plan(retry_count: retry_count)
165
+ plan_status = @plan_command.run_plan(retry_count: retry_count)
140
166
  results[:plan_status] = plan_status
141
167
  return [false, results] unless [:ok, :changes].include?(plan_status)
142
168
  end
@@ -160,6 +186,15 @@ module MuxTf
160
186
  log wrap_log["-" * 40, color: :red]
161
187
  log wrap_log["!! User Error, Please fix the issue and try again", color: :red]
162
188
  log wrap_log["-" * 40, color: :red]
189
+ results[:user_error] = true
190
+ return [false, results]
191
+ end
192
+ if remedies.delete? :auth
193
+ remedy = :auth
194
+ log wrap_log["auth error encountered!", color: :red]
195
+ log wrap_log["-" * 40, color: :red]
196
+ log wrap_log["!! Auth Error, Please fix the issue and try again", color: :red]
197
+ log wrap_log["-" * 40, color: :red]
163
198
  return [false, results]
164
199
  end
165
200
 
@@ -174,27 +209,6 @@ module MuxTf
174
209
  [true, results]
175
210
  end
176
211
 
177
- def validate
178
- log "Validating module ...", depth: 1
179
- tf_validate.parsed_output
180
- end
181
-
182
- def create_plan(filename, targets: [])
183
- log "Preparing Plan ...", depth: 1
184
- exit_code, meta = PlanFormatter.pretty_plan(filename, targets: targets)
185
- case exit_code
186
- when 0
187
- [:ok, meta]
188
- when 1
189
- [:error, meta]
190
- when 2
191
- [:changes, meta]
192
- else
193
- log pastel.yellow("terraform plan exited with an unknown exit code: #{exit_code}")
194
- [:unknown, meta]
195
- end
196
- end
197
-
198
212
  def launch_cmd_loop(status)
199
213
  return if ENV["NO_CMD"]
200
214
 
@@ -232,7 +246,7 @@ module MuxTf
232
246
  def build_root_cmd
233
247
  root_cmd = define_cmd(nil)
234
248
 
235
- root_cmd.add_command(plan_cmd)
249
+ root_cmd.add_command(@plan_command.plan_cmd)
236
250
  root_cmd.add_command(apply_cmd)
237
251
  root_cmd.add_command(shell_cmd)
238
252
  root_cmd.add_command(force_unlock_cmd)
@@ -240,6 +254,7 @@ module MuxTf
240
254
  root_cmd.add_command(reconfigure_cmd)
241
255
  root_cmd.add_command(interactive_cmd)
242
256
  root_cmd.add_command(plan_details_cmd)
257
+ root_cmd.add_command(init_cmd)
243
258
 
244
259
  root_cmd.add_command(exit_cmd)
245
260
  root_cmd.add_command(define_cmd("help", summary: "Show help for commands") { |_opts, _args, cmd| puts cmd.supercommand.help })
@@ -261,20 +276,26 @@ module MuxTf
261
276
  def plan_details_cmd
262
277
  define_cmd("details", summary: "Show Plan Details") do |_opts, _args, _cmd|
263
278
  puts plan_summary_text
264
- end
265
- end
266
279
 
267
- def plan_cmd
268
- define_cmd("plan", summary: "Re-run plan") do |_opts, _args, _cmd|
269
- run_validate && run_plan
280
+ unless ENV["JSON_PLAN"]
281
+ log "Printing Plan Summary ...", depth: 1
282
+
283
+ plan_filename = PlanFilenameGenerator.for_path
284
+ plan = PlanSummaryHandler.from_file(plan_filename)
285
+ plan.simple_summary do |line|
286
+ log line
287
+ end
288
+ end
289
+ # puts plan_summary_text if ENV["JSON_PLAN"]
270
290
  end
271
291
  end
272
292
 
273
293
  def apply_cmd
274
294
  define_cmd("apply", summary: "Apply the current plan") do |_opts, _args, _cmd|
295
+ plan_filename = PlanFilenameGenerator.for_path
275
296
  status = tf_apply(filename: plan_filename)
276
297
  if status.success?
277
- plan_status = run_plan
298
+ plan_status = @plan_command.run_plan
278
299
  throw :stop, :done if plan_status == :ok
279
300
  else
280
301
  log "Apply Failed!"
@@ -335,6 +356,17 @@ module MuxTf
335
356
  end
336
357
  end
337
358
 
359
+ def init_cmd
360
+ define_cmd("init", summary: "Re-run init") do |_opts, _args, _cmd|
361
+ exit_code, meta = PlanFormatter.run_tf_init
362
+ print_errors_and_warnings(meta)
363
+ if exit_code != 0
364
+ log meta.inspect unless meta.empty?
365
+ log "Init Failed!"
366
+ end
367
+ end
368
+ end
369
+
338
370
  def upgrade_cmd
339
371
  define_cmd("upgrade", summary: "Upgrade modules/plguins") do |_opts, _args, _cmd|
340
372
  status, meta = run_upgrade
@@ -358,13 +390,18 @@ module MuxTf
358
390
 
359
391
  def interactive_cmd
360
392
  define_cmd("interactive", summary: "Apply interactively") do |_opts, _args, _cmd|
393
+ plan_filename = PlanFilenameGenerator.for_path
361
394
  plan = PlanSummaryHandler.from_file(plan_filename)
362
395
  begin
363
- abort_message = catch(:abort) { plan.run_interactive }
396
+ abort_message = catch(:abort) {
397
+ result = plan.run_interactive
398
+ log "Re-running apply with the selected resources ..."
399
+ @plan_command.run_plan(targets: result)
400
+ }
364
401
  if abort_message
365
402
  log pastel.red("Aborted: #{abort_message}")
366
403
  else
367
- run_plan
404
+ @plan_command.run_plan
368
405
  end
369
406
  rescue Exception => e # rubocop:disable Lint/RescueException
370
407
  log e.full_message
@@ -373,99 +410,6 @@ module MuxTf
373
410
  end
374
411
  end
375
412
 
376
- def print_errors_and_warnings(meta) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
377
- message = []
378
- message << pastel.yellow("#{meta[:warnings].length} Warnings") if meta[:warnings]
379
- message << pastel.red("#{meta[:errors].length} Errors") if meta[:errors]
380
- if message.length.positive?
381
- log ""
382
- log "Encountered: #{message.join(' and ')}"
383
- log ""
384
- end
385
-
386
- meta[:warnings]&.each do |warning|
387
- log "-" * 20
388
- log pastel.yellow("Warning: #{warning[:message]}")
389
- warning[:body]&.each do |line|
390
- log pastel.yellow(line), depth: 1
391
- end
392
- log ""
393
- end
394
-
395
- meta[:errors]&.each do |error|
396
- log "-" * 20
397
- log pastel.red("Error: #{error[:message]}")
398
- error[:body]&.each do |line|
399
- log pastel.red(line), depth: 1
400
- end
401
- log ""
402
- end
403
-
404
- return unless message.length.positive?
405
-
406
- log ""
407
- end
408
-
409
- def detect_remedies_from_plan(meta)
410
- remedies = Set.new
411
- meta[:errors]&.each do |error|
412
- remedies << :plan if error[:message].include?("timeout while waiting for plugin to start")
413
- end
414
- remedies << :unlock if lock_error?(meta)
415
- remedies
416
- end
417
-
418
- def lock_error?(meta)
419
- meta && meta["error"] == "lock"
420
- end
421
-
422
- def extract_lock_info(meta)
423
- {
424
- lock_id: meta["ID"],
425
- operation: meta["Operation"],
426
- who: meta["Who"],
427
- created: meta["Created"]
428
- }
429
- end
430
-
431
- def run_plan(targets: [], level: 1, retry_count: 0)
432
- plan_status, = remedy_retry_helper(from: :plan, level: level, attempt: retry_count) {
433
- @last_lock_info = nil
434
-
435
- plan_status, meta = create_plan(plan_filename, targets: targets)
436
-
437
- print_errors_and_warnings(meta)
438
-
439
- remedies = detect_remedies_from_plan(meta)
440
-
441
- if remedies.include?(:unlock)
442
- @last_lock_info = extract_lock_info(meta)
443
- throw :abort, [plan_status, meta]
444
- end
445
-
446
- [remedies, plan_status, meta]
447
- }
448
-
449
- case plan_status
450
- when :ok
451
- log "no changes", depth: 1
452
- when :error
453
- log "something went wrong", depth: 1
454
- when :changes
455
- unless ENV["JSON_PLAN"]
456
- log "Printing Plan Summary ...", depth: 1
457
- pretty_plan_summary(plan_filename)
458
- end
459
- puts plan_summary_text if ENV["JSON_PLAN"]
460
- when :unknown
461
- # nothing
462
- end
463
-
464
- plan_status
465
- end
466
-
467
- public :run_plan
468
-
469
413
  def run_upgrade
470
414
  exit_code, meta = PlanFormatter.run_tf_init(upgrade: true)
471
415
  print_errors_and_warnings(meta)
@@ -479,18 +423,6 @@ module MuxTf
479
423
  [:unknown, meta]
480
424
  end
481
425
  end
482
-
483
- def pretty_plan_summary(filename)
484
- plan = PlanSummaryHandler.from_file(filename)
485
- plan.flat_summary.each do |line|
486
- log line, depth: 2
487
- end
488
- plan.output_summary.each do |line|
489
- log line, depth: 2
490
- end
491
- log "", depth: 2
492
- log plan.summary, depth: 2
493
- end
494
426
  end
495
427
  end
496
428
  end