mux_tf 0.11.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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