mux_tf 0.15.0 → 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.
@@ -6,11 +6,9 @@ module MuxTf
6
6
  extend PiotrbCliUtils::Util
7
7
  include Coloring
8
8
 
9
- class << self # rubocop:disable Metrics/ClassLength
10
- def log_unhandled_line(state, line, reason: nil)
11
- p [state, pastel.strip(line), reason]
12
- end
9
+ extend ErrorHandlingMethods
13
10
 
11
+ class << self
14
12
  def pretty_plan(filename, targets: [])
15
13
  if ENV["JSON_PLAN"]
16
14
  pretty_plan_v2(filename, targets: targets)
@@ -44,7 +42,6 @@ module MuxTf
44
42
  result
45
43
  end
46
44
 
47
- # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
48
45
  def tf_plan_json(out:, targets: [], &block)
49
46
  emit_line = proc { |result|
50
47
  result[:level] ||= result[:stream] == :stderr ? "error" : "info"
@@ -84,11 +81,11 @@ module MuxTf
84
81
  status = tf_plan(out: out, detailed_exitcode: true, color: true, compact_warnings: false, json: true, input: false,
85
82
  targets: targets) { |(stream, raw_line)|
86
83
  case stream
87
- # when :command
88
- # puts raw_line
84
+ when :command
85
+ log "Running command: #{raw_line.strip} ...", depth: 2
89
86
  when :stdout
90
87
  parsed_line = JSON.parse(raw_line)
91
- parsed_line.keys.each do |key| # rubocop:disable Style/HashEachMethods -- intentional, allow adding keys to hash while iterating
88
+ parsed_line.keys.each do |key|
92
89
  if key[0] == "@"
93
90
  parsed_line[key[1..]] = parsed_line[key]
94
91
  parsed_line.delete(key)
@@ -129,7 +126,6 @@ module MuxTf
129
126
  emit_line.call(last_stderr_line) if last_stderr_line
130
127
  status
131
128
  end
132
- # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
133
129
 
134
130
  def parse_lock_info(detail)
135
131
  # Lock Info:
@@ -174,7 +170,7 @@ module MuxTf
174
170
  log log_line
175
171
  end
176
172
 
177
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
173
+ # rubocop:disable Metrics/AbcSize
178
174
  def pretty_plan_v2(filename, targets: [])
179
175
  meta = {}
180
176
  meta[:seen] = {
@@ -350,26 +346,27 @@ module MuxTf
350
346
  }
351
347
  [status.status, meta]
352
348
  end
353
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
354
-
355
- def pretty_plan_v1(filename, targets: []) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
356
- meta = {}
349
+ # rubocop:enable Metrics/AbcSize
357
350
 
358
- parser = StatefulParser.new(normalizer: pastel.method(:strip))
351
+ def setup_plan_v1_parser(parser)
359
352
  parser.state(:info, /^Acquiring state lock/)
360
353
  parser.state(:error, /(Error locking state|Error:)/, [:none, :blank, :info, :reading])
361
354
  parser.state(:reading, /: (Reading...|Read complete after)/, [:none, :info, :reading])
362
355
  parser.state(:none, /^$/, [:reading])
363
- parser.state(:refreshing, /^.+: Refreshing state... \[id=/, [:none, :info, :reading])
356
+ parser.state(:refreshing, /^.+: Refreshing state... \[id=/, [:none, :info, :reading, :import])
364
357
  parser.state(:refreshing, /Refreshing Terraform state in-memory prior to plan.../,
365
358
  [:none, :blank, :info, :reading])
366
359
  parser.state(:none, /^----------+$/, [:refreshing])
367
360
  parser.state(:none, /^$/, [:refreshing])
368
361
 
362
+ parser.state(:import, /".+: Preparing import... \[id=.+\]$/, [:none, :import])
363
+ parser.state(:none, /^$/, [:import])
364
+
369
365
  parser.state(:output_info, /^Changes to Outputs:$/, [:none])
370
366
  parser.state(:none, /^$/, [:output_info])
371
367
 
372
368
  parser.state(:plan_info, /Terraform will perform the following actions:/, [:none])
369
+ parser.state(:plan_info, /You can apply this plan to save these new output values to the Terraform/, [:none])
373
370
  parser.state(:plan_summary, /^Plan:/, [:plan_info])
374
371
 
375
372
  parser.state(:plan_legend, /^Terraform used the selected providers to generate the following execution$/)
@@ -378,94 +375,146 @@ module MuxTf
378
375
  parser.state(:plan_info, /Terraform planned the following actions, but then encountered a problem:/, [:none])
379
376
  parser.state(:plan_info, /No changes. Your infrastructure matches the configuration./, [:none])
380
377
 
381
- parser.state(:plan_error, /Planning failed. Terraform encountered an error while generating this plan./, [:refreshing])
378
+ parser.state(:plan_error, /Planning failed. Terraform encountered an error while generating this plan./, [:refreshing, :none])
382
379
 
383
380
  # this extends the error block to include the lock info
384
381
  parser.state(:error_lock_info, /Lock Info/, [:error_block_error])
385
382
  parser.state(:after_error, /^╵/, [:error_lock_info])
383
+ end
386
384
 
387
- setup_error_handling(parser, from_states: [:plan_error, :none, :blank, :info, :reading, :plan_summary, :refreshing])
385
+ def handle_plan_v1_line(state, line, meta, first_in_state:, stripped_line:)
386
+ case state
387
+ when :none
388
+ if line.blank?
389
+ # nothing
390
+ elsif stripped_line.match(/Error when retrieving token from sso/) || stripped_line.match(/Error loading SSO Token/)
391
+ meta[:need_auth] = true
392
+ log pastel.red("authentication problem"), depth: 2
393
+ else
394
+ log_unhandled_line(state, line, reason: "unexpected non blank line in :none state")
395
+ end
396
+ when :reading
397
+ if stripped_line.match(/^(.+): Reading...$/)
398
+ log "Reading: #{$LAST_MATCH_INFO[1]} ...", depth: 2
399
+ elsif stripped_line.match(/^(.+): Read complete after ([^\[]+)(?: \[(.+)\])?$/)
400
+ if $LAST_MATCH_INFO[3]
401
+ log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]} [#{$LAST_MATCH_INFO[3]}]", depth: 3
402
+ else
403
+ log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]}", depth: 3
404
+ end
405
+ else
406
+ log_unhandled_line(state, line, reason: "unexpected line in :reading state")
407
+ end
408
+ when :import
409
+ # github_repository_topics.this[\"tf-k8s-infra-modules\"]: Preparing import... [id=tf-k8s-infra-modules]
410
+ matches = stripped_line.match(/^(?<resource>.+): Preparing import... \[id=(?<id>.+)\]$/)
411
+ if matches
412
+ log "Importing #{pastel.cyan(matches[:resource])} (id=#{pastel.yellow(matches[:id])}) ...", depth: 2
413
+ else
414
+ p [:import, "couldn't parse line:", stripped_line]
415
+ end
416
+ when :info
417
+ if /Acquiring state lock. This may take a few moments.../.match?(line)
418
+ log "Acquiring state lock ...", depth: 2
419
+ else
420
+ log_unhandled_line(state, line, reason: "unexpected line in :info state")
421
+ end
422
+ when :plan_error
423
+ case pastel.strip(line)
424
+ when ""
425
+ # skip empty line
426
+ when /Releasing state lock. This may take a few moments"/
427
+ log line, depth: 2
428
+ when /Planning failed./ # rubocop:disable Lint/DuplicateBranch
429
+ log line, depth: 2
430
+ else
431
+ log_unhandled_line(state, line, reason: "unexpected line in :plan_error state")
432
+ end
433
+ when :error_lock_info
434
+ meta["error"] = "lock"
435
+ meta[$LAST_MATCH_INFO[1]] = $LAST_MATCH_INFO[2] if line =~ /([A-Z]+\S+)+:\s+(.+)$/
436
+ if stripped_line == ""
437
+ meta[:current_error][:body] << stripped_line if meta[:current_error][:body].last != ""
438
+ else
439
+ meta[:current_error][:body] << stripped_line
440
+ end
441
+ when :refreshing
442
+ if first_in_state
443
+ log "Refreshing state ", depth: 2, newline: false
444
+ else
445
+ print "."
446
+ end
447
+ when :plan_legend
448
+ puts if first_in_state
449
+ log line, depth: 2
450
+ when :refresh_done
451
+ puts if first_in_state
452
+ when :plan_info # rubocop:disable Lint/DuplicateBranch
453
+ puts if first_in_state
454
+ log line, depth: 2
455
+ when :output_info # rubocop:disable Lint/DuplicateBranch
456
+ puts if first_in_state
457
+ log line, depth: 2
458
+ when :plan_summary
459
+ log line, depth: 2
460
+ else
461
+ return false
462
+ end
463
+ true
464
+ end
465
+
466
+ def pretty_plan_v1(filename, targets: [])
467
+ meta = {}
468
+ init_phase = :none
469
+
470
+ parser = StatefulParser.new(normalizer: pastel.method(:strip))
471
+
472
+ setup_init_parser(parser)
473
+ setup_plan_v1_parser(parser)
474
+
475
+ setup_error_handling(parser,
476
+ from_states: [:plan_error, :none, :blank, :info, :reading, :plan_summary, :refreshing] + [:plugins, :modules_init])
388
477
 
389
478
  last_state = nil
390
479
 
391
- status = tf_plan(out: filename, detailed_exitcode: true, compact_warnings: true, targets: targets) { |raw_line|
392
- parser.parse(raw_line.rstrip) do |state, line|
393
- first_in_state = last_state != state
394
-
395
- case state
396
- when :none
397
- if line.blank?
398
- # nothing
399
- elsif raw_line.match(/Error when retrieving token from sso/) || raw_line.match(/Error loading SSO Token/)
400
- meta[:need_auth] = true
401
- log pastel.red("authentication problem"), depth: 2
402
- else
403
- log_unhandled_line(state, line, reason: "unexpected non blank line in :none state")
404
- end
405
- when :reading
406
- clean_line = pastel.strip(line)
407
- if clean_line.match(/^(.+): Reading...$/)
408
- log "Reading: #{$LAST_MATCH_INFO[1]} ...", depth: 2
409
- elsif clean_line.match(/^(.+): Read complete after ([^\[]+)(?: \[(.+)\])?$/)
410
- if $LAST_MATCH_INFO[3]
411
- log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]} [#{$LAST_MATCH_INFO[3]}]", depth: 3
412
- else
413
- log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]}", depth: 3
414
- end
415
- else
416
- log_unhandled_line(state, line, reason: "unexpected line in :reading state")
417
- end
418
- when :info
419
- if /Acquiring state lock. This may take a few moments.../.match?(line)
420
- log "Acquiring state lock ...", depth: 2
421
- else
422
- log_unhandled_line(state, line, reason: "unexpected line in :info state")
423
- end
424
- when :plan_error
425
- case pastel.strip(line)
426
- when ""
427
- # skip empty line
428
- when /Releasing state lock. This may take a few moments"/
429
- log line, depth: 2
430
- when /Planning failed./ # rubocop:disable Lint/DuplicateBranch
431
- log line, depth: 2
432
- else
433
- log_unhandled_line(state, line, reason: "unexpected line in :plan_error state")
434
- end
435
- when :error_lock_info
436
- meta["error"] = "lock"
437
- meta[$LAST_MATCH_INFO[1]] = $LAST_MATCH_INFO[2] if line =~ /([A-Z]+\S+)+:\s+(.+)$/
438
- clean_line = pastel.strip(line).gsub(/^│ /, "")
439
- if clean_line == ""
440
- meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
441
- else
442
- meta[:current_error][:body] << clean_line
443
- end
444
- when :refreshing
445
- if first_in_state
446
- log "Refreshing state ", depth: 2, newline: false
480
+ stderr_handler = StderrLineHandler.new(operation: :plan)
481
+
482
+ status = tf_plan(out: filename, detailed_exitcode: true, compact_warnings: true, targets: targets, split_streams: true) { |(stream, raw_line)|
483
+ case stream
484
+ when :command
485
+ log "Running command: #{raw_line.strip} ...", depth: 2
486
+ when :stderr
487
+ stderr_handler.handle(raw_line)
488
+ when :stdout
489
+ stripped_line = pastel.strip(raw_line.rstrip)
490
+ parser.parse(raw_line.rstrip) do |state, line|
491
+ first_in_state = last_state != state
492
+
493
+ if (handled = handle_plan_v1_line(state, line, meta, first_in_state: first_in_state, stripped_line: stripped_line))
494
+ # great!
495
+ elsif (handled = handle_init_line(state, line, meta, phase: init_phase, stripped_line: stripped_line))
496
+ init_phase = handled[:phase]
497
+ elsif handle_error_states(meta, state, line)
498
+ # no-op
447
499
  else
448
- print "."
500
+ log_unhandled_line(state, line, reason: "unexpected state")
449
501
  end
450
- when :plan_legend
451
- puts if first_in_state
452
- log line, depth: 2
453
- when :refresh_done
454
- puts if first_in_state
455
- when :plan_info # rubocop:disable Lint/DuplicateBranch
456
- puts if first_in_state
457
- log line, depth: 2
458
- when :output_info # rubocop:disable Lint/DuplicateBranch
459
- puts if first_in_state
460
- log line, depth: 2
461
- when :plan_summary
462
- log line, depth: 2
463
- else
464
- log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
502
+
503
+ last_state = state
465
504
  end
466
- last_state = state
467
505
  end
468
506
  }
507
+
508
+ stderr_handler.flush
509
+ stderr_handler.merge_meta_into(meta)
510
+
511
+ meta[:errors]&.each do |error|
512
+ if error[:message] == "Error acquiring the state lock"
513
+ meta["error"] = "lock"
514
+ meta.merge!(parse_lock_info(error[:body].join("\n")))
515
+ end
516
+ end
517
+
469
518
  [status.status, meta]
470
519
  end
471
520
 
@@ -475,10 +524,8 @@ module MuxTf
475
524
  remedies << :reconfigure if meta[:need_reconfigure]
476
525
  remedies << :auth if meta[:need_auth]
477
526
  log "!! expected meta[:errors] to be set, how did we get here?" unless meta[:errors]
478
- if meta[:errors]
479
- meta[:errors].each do |error|
480
- remedies << :add_provider_constraint if error[:body].grep(/Could not retrieve the list of available versions for provider/)
481
- end
527
+ meta[:errors]&.each do |error|
528
+ remedies << :add_provider_constraint if error[:body].grep(/Could not retrieve the list of available versions for provider/)
482
529
  end
483
530
  if remedies.empty?
484
531
  log "!! don't know how to generate init remedies for this"
@@ -491,193 +538,176 @@ module MuxTf
491
538
  remedies
492
539
  end
493
540
 
494
- def setup_error_handling(parser, from_states:)
495
- parser.state(:error_block, /^╷/, from_states | [:after_error])
496
- parser.state(:error_block_error, /^│ Error: /, [:error_block])
497
- parser.state(:error_block_warning, /^│ Warning: /, [:error_block])
498
- parser.state(:after_error, /^╵/, [:error_block, :error_block_error, :error_block_warning])
541
+ def setup_init_parser(parser)
542
+ parser.state(:modules_init, /^Initializing modules\.\.\./, [:none, :backend])
543
+ parser.state(:modules_upgrade, /^Upgrading modules\.\.\./)
544
+ parser.state(:backend, /^Initializing the backend\.\.\./, [:none, :modules_init, :modules_upgrade])
545
+ parser.state(:plugins, /^Initializing provider plugins\.\.\./, [:backend, :modules_init])
546
+
547
+ parser.state(:backend_error, /Error when retrieving token from sso/, [:backend])
548
+
549
+ parser.state(:plugin_warnings, /^$/, [:plugins])
550
+ parser.state(:backend_error, /Error:/, [:backend])
499
551
  end
500
552
 
501
- def handle_error_states(meta, state, line) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
553
+ def handle_init_line(state, line, meta, phase:, stripped_line:)
502
554
  case state
503
- when :error_block
504
- meta[:current_error] = {
505
- type: :unknown,
506
- body: []
507
- }
508
- when :error_block_error, :error_block_warning
509
- clean_line = pastel.strip(line).gsub(/^│ /, "")
510
- if clean_line =~ /^(Warning|Error): (.+)$/
511
- meta[:current_error][:type] = $LAST_MATCH_INFO[1].downcase.to_sym
512
- meta[:current_error][:message] = $LAST_MATCH_INFO[2]
513
- elsif clean_line == ""
514
- # skip double empty lines
515
- meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
555
+ when :modules_init
556
+ if phase == state
557
+ case stripped_line
558
+ when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
559
+ print "D"
560
+ when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
561
+ print "D"
562
+ when /^- (?<module>[^ ]+) in (?<path>.+)$/
563
+ print "."
564
+ when ""
565
+ puts
566
+ else
567
+ log_unhandled_line(state, line, reason: "unexpected line in :modules_init state")
568
+ end
516
569
  else
517
- meta[:current_error][:body] ||= []
518
- meta[:current_error][:body] << clean_line
570
+ phase = state
571
+ log "Initializing modules ", depth: 1
519
572
  end
520
- when :after_error
521
- case pastel.strip(line)
522
- when "╵" # closing of an error block
523
- if meta[:current_error][:type] == :error
524
- meta[:errors] ||= []
525
- meta[:errors] << meta[:current_error]
573
+ when :modules_upgrade
574
+ if phase == state
575
+ case stripped_line
576
+ when /^- (?<module>[^ ]+) in (?<path>.+)$/
577
+ print "."
578
+ when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
579
+ print "D"
580
+ when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
581
+ print "D"
582
+ when ""
583
+ puts
584
+ else
585
+ log_unhandled_line(state, line, reason: "unexpected line in :modules_upgrade state")
526
586
  end
527
- if meta[:current_error][:type] == :warning
528
- meta[:warnings] ||= []
529
- meta[:warnings] << meta[:current_error]
587
+ else
588
+ # first line
589
+ phase = state
590
+ log "Upgrding modules ", depth: 1, newline: false
591
+ end
592
+ when :backend
593
+ if phase == state
594
+ case stripped_line
595
+ when /^Successfully configured/
596
+ log line, depth: 2
597
+ when /unless the backend/ # rubocop:disable Lint/DuplicateBranch
598
+ log line, depth: 2
599
+ when ""
600
+ puts
601
+ else
602
+ log_unhandled_line(state, line, reason: "unexpected line in :backend state")
530
603
  end
531
- meta.delete(:current_error)
604
+ else
605
+ # first line
606
+ phase = state
607
+ log "Initializing the backend ", depth: 1 # , newline: false
608
+ end
609
+ when :backend_error
610
+ if raw_line.match "terraform init -reconfigure"
611
+ meta[:need_reconfigure] = true
612
+ log pastel.red("module needs to be reconfigured"), depth: 2
532
613
  end
614
+ if raw_line.match "Error when retrieving token from sso"
615
+ meta[:need_auth] = true
616
+ log pastel.red("authentication problem"), depth: 2
617
+ end
618
+ when :plugins
619
+ if phase == state
620
+ case stripped_line
621
+ when /^- Reusing previous version of (?<module>.+) from the dependency lock file$/
622
+ info = $LAST_MATCH_INFO.named_captures
623
+ log "- [FROM-LOCK] #{info['module']}", depth: 2
624
+ when /^- (?<module>.+) is built in to Terraform$/
625
+ info = $LAST_MATCH_INFO.named_captures
626
+ log "- [BUILTIN] #{info['module']}", depth: 2
627
+ when /^- Finding (?<module>[^ ]+) versions matching "(?<version>.+)"\.\.\./
628
+ info = $LAST_MATCH_INFO.named_captures
629
+ log "- [FIND] #{info['module']} matching #{info['version'].inspect}", depth: 2
630
+ when /^- Finding latest version of (?<module>.+)\.\.\.$/
631
+ info = $LAST_MATCH_INFO.named_captures
632
+ log "- [FIND] #{info['module']}", depth: 2
633
+ when /^- Installing (?<module>[^ ]+) v(?<version>.+)\.\.\.$/
634
+ info = $LAST_MATCH_INFO.named_captures
635
+ log "- [INSTALLING] #{info['module']} v#{info['version']}", depth: 2
636
+ when /^- Installed (?<module>[^ ]+) v(?<version>.+) \(signed by(?: a)? (?<signed>.+)\)$/
637
+ info = $LAST_MATCH_INFO.named_captures
638
+ log "- [INSTALLED] #{info['module']} v#{info['version']} (#{info['signed']})", depth: 2
639
+ when /^- Using previously-installed (?<module>[^ ]+) v(?<version>.+)$/
640
+ info = $LAST_MATCH_INFO.named_captures
641
+ log "- [USING] #{info['module']} v#{info['version']}", depth: 2
642
+ when /^- Downloading plugin for provider "(?<provider>[^"]+)" \((?<provider_path>[^)]+)\) (?<version>.+)\.\.\.$/
643
+ info = $LAST_MATCH_INFO.named_captures
644
+ log "- #{info['provider']} #{info['version']}", depth: 2
645
+ when /^- Using (?<provider>[^ ]+) v(?<version>.+) from the shared cache directory$/
646
+ info = $LAST_MATCH_INFO.named_captures
647
+ log "- [CACHE HIT] #{info['provider']} #{info['version']}", depth: 2
648
+ when "- Checking for available provider plugins..."
649
+ # noop
650
+ else
651
+ log_unhandled_line(state, line, reason: "unexpected line in :plugins state")
652
+ end
653
+ else
654
+ # first line
655
+ phase = state
656
+ log "Initializing provider plugins ...", depth: 1
657
+ end
658
+ when :plugin_warnings
659
+ if phase == state
660
+ log pastel.yellow(line), depth: 1
661
+ else
662
+ # first line
663
+ phase = state
664
+ end
665
+ when :none
666
+ log_unhandled_line(state, line, reason: "unexpected line in :none state") if line != ""
533
667
  else
534
668
  return false
669
+ # log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
535
670
  end
536
- true
671
+
672
+ { phase: phase }
537
673
  end
538
674
 
539
- def run_tf_init(upgrade: nil, reconfigure: nil) # rubocop:disable Metrics/MethodLength
675
+ def run_tf_init(upgrade: nil, reconfigure: nil)
540
676
  phase = :init
541
677
 
542
678
  meta = {}
543
679
 
544
680
  parser = StatefulParser.new(normalizer: pastel.method(:strip))
545
681
 
546
- parser.state(:modules_init, /^Initializing modules\.\.\./, [:none, :backend])
547
- parser.state(:modules_upgrade, /^Upgrading modules\.\.\./)
548
- parser.state(:backend, /^Initializing the backend\.\.\./, [:none, :modules_init, :modules_upgrade])
549
- parser.state(:plugins, /^Initializing provider plugins\.\.\./, [:backend, :modules_init])
550
-
551
- parser.state(:backend_error, /Error when retrieving token from sso/, [:backend])
552
-
553
- parser.state(:plugin_warnings, /^$/, [:plugins])
554
- parser.state(:backend_error, /Error:/, [:backend])
682
+ setup_init_parser(parser)
555
683
 
556
684
  setup_error_handling(parser, from_states: [:plugins, :modules_init])
557
685
 
558
- status = tf_init(upgrade: upgrade, reconfigure: reconfigure) { |raw_line|
559
- stripped_line = pastel.strip(raw_line.rstrip)
686
+ stderr_handler = StderrLineHandler.new(operation: :init)
560
687
 
561
- parser.parse(raw_line.rstrip) do |state, line|
562
- case state
563
- when :modules_init
564
- if phase != state
565
- phase = state
566
- log "Initializing modules ", depth: 1
567
- next
568
- end
569
- case stripped_line
570
- when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
571
- print "D"
572
- when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
573
- print "D"
574
- when /^- (?<module>[^ ]+) in (?<path>.+)$/
575
- print "."
576
- when ""
577
- puts
578
- else
579
- log_unhandled_line(state, line, reason: "unexpected line in :modules_init state")
580
- end
581
- when :modules_upgrade
582
- if phase != state
583
- # first line
584
- phase = state
585
- log "Upgrding modules ", depth: 1, newline: false
586
- next
587
- end
588
- case stripped_line
589
- when /^- (?<module>[^ ]+) in (?<path>.+)$/
590
- print "."
591
- when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
592
- print "D"
593
- when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
594
- print "D"
595
- when ""
596
- puts
597
- else
598
- log_unhandled_line(state, line, reason: "unexpected line in :modules_upgrade state")
599
- end
600
- when :backend
601
- if phase != state
602
- # first line
603
- phase = state
604
- log "Initializing the backend ", depth: 1 # , newline: false
605
- next
606
- end
607
- case stripped_line
608
- when /^Successfully configured/
609
- log line, depth: 2
610
- when /unless the backend/ # rubocop:disable Lint/DuplicateBranch
611
- log line, depth: 2
612
- when ""
613
- puts
614
- else
615
- log_unhandled_line(state, line, reason: "unexpected line in :backend state")
616
- end
617
- when :backend_error
618
- if raw_line.match "terraform init -reconfigure"
619
- meta[:need_reconfigure] = true
620
- log pastel.red("module needs to be reconfigured"), depth: 2
621
- end
622
- if raw_line.match "Error when retrieving token from sso"
623
- meta[:need_auth] = true
624
- log pastel.red("authentication problem"), depth: 2
625
- end
626
- when :plugins
627
- if phase != state
628
- # first line
629
- phase = state
630
- log "Initializing provider plugins ...", depth: 1
631
- next
632
- end
633
- case stripped_line
634
- when /^- Reusing previous version of (?<module>.+) from the dependency lock file$/
635
- info = $LAST_MATCH_INFO.named_captures
636
- log "- [FROM-LOCK] #{info['module']}", depth: 2
637
- when /^- (?<module>.+) is built in to Terraform$/
638
- info = $LAST_MATCH_INFO.named_captures
639
- log "- [BUILTIN] #{info['module']}", depth: 2
640
- when /^- Finding (?<module>[^ ]+) versions matching "(?<version>.+)"\.\.\./
641
- info = $LAST_MATCH_INFO.named_captures
642
- log "- [FIND] #{info['module']} matching #{info['version'].inspect}", depth: 2
643
- when /^- Finding latest version of (?<module>.+)\.\.\.$/
644
- info = $LAST_MATCH_INFO.named_captures
645
- log "- [FIND] #{info['module']}", depth: 2
646
- when /^- Installing (?<module>[^ ]+) v(?<version>.+)\.\.\.$/
647
- info = $LAST_MATCH_INFO.named_captures
648
- log "- [INSTALLING] #{info['module']} v#{info['version']}", depth: 2
649
- when /^- Installed (?<module>[^ ]+) v(?<version>.+) \(signed by(?: a)? (?<signed>.+)\)$/
650
- info = $LAST_MATCH_INFO.named_captures
651
- log "- [INSTALLED] #{info['module']} v#{info['version']} (#{info['signed']})", depth: 2
652
- when /^- Using previously-installed (?<module>[^ ]+) v(?<version>.+)$/
653
- info = $LAST_MATCH_INFO.named_captures
654
- log "- [USING] #{info['module']} v#{info['version']}", depth: 2
655
- when /^- Downloading plugin for provider "(?<provider>[^"]+)" \((?<provider_path>[^)]+)\) (?<version>.+)\.\.\.$/
656
- info = $LAST_MATCH_INFO.named_captures
657
- log "- #{info['provider']} #{info['version']}", depth: 2
658
- when "- Checking for available provider plugins..."
659
- # noop
688
+ status = tf_init(upgrade: upgrade, reconfigure: reconfigure) { |(stream, raw_line)|
689
+ case stream
690
+ when :command
691
+ log "Running command: #{raw_line.strip} ...", depth: 2
692
+ when :stderr
693
+ stderr_handler.handle(raw_line)
694
+ when :stdout
695
+ stripped_line = pastel.strip(raw_line.rstrip)
696
+ parser.parse(raw_line.rstrip) do |state, line|
697
+ if (handled = handle_init_line(state, line, meta, phase: phase, stripped_line: stripped_line))
698
+ phase = handled[:phase]
699
+ elsif handle_error_states(meta, state, line)
700
+ # no-op
660
701
  else
661
- log_unhandled_line(state, line, reason: "unexpected line in :plugins state")
702
+ log_unhandled_line(state, line, reason: "unexpected state")
662
703
  end
663
- when :plugin_warnings
664
- if phase != state
665
- # first line
666
- phase = state
667
- next
668
- end
669
-
670
- log pastel.yellow(line), depth: 1
671
- when :none
672
- next if line == ""
673
-
674
- log_unhandled_line(state, line, reason: "unexpected line in :none state")
675
- else
676
- log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
677
704
  end
678
705
  end
679
706
  }
680
707
 
708
+ stderr_handler.flush
709
+ stderr_handler.merge_meta_into(meta)
710
+
681
711
  [status.status, meta]
682
712
  end
683
713
 
@@ -693,8 +723,7 @@ module MuxTf
693
723
  end
694
724
  end
695
725
 
696
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
697
- def process_validation(info) # rubocop:disable Metrics/CyclomaticComplexity
726
+ def process_validation(info)
698
727
  remedies = Set.new
699
728
 
700
729
  if (info["error_count"]).positive? || (info["warning_count"]).positive?
@@ -749,11 +778,10 @@ module MuxTf
749
778
 
750
779
  remedies
751
780
  end
752
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
753
781
 
754
782
  private
755
783
 
756
- def format_validation_range(dinfo, color) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
784
+ def format_validation_range(dinfo, color)
757
785
  range = dinfo["range"]
758
786
  # filename: "../../../modules/pods/jane_pod/main.tf"
759
787
  # start: