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.
@@ -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,91 +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
480
+ stderr_handler = StderrLineHandler.new(operation: :plan)
394
481
 
395
- case state
396
- when :none
397
- if line.blank?
398
- # nothing
399
- else
400
- log_unhandled_line(state, line, reason: "unexpected non blank line in :none state")
401
- end
402
- when :reading
403
- clean_line = pastel.strip(line)
404
- if clean_line.match(/^(.+): Reading...$/)
405
- log "Reading: #{$LAST_MATCH_INFO[1]} ...", depth: 2
406
- elsif clean_line.match(/^(.+): Read complete after ([^\[]+)(?: \[(.+)\])?$/)
407
- if $LAST_MATCH_INFO[3]
408
- log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]} [#{$LAST_MATCH_INFO[3]}]", depth: 3
409
- else
410
- log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]}", depth: 3
411
- end
412
- else
413
- log_unhandled_line(state, line, reason: "unexpected line in :reading state")
414
- end
415
- when :info
416
- if /Acquiring state lock. This may take a few moments.../.match?(line)
417
- log "Acquiring state lock ...", depth: 2
418
- else
419
- log_unhandled_line(state, line, reason: "unexpected line in :info state")
420
- end
421
- when :plan_error
422
- case pastel.strip(line)
423
- when ""
424
- # skip empty line
425
- when /Releasing state lock. This may take a few moments"/
426
- log line, depth: 2
427
- when /Planning failed./ # rubocop:disable Lint/DuplicateBranch
428
- log line, depth: 2
429
- else
430
- log_unhandled_line(state, line, reason: "unexpected line in :plan_error state")
431
- end
432
- when :error_lock_info
433
- meta["error"] = "lock"
434
- meta[$LAST_MATCH_INFO[1]] = $LAST_MATCH_INFO[2] if line =~ /([A-Z]+\S+)+:\s+(.+)$/
435
- clean_line = pastel.strip(line).gsub(/^│ /, "")
436
- if clean_line == ""
437
- meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
438
- else
439
- meta[:current_error][:body] << clean_line
440
- end
441
- when :refreshing
442
- if first_in_state
443
- log "Refreshing state ", depth: 2, newline: false
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
444
499
  else
445
- print "."
500
+ log_unhandled_line(state, line, reason: "unexpected state")
446
501
  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
- log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
502
+
503
+ last_state = state
462
504
  end
463
- last_state = state
464
505
  end
465
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
+
466
518
  [status.status, meta]
467
519
  end
468
520
 
@@ -470,11 +522,10 @@ module MuxTf
470
522
  remedies = Set.new
471
523
  if status != 0
472
524
  remedies << :reconfigure if meta[:need_reconfigure]
525
+ remedies << :auth if meta[:need_auth]
473
526
  log "!! expected meta[:errors] to be set, how did we get here?" unless meta[:errors]
474
- if meta[:errors]
475
- meta[:errors].each do |error|
476
- remedies << :add_provider_constraint if error[:body].grep(/Could not retrieve the list of available versions for provider/)
477
- 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/)
478
529
  end
479
530
  if remedies.empty?
480
531
  log "!! don't know how to generate init remedies for this"
@@ -487,187 +538,176 @@ module MuxTf
487
538
  remedies
488
539
  end
489
540
 
490
- def setup_error_handling(parser, from_states:)
491
- parser.state(:error_block, /^╷/, from_states | [:after_error])
492
- parser.state(:error_block_error, /^│ Error: /, [:error_block])
493
- parser.state(:error_block_warning, /^│ Warning: /, [:error_block])
494
- 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])
495
551
  end
496
552
 
497
- 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:)
498
554
  case state
499
- when :error_block
500
- meta[:current_error] = {
501
- type: :unknown,
502
- body: []
503
- }
504
- when :error_block_error, :error_block_warning
505
- clean_line = pastel.strip(line).gsub(/^│ /, "")
506
- if clean_line =~ /^(Warning|Error): (.+)$/
507
- meta[:current_error][:type] = $LAST_MATCH_INFO[1].downcase.to_sym
508
- meta[:current_error][:message] = $LAST_MATCH_INFO[2]
509
- elsif clean_line == ""
510
- # skip double empty lines
511
- 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
512
569
  else
513
- meta[:current_error][:body] ||= []
514
- meta[:current_error][:body] << clean_line
570
+ phase = state
571
+ log "Initializing modules ", depth: 1
515
572
  end
516
- when :after_error
517
- case pastel.strip(line)
518
- when "╵" # closing of an error block
519
- if meta[:current_error][:type] == :error
520
- meta[:errors] ||= []
521
- 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")
522
586
  end
523
- if meta[:current_error][:type] == :warning
524
- meta[:warnings] ||= []
525
- 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")
603
+ end
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
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")
526
652
  end
527
- meta.delete(:current_error)
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
528
664
  end
665
+ when :none
666
+ log_unhandled_line(state, line, reason: "unexpected line in :none state") if line != ""
529
667
  else
530
668
  return false
669
+ # log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
531
670
  end
532
- true
671
+
672
+ { phase: phase }
533
673
  end
534
674
 
535
- def run_tf_init(upgrade: nil, reconfigure: nil) # rubocop:disable Metrics/MethodLength
675
+ def run_tf_init(upgrade: nil, reconfigure: nil)
536
676
  phase = :init
537
677
 
538
678
  meta = {}
539
679
 
540
680
  parser = StatefulParser.new(normalizer: pastel.method(:strip))
541
681
 
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])
682
+ setup_init_parser(parser)
546
683
 
547
- parser.state(:plugin_warnings, /^$/, [:plugins])
548
- parser.state(:backend_error, /Error:/, [:backend])
684
+ setup_error_handling(parser, from_states: [:plugins, :modules_init])
549
685
 
550
- setup_error_handling(parser, from_states: [:plugins])
686
+ stderr_handler = StderrLineHandler.new(operation: :init)
551
687
 
552
- status = tf_init(upgrade: upgrade, reconfigure: reconfigure) { |raw_line|
553
- stripped_line = pastel.strip(raw_line.rstrip)
554
-
555
- parser.parse(raw_line.rstrip) do |state, line|
556
- case state
557
- when :modules_init
558
- if phase != state
559
- phase = state
560
- log "Initializing modules ", depth: 1
561
- next
562
- end
563
- case stripped_line
564
- when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
565
- print "D"
566
- when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
567
- print "D"
568
- when /^- (?<module>[^ ]+) in (?<path>.+)$/
569
- print "."
570
- when ""
571
- puts
572
- else
573
- log_unhandled_line(state, line, reason: "unexpected line in :modules_init state")
574
- end
575
- when :modules_upgrade
576
- if phase != state
577
- # first line
578
- phase = state
579
- log "Upgrding modules ", depth: 1, newline: false
580
- next
581
- end
582
- case stripped_line
583
- when /^- (?<module>[^ ]+) in (?<path>.+)$/
584
- print "."
585
- when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
586
- print "D"
587
- when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
588
- print "D"
589
- when ""
590
- puts
591
- else
592
- log_unhandled_line(state, line, reason: "unexpected line in :modules_upgrade state")
593
- end
594
- when :backend
595
- if phase != state
596
- # first line
597
- phase = state
598
- log "Initializing the backend ", depth: 1 # , newline: false
599
- next
600
- end
601
- case stripped_line
602
- when /^Successfully configured/
603
- log line, depth: 2
604
- when /unless the backend/ # rubocop:disable Lint/DuplicateBranch
605
- log line, depth: 2
606
- when ""
607
- puts
608
- else
609
- log_unhandled_line(state, line, reason: "unexpected line in :backend state")
610
- end
611
- when :backend_error
612
- if raw_line.match "terraform init -reconfigure"
613
- meta[:need_reconfigure] = true
614
- log pastel.red("module needs to be reconfigured"), depth: 2
615
- end
616
- when :plugins
617
- if phase != state
618
- # first line
619
- phase = state
620
- log "Initializing provider plugins ...", depth: 1
621
- next
622
- end
623
- case stripped_line
624
- when /^- Reusing previous version of (?<module>.+) from the dependency lock file$/
625
- info = $LAST_MATCH_INFO.named_captures
626
- log "- [FROM-LOCK] #{info['module']}", depth: 2
627
- when /^- (?<module>.+) is built in to Terraform$/
628
- info = $LAST_MATCH_INFO.named_captures
629
- log "- [BUILTIN] #{info['module']}", depth: 2
630
- when /^- Finding (?<module>[^ ]+) versions matching "(?<version>.+)"\.\.\./
631
- info = $LAST_MATCH_INFO.named_captures
632
- log "- [FIND] #{info['module']} matching #{info['version'].inspect}", depth: 2
633
- when /^- Finding latest version of (?<module>.+)\.\.\.$/
634
- info = $LAST_MATCH_INFO.named_captures
635
- log "- [FIND] #{info['module']}", depth: 2
636
- when /^- Installing (?<module>[^ ]+) v(?<version>.+)\.\.\.$/
637
- info = $LAST_MATCH_INFO.named_captures
638
- log "- [INSTALLING] #{info['module']} v#{info['version']}", depth: 2
639
- when /^- Installed (?<module>[^ ]+) v(?<version>.+) \(signed by(?: a)? (?<signed>.+)\)$/
640
- info = $LAST_MATCH_INFO.named_captures
641
- log "- [INSTALLED] #{info['module']} v#{info['version']} (#{info['signed']})", depth: 2
642
- when /^- Using previously-installed (?<module>[^ ]+) v(?<version>.+)$/
643
- info = $LAST_MATCH_INFO.named_captures
644
- log "- [USING] #{info['module']} v#{info['version']}", depth: 2
645
- when /^- Downloading plugin for provider "(?<provider>[^"]+)" \((?<provider_path>[^)]+)\) (?<version>.+)\.\.\.$/
646
- info = $LAST_MATCH_INFO.named_captures
647
- log "- #{info['provider']} #{info['version']}", depth: 2
648
- when "- Checking for available provider plugins..."
649
- # 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
650
701
  else
651
- log_unhandled_line(state, line, reason: "unexpected line in :plugins state")
702
+ log_unhandled_line(state, line, reason: "unexpected state")
652
703
  end
653
- when :plugin_warnings
654
- if phase != state
655
- # first line
656
- phase = state
657
- next
658
- end
659
-
660
- log pastel.yellow(line), depth: 1
661
- when :none
662
- next if line == ""
663
-
664
- log_unhandled_line(state, line, reason: "unexpected line in :none state")
665
- else
666
- log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
667
704
  end
668
705
  end
669
706
  }
670
707
 
708
+ stderr_handler.flush
709
+ stderr_handler.merge_meta_into(meta)
710
+
671
711
  [status.status, meta]
672
712
  end
673
713
 
@@ -683,8 +723,7 @@ module MuxTf
683
723
  end
684
724
  end
685
725
 
686
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
687
- def process_validation(info) # rubocop:disable Metrics/CyclomaticComplexity
726
+ def process_validation(info)
688
727
  remedies = Set.new
689
728
 
690
729
  if (info["error_count"]).positive? || (info["warning_count"]).positive?
@@ -739,11 +778,10 @@ module MuxTf
739
778
 
740
779
  remedies
741
780
  end
742
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
743
781
 
744
782
  private
745
783
 
746
- def format_validation_range(dinfo, color) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
784
+ def format_validation_range(dinfo, color)
747
785
  range = dinfo["range"]
748
786
  # filename: "../../../modules/pods/jane_pod/main.tf"
749
787
  # start: