mux_tf 0.11.0 → 0.13.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: cd2c3c9fe3713fedf24899d07fe227d731d6331eef42356934bd3ba2a0f3b68b
4
- data.tar.gz: 878d5e65dbfe4335b58f2867be76d5d7f91d52702338695c2b0c89d98c0dc615
3
+ metadata.gz: ab68c9969748e0246a48cb2bf53876b0b2f47993b021017e1e9bb31782a7e4b4
4
+ data.tar.gz: 63fb2194ba923f3c30c9480f50946c550e85ea9e7e1988fdcc1764cbe7666f6a
5
5
  SHA512:
6
- metadata.gz: 91cf73ffb77f5282ebf0bb4186d9eb8ba51a75b58b2767d139c81ecced720cc94a1852ec4bd0d038ec203063500b24ef12712b4e54295c0d308991e9fe393340
7
- data.tar.gz: 55ea5b19015b5266cec12962a25b41d8a29ea090cab7a9de6381147a70c90c214b995c085862bc0e084582711c99e375f8a7d07871e1bf8c0f8a63f55f7bdba4
6
+ metadata.gz: e8d1442c6cff1c7c0828347176b1cc0a49fa28fb5a27e0212e730e3bf176fc4a072ba247e9cbf6d248c488fb1c62ffc6a71181dbde47e0b35a759b798ded6886
7
+ data.tar.gz: 3d85fc7be2b58bd815060145c33bccf7dc5814a5ee3a052f5adc6a4c7512792e290f0b1bb26faa07d4f950aab81036bcf9c09b91c2a3a20878fcd70601bd2044
@@ -4,7 +4,7 @@ require "bundler"
4
4
 
5
5
  module MuxTf
6
6
  module Cli
7
- module Current
7
+ module Current # rubocop:disable Metrics/ModuleLength
8
8
  extend TerraformHelpers
9
9
  extend PiotrbCliUtils::Util
10
10
  extend PiotrbCliUtils::CriCommandSupport
@@ -32,19 +32,16 @@ module MuxTf
32
32
  return launch_cmd_loop(:error) unless upgrade_status == :ok
33
33
  end
34
34
 
35
- plan_status, @plan_meta = create_plan(plan_filename)
35
+ plan_status = run_plan
36
36
 
37
37
  case plan_status
38
38
  when :ok
39
- log "no changes, exiting", depth: 1
39
+ log "exiting", depth: 1
40
40
  when :error
41
- log "something went wrong", depth: 1
42
41
  launch_cmd_loop(plan_status)
43
- when :changes
44
- log "Printing Plan Summary ...", depth: 1
45
- pretty_plan_summary(plan_filename)
42
+ when :changes # rubocop:disable Lint/DuplicateBranch
46
43
  launch_cmd_loop(plan_status)
47
- when :unknown
44
+ when :unknown # rubocop:disable Lint/DuplicateBranch
48
45
  launch_cmd_loop(plan_status)
49
46
  end
50
47
  end
@@ -68,28 +65,45 @@ module MuxTf
68
65
 
69
66
  def run_validate
70
67
  remedies = PlanFormatter.process_validation(validate)
71
- process_remedies(remedies)
68
+ status, _results = process_remedies(remedies)
69
+ status
72
70
  end
73
71
 
74
- def process_remedies(remedies)
72
+ def process_remedies(remedies, retry_count: 0) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
73
+ results = {}
74
+ if retry_count > 5
75
+ log "giving up because retry_count: #{retry_count}", depth: 1
76
+ log "unprocessed remedies: #{remedies.to_a}", depth: 1
77
+ return [false, results]
78
+ end
75
79
  if remedies.delete? :init
76
- log "Running terraform init ...", depth: 2
80
+ log "[remedy] Running terraform init ...", depth: 2
77
81
  remedies = PlanFormatter.init_status_to_remedies(*PlanFormatter.run_tf_init)
78
- if process_remedies(remedies)
82
+ status, r_results = process_remedies(remedies)
83
+ results.merge!(r_results)
84
+ if status
79
85
  remedies = PlanFormatter.process_validation(validate)
80
- return false unless process_remedies(remedies)
86
+ return [false, results] unless process_remedies(remedies)
81
87
  end
82
88
  end
89
+ if remedies.delete?(:plan)
90
+ log "[remedy] Running terraform plan ...", depth: 2
91
+ plan_status = run_plan(retry_count: retry_count)
92
+ results[:plan_status] = plan_status
93
+ return [false, results] unless [:ok, :changes].include?(plan_status)
94
+ end
83
95
  if remedies.delete? :reconfigure
84
- log "Running terraform init ...", depth: 2
96
+ log "[remedy] Running terraform init ...", depth: 2
85
97
  remedies = PlanFormatter.init_status_to_remedies(*PlanFormatter.run_tf_init(reconfigure: true))
86
- return false unless process_remedies(remedies)
98
+ status, r_results = process_remedies(remedies)
99
+ results.merge!(r_results)
100
+ return [false, results] unless status
87
101
  end
88
102
  unless remedies.empty?
89
103
  log "unprocessed remedies: #{remedies.to_a}", depth: 1
90
- return false
104
+ return [false, results]
91
105
  end
92
- true
106
+ [true, results]
93
107
  end
94
108
 
95
109
  def validate
@@ -268,20 +282,68 @@ module MuxTf
268
282
  end
269
283
  end
270
284
 
271
- def run_plan(targets: [])
285
+ def print_errors_and_warnings # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
286
+ message = []
287
+ message << Paint["#{@plan_meta[:warnings].length} Warnings", :yellow] if @plan_meta[:warnings]
288
+ message << Paint["#{@plan_meta[:errors].length} Errors", :red] if @plan_meta[:errors]
289
+ if message.length.positive?
290
+ log ""
291
+ log "Encountered: #{message.join(' and ')}"
292
+ log ""
293
+ end
294
+
295
+ @plan_meta[:warnings]&.each do |warning|
296
+ log "-" * 20
297
+ log Paint["Warning: #{warning[:message]}", :yellow]
298
+ warning[:body]&.each do |line|
299
+ log Paint[line, :yellow], depth: 1
300
+ end
301
+ log ""
302
+ end
303
+
304
+ @plan_meta[:errors]&.each do |error|
305
+ log "-" * 20
306
+ log Paint["Error: #{error[:message]}", :red]
307
+ error[:body]&.each do |line|
308
+ log Paint[line, :red], depth: 1
309
+ end
310
+ log ""
311
+ end
312
+
313
+ return unless message.length.positive?
314
+
315
+ log ""
316
+ end
317
+
318
+ def detect_remedies_from_plan
319
+ remedies = Set.new
320
+ @plan_meta[:errors]&.each do |error|
321
+ remedies << :plan if error[:message].include?("timeout while waiting for plugin to start")
322
+ end
323
+ remedies
324
+ end
325
+
326
+ def run_plan(targets: [], retry_count: 0)
272
327
  plan_status, @plan_meta = create_plan(plan_filename, targets: targets)
273
328
 
274
329
  case plan_status
275
330
  when :ok
276
331
  log "no changes", depth: 1
277
332
  when :error
278
- log "something went wrong", depth: 1
333
+ # log "something went wrong", depth: 1
334
+ print_errors_and_warnings
335
+ remedies = detect_remedies_from_plan
336
+ status, results = process_remedies(remedies, retry_count: retry_count)
337
+ plan_status = results[:plan_status] if status
279
338
  when :changes
280
339
  log "Printing Plan Summary ...", depth: 1
281
340
  pretty_plan_summary(plan_filename)
282
341
  when :unknown
283
342
  # nothing
284
343
  end
344
+
345
+ print_errors_and_warnings
346
+
285
347
  plan_status
286
348
  end
287
349
 
@@ -292,8 +354,6 @@ module MuxTf
292
354
  [:ok, meta]
293
355
  when 1
294
356
  [:error, meta]
295
- # when 2
296
- # [:changes, meta]
297
357
  else
298
358
  log Paint["terraform init upgrade exited with an unknown exit code: #{exit_code}", :yellow]
299
359
  [:unknown, meta]
data/lib/mux_tf/cli.rb CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  module MuxTf
4
4
  module Cli
5
+ extend PiotrbCliUtils::Util
6
+
5
7
  def self.run(mode, args)
6
8
  case mode
7
9
  when :mux
@@ -9,13 +9,11 @@ module MuxTf
9
9
  def pretty_plan(filename, targets: []) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
10
10
  pastel = Pastel.new
11
11
 
12
- once = OnceHelper.new
13
-
14
12
  meta = {}
15
13
 
16
14
  parser = StatefulParser.new(normalizer: pastel.method(:strip))
17
15
  parser.state(:info, /^Acquiring state lock/)
18
- parser.state(:error, /(╷|Error locking state|Error:)/, [:none, :blank, :info, :reading])
16
+ parser.state(:error, /(Error locking state|Error:)/, [:none, :blank, :info, :reading])
19
17
  parser.state(:reading, /: (Reading...|Read complete after)/, [:none, :info, :reading])
20
18
  parser.state(:none, /^$/, [:reading])
21
19
  parser.state(:refreshing, /^.+: Refreshing state... \[id=/, [:none, :info, :reading])
@@ -23,19 +21,30 @@ module MuxTf
23
21
  [:none, :blank, :info, :reading])
24
22
  parser.state(:refresh_done, /^----------+$/, [:refreshing])
25
23
  parser.state(:refresh_done, /^$/, [:refreshing])
24
+
25
+ parser.state(:output_info, /^Changes to Outputs:$/, [:refresh_done])
26
+ parser.state(:refresh_done, /^$/, [:output_info])
27
+
26
28
  parser.state(:plan_info, /Terraform will perform the following actions:/, [:refresh_done, :none])
27
29
  parser.state(:plan_summary, /^Plan:/, [:plan_info])
28
30
 
29
31
  parser.state(:plan_legend, /^Terraform used the selected providers to generate the following execution$/)
30
32
  parser.state(:none, /^$/, [:plan_legend])
31
33
 
32
- parser.state(:error_lock_info, /Lock Info/, [:error])
33
- parser.state(:error, /^$/, [:error_lock_info])
34
+ parser.state(:error_lock_info, /Lock Info/, [:plan_error_error])
35
+
36
+ parser.state(:plan_error, /Planning failed. Terraform encountered an error while generating this plan./, [:refreshing, :refresh_done])
37
+ parser.state(:plan_error_block, /^╷/, [:plan_error, :none, :blank, :info, :reading])
38
+ parser.state(:plan_error_warning, /^│ Warning: /, [:plan_error_block])
39
+ parser.state(:plan_error_error, /^│ Error: /, [:plan_error_block])
40
+ parser.state(:plan_error, /^╵/, [:plan_error_warning, :plan_error_error, :plan_error_block, :error_lock_info])
34
41
 
35
- parser.state(:plan_error, /^╷|Error: /, [:refreshing, :refresh_done])
42
+ last_state = nil
36
43
 
37
44
  status = tf_plan(out: filename, detailed_exitcode: true, compact_warnings: true, targets: targets) { |raw_line|
38
45
  parser.parse(raw_line.rstrip) do |state, line|
46
+ first_in_state = last_state != state
47
+
39
48
  case state
40
49
  when :none
41
50
  if line.blank?
@@ -62,39 +71,77 @@ module MuxTf
62
71
  else
63
72
  p [state, line]
64
73
  end
65
- when :error
66
- meta["error"] = "lock"
67
- log Paint[line, :red], depth: 2
74
+ when :plan_error_block
75
+ meta[:current_error] = {
76
+ type: :unknown,
77
+ body: []
78
+ }
79
+ when :plan_error_warning, :plan_error_error
80
+ clean_line = pastel.strip(line).gsub(/^│ /, "")
81
+ if clean_line =~ /^(Warning|Error): (.+)$/
82
+ meta[:current_error][:type] = $LAST_MATCH_INFO[1].downcase.to_sym
83
+ meta[:current_error][:message] = $LAST_MATCH_INFO[2]
84
+ elsif clean_line == ""
85
+ # skip double empty lines
86
+ meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
87
+ else
88
+ meta[:current_error][:body] ||= []
89
+ meta[:current_error][:body] << clean_line
90
+ end
68
91
  when :plan_error
69
- once.for(state).once do puts end
70
- meta["error"] = "refresh"
71
- log Paint[line, :red], depth: 2
92
+ case pastel.strip(line)
93
+ when "" # closing of an error block
94
+ if meta[:current_error][:type] == :error
95
+ meta[:errors] ||= []
96
+ meta[:errors] << meta[:current_error]
97
+ end
98
+ if meta[:current_error][:type] == :warning
99
+ meta[:warnings] ||= []
100
+ meta[:warnings] << meta[:current_error]
101
+ end
102
+ meta.delete(:current_error)
103
+ when ""
104
+ # skip empty line
105
+ when /Releasing state lock. This may take a few moments"/
106
+ log line, depth: 2
107
+ when /Planning failed./ # rubocop:disable Lint/DuplicateBranch
108
+ log line, depth: 2
109
+ else
110
+ p [state, line]
111
+ end
72
112
  when :error_lock_info
113
+ meta["error"] = "lock"
73
114
  meta[$LAST_MATCH_INFO[1]] = $LAST_MATCH_INFO[2] if line =~ /([A-Z]+\S+)+:\s+(.+)$/
74
- log Paint[line, :red], depth: 2
115
+ clean_line = pastel.strip(line).gsub(/^│ /, "")
116
+ if clean_line == ""
117
+ meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
118
+ else
119
+ meta[:current_error][:body] << clean_line
120
+ end
121
+ # log Paint[line, :red], depth: 2
75
122
  when :refreshing
76
- once.for(state).once {
123
+ if first_in_state
77
124
  log "Refreshing state ", depth: 2, newline: false
78
- }.otherwise {
125
+ else
79
126
  print "."
80
- }
127
+ end
81
128
  when :plan_legend
82
- once.for(state).once do puts end
129
+ puts if first_in_state
83
130
  log line, depth: 2
84
131
  when :refresh_done
85
- once.for(state).once {
86
- puts
87
- }.otherwise {
88
- # nothing
89
- }
132
+ puts if first_in_state
90
133
  when :plan_info # rubocop:disable Lint/DuplicateBranch
91
- once.for(state).once do puts end
134
+ puts if first_in_state
135
+ log line, depth: 2
136
+ when :output_info # rubocop:disable Lint/DuplicateBranch
137
+ puts if first_in_state
92
138
  log line, depth: 2
93
139
  when :plan_summary
94
140
  log line, depth: 2
95
141
  else
96
142
  p [state, pastel.strip(line)]
97
143
  end
144
+ last_state = state
98
145
  end
99
146
  }
100
147
  [status.status, meta]
@@ -264,6 +311,8 @@ module MuxTf
264
311
  remedies << :init
265
312
  elsif /there is no package for .+ cached in/.match?(dinfo["summary"]) # rubocop:disable Lint/DuplicateBranch
266
313
  remedies << :init
314
+ elsif dinfo["detail"]&.include?("timeout while waiting for plugin to start") # rubocop:disable Lint/DuplicateBranch
315
+ remedies << :init
267
316
  else
268
317
  log dinfo["detail"], depth: 4 if dinfo["detail"]
269
318
  log format_validation_range(dinfo["range"], color), depth: 4 if dinfo["range"]
@@ -239,7 +239,7 @@ module MuxTf
239
239
  throw :abort, "nothing selected"
240
240
  else
241
241
  log "Re-running apply with the selected resources ..."
242
- run_plan(targets: result)
242
+ MuxTf::Cli::Current.run_plan(targets: result)
243
243
  end
244
244
  end
245
245
 
@@ -263,36 +263,6 @@ module MuxTf
263
263
  end
264
264
  end
265
265
 
266
- def run_plan(targets: [])
267
- plan_filename = MuxTf::Cli::Current.plan_filename
268
- plan_status, @plan_meta = create_plan(plan_filename, targets: targets)
269
-
270
- case plan_status
271
- when :ok
272
- log "no changes", depth: 1
273
- when :error
274
- log "something went wrong", depth: 1
275
- when :changes
276
- log "Printing Plan Summary ...", depth: 1
277
- pretty_plan_summary(plan_filename)
278
- when :unknown
279
- # nothing
280
- end
281
- plan_status
282
- end
283
-
284
- def pretty_plan_summary(filename)
285
- plan = PlanSummaryHandler.from_file(filename)
286
- plan.flat_summary.each do |line|
287
- log line, depth: 2
288
- end
289
- plan.output_summary.each do |line|
290
- log line, depth: 2
291
- end
292
- log "", depth: 2
293
- log plan.summary, depth: 2
294
- end
295
-
296
266
  def prune_unchanged_deps(_parts)
297
267
  valid_addresses = resource_parts.map { |part| part[:address] }
298
268
 
@@ -3,6 +3,7 @@
3
3
  module MuxTf
4
4
  module TerraformHelpers
5
5
  include PiotrbCliUtils::ShellHelpers
6
+ include PiotrbCliUtils::Util
6
7
 
7
8
  ResultStruct = Struct.new("TerraformResponse", :status, :success?, :output, :parsed_output, keyword_init: true)
8
9
 
data/lib/mux_tf/tmux.rb CHANGED
@@ -4,6 +4,8 @@ require "shellwords"
4
4
 
5
5
  module MuxTf
6
6
  module Tmux
7
+ extend PiotrbCliUtils::Util
8
+
7
9
  class << self
8
10
  def session_running?(name)
9
11
  tmux("has-session -t #{name.inspect} 2>/dev/null", raise_on_error: false)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MuxTf
4
- VERSION = "0.11.0"
4
+ VERSION = "0.13.0"
5
5
  end
data/lib/mux_tf.rb CHANGED
@@ -23,7 +23,6 @@ require "dotenv"
23
23
 
24
24
  require_relative "./mux_tf/version"
25
25
  require_relative "./mux_tf/plan_filename_generator"
26
- require_relative "./mux_tf/once_helper"
27
26
  require_relative "./mux_tf/resource_tokenizer"
28
27
  require_relative "./mux_tf/cli"
29
28
  require_relative "./mux_tf/tmux"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mux_tf
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.11.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Piotr Banasik
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2023-07-05 00:00:00.000000000 Z
11
+ date: 2023-07-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -141,7 +141,6 @@ files:
141
141
  - lib/mux_tf/cli/current.rb
142
142
  - lib/mux_tf/cli/mux.rb
143
143
  - lib/mux_tf/cli/plan_summary.rb
144
- - lib/mux_tf/once_helper.rb
145
144
  - lib/mux_tf/plan_filename_generator.rb
146
145
  - lib/mux_tf/plan_formatter.rb
147
146
  - lib/mux_tf/plan_summary_handler.rb
@@ -1,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module MuxTf
4
- class OnceHelper
5
- # once = OnceHelper.new
6
- # once.for(:phase).once { ... }.otherwise { ... }
7
-
8
- class StateEvaluator
9
- def initialize(once_helper, new_state)
10
- if once_helper.state == new_state
11
- @path = :otherwise
12
- else
13
- once_helper.state = new_state
14
- @path = :once
15
- end
16
- end
17
-
18
- def once
19
- yield if @path == :then
20
- self
21
- end
22
-
23
- def otherwise
24
- yield if @path == :otherwise
25
- self
26
- end
27
- end
28
-
29
- def initialize
30
- @state = nil
31
- end
32
-
33
- attr_accessor :state
34
-
35
- def for(new_state)
36
- StateEvaluator.new(self, new_state)
37
- end
38
- end
39
- end