mux_tf 0.16.0 → 0.17.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.
@@ -1,12 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MuxTf
4
- class PlanFormatter # rubocop:disable Metrics/ClassLength
4
+ class PlanFormatter
5
5
  extend TerraformHelpers
6
6
  extend PiotrbCliUtils::Util
7
7
  include Coloring
8
8
 
9
9
  extend ErrorHandlingMethods
10
+ extend FormatterCommon
10
11
 
11
12
  class << self
12
13
  def pretty_plan(filename, targets: [])
@@ -17,114 +18,11 @@ module MuxTf
17
18
  end
18
19
  end
19
20
 
20
- def parse_non_json_plan_line(raw_line)
21
- result = {}
22
-
23
- if raw_line.match(/^time=(?<timestamp>[^ ]+) level=(?<level>[^ ]+) msg=(?<message>.+?)(?: prefix=\[(?<prefix>.+?)\])?\s*$/)
24
- result.merge!($LAST_MATCH_INFO.named_captures.symbolize_keys)
25
- result[:module] = "terragrunt"
26
- result.delete(:prefix) unless result[:prefix]
27
- result[:prefix] = Pathname.new(result[:prefix]).relative_path_from(Dir.getwd).to_s if result[:prefix]
28
-
29
- result[:merge_up] = true if result[:message].match(/^\d+ errors? occurred:$/)
30
- elsif raw_line.strip == ""
31
- result[:blank] = true
32
- else
33
- result[:message] = raw_line
34
- result[:merge_up] = true
35
- end
36
-
37
- # time=2023-08-25T11:44:41-07:00 level=error msg=Terraform invocation failed in /Users/piotr/Work/janepods/.terragrunt-cache/BM86IAj5tW4bZga2lXeYT8tdOKI/V0IEypKSfyl-kHfCnRNAqyX02V8/modules/event-bus prefix=[/Users/piotr/Work/janepods/accounts/eks-dev/admin/apps/kube-system-event-bus]
38
- # time=2023-08-25T11:44:41-07:00 level=error msg=1 error occurred:
39
- # * [/Users/piotr/Work/janepods/.terragrunt-cache/BM86IAj5tW4bZga2lXeYT8tdOKI/V0IEypKSfyl-kHfCnRNAqyX02V8/modules/event-bus] exit status 2
40
- #
41
- #
42
- result
43
- end
44
-
45
21
  def tf_plan_json(out:, targets: [], &block)
46
- emit_line = proc { |result|
47
- result[:level] ||= result[:stream] == :stderr ? "error" : "info"
48
- result[:module] ||= result[:stream]
49
- result[:type] ||= "unknown"
50
-
51
- if result[:message].match(/^Terraform invocation failed in (.+)/)
52
- result[:type] = "tf_failed"
53
-
54
- lines = result[:message].split("\n")
55
- result[:diagnostic] = {
56
- "summary" => "Terraform invocation failed",
57
- "detail" => result[:message],
58
- roots: [],
59
- extra: []
60
- }
61
-
62
- lines.each do |line|
63
- if line.match(/^\s+\* \[(.+)\] exit status (\d+)$/)
64
- result[:diagnostic][:roots] << {
65
- path: $LAST_MATCH_INFO[1],
66
- status: $LAST_MATCH_INFO[2].to_i
67
- }
68
- elsif line.match(/^\d+ errors? occurred$/)
69
- # noop
70
- else
71
- result[:diagnostic][:extra] << line
72
- end
73
- end
74
-
75
- result[:message] = "Terraform invocation failed"
76
- end
77
-
78
- block.call(result)
79
- }
80
- last_stderr_line = nil
81
- status = tf_plan(out: out, detailed_exitcode: true, color: true, compact_warnings: false, json: true, input: false,
82
- targets: targets) { |(stream, raw_line)|
83
- case stream
84
- when :command
85
- log "Running command: #{raw_line.strip} ...", depth: 2
86
- when :stdout
87
- parsed_line = JSON.parse(raw_line)
88
- parsed_line.keys.each do |key|
89
- if key[0] == "@"
90
- parsed_line[key[1..]] = parsed_line[key]
91
- parsed_line.delete(key)
92
- end
93
- end
94
- parsed_line.symbolize_keys!
95
- parsed_line[:stream] = stream
96
- if last_stderr_line
97
- emit_line.call(last_stderr_line)
98
- last_stderr_line = nil
99
- end
100
- emit_line.call(parsed_line)
101
- when :stderr
102
- parsed_line = parse_non_json_plan_line(raw_line)
103
- parsed_line[:stream] = stream
104
-
105
- if parsed_line[:blank]
106
- if last_stderr_line
107
- emit_line.call(last_stderr_line)
108
- last_stderr_line = nil
109
- end
110
- elsif parsed_line[:merge_up]
111
- if last_stderr_line
112
- last_stderr_line[:message] += "\n#{parsed_line[:message]}"
113
- else
114
- # this is just a standalone message then
115
- parsed_line.delete(:merge_up)
116
- last_stderr_line = parsed_line
117
- end
118
- elsif last_stderr_line
119
- emit_line.call(last_stderr_line)
120
- last_stderr_line = parsed_line
121
- else
122
- last_stderr_line = parsed_line
123
- end
124
- end
125
- }
126
- emit_line.call(last_stderr_line) if last_stderr_line
127
- status
22
+ tf_cmd_json(proc { |handler|
23
+ tf_plan(out: out, detailed_exitcode: true, color: true, compact_warnings: false, json: true, input: false,
24
+ targets: targets, &handler)
25
+ }, &block)
128
26
  end
129
27
 
130
28
  def parse_lock_info(detail)
@@ -144,7 +42,7 @@ module MuxTf
144
42
  result
145
43
  end
146
44
 
147
- def print_plan_line(parsed_line, without: [])
45
+ def print_plan_line(parsed_line, without: [], from: nil)
148
46
  default_without = [
149
47
  :level,
150
48
  :module,
@@ -156,8 +54,9 @@ module MuxTf
156
54
  :ui
157
55
  ]
158
56
  extra = parsed_line.without(*default_without, *without)
159
- data = parsed_line.merge(extra: extra)
57
+ data = parsed_line.merge(extra: extra).merge(from: from)
160
58
  log_line = [
59
+ "%<from>s",
161
60
  "%<level>-6s",
162
61
  "%<module>-12s",
163
62
  "%<type>-10s",
@@ -170,7 +69,143 @@ module MuxTf
170
69
  log log_line
171
70
  end
172
71
 
173
- # rubocop:disable Metrics/AbcSize
72
+ def parse_tf_ui_line(parsed_line, meta, seen, skip_plan_summary: false)
73
+ # p(parsed_line)
74
+ case parsed_line[:type]
75
+ when "version"
76
+ meta[:terraform_version] = parsed_line[:terraform]
77
+ meta[:terraform_ui_version] = parsed_line[:ui]
78
+ when "apply_start", "refresh_start"
79
+ first_in_group = !seen.call(parsed_line[:module], "apply_start") &&
80
+ !seen.call(parsed_line[:module], "refresh_start")
81
+ log "Refreshing ", depth: 1, newline: false if first_in_group
82
+ # {
83
+ # :hook=>{
84
+ # "resource"=>{
85
+ # "addr"=>"data.aws_eks_cluster_auth.this",
86
+ # "module"=>"",
87
+ # "resource"=>"data.aws_eks_cluster_auth.this",
88
+ # "implied_provider"=>"aws",
89
+ # "resource_type"=>"aws_eks_cluster_auth",
90
+ # "resource_name"=>"this",
91
+ # "resource_key"=>nil
92
+ # },
93
+ # "action"=>"read"
94
+ # }
95
+ # }
96
+ log ".", newline: false
97
+ when "apply_complete", "refresh_complete"
98
+ # {
99
+ # :hook=>{
100
+ # "resource"=>{
101
+ # "addr"=>"data.aws_eks_cluster_auth.this",
102
+ # "module"=>"",
103
+ # "resource"=>"data.aws_eks_cluster_auth.this",
104
+ # "implied_provider"=>"aws",
105
+ # "resource_type"=>"aws_eks_cluster_auth",
106
+ # "resource_name"=>"this",
107
+ # "resource_key"=>nil
108
+ # },
109
+ # "action"=>"read",
110
+ # "id_key"=>"id",
111
+ # "id_value"=>"admin",
112
+ # "elapsed_seconds"=>0
113
+ # }
114
+ # }
115
+ # noop
116
+ when "resource_drift"
117
+ first_in_group = !seen.call(parsed_line[:module], "resource_drift") &&
118
+ !seen.call(parsed_line[:module], "planned_change")
119
+ # {
120
+ # :change=>{
121
+ # "resource"=>{"addr"=>"module.application.kubectl_manifest.application", "module"=>"module.application", "resource"=>"kubectl_manifest.application", "implied_provider"=>"kubectl", "resource_type"=>"kubectl_manifest", "resource_name"=>"application", "resource_key"=>nil},
122
+ # "action"=>"update"
123
+ # }
124
+ # }
125
+ if first_in_group
126
+ log ""
127
+ log ""
128
+ log "Planned Changes:"
129
+ end
130
+ # {
131
+ # :change=>{
132
+ # "resource"=>{"addr"=>"aws_iam_policy.crossplane_aws_ecr[0]", "module"=>"", "resource"=>"aws_iam_policy.crossplane_aws_ecr[0]", "implied_provider"=>"aws", "resource_type"=>"aws_iam_policy", "resource_name"=>"crossplane_aws_ecr", "resource_key"=>0},
133
+ # "action"=>"update"
134
+ # },
135
+ # :type=>"resource_drift",
136
+ # :level=>"info",
137
+ # :message=>"aws_iam_policy.crossplane_aws_ecr[0]: Drift detected (update)",
138
+ # :module=>"terraform.ui",
139
+ # :timestamp=>"2023-09-26T17:11:46.340117-07:00",
140
+ # :stream=>:stdout
141
+ # }
142
+
143
+ log format("[%<action>s] %<addr>s - Drift Detected (%<change_action>s)",
144
+ action: PlanSummaryHandler.format_action(parsed_line[:change]["action"]),
145
+ addr: PlanSummaryHandler.format_address(parsed_line[:change]["resource"]["addr"]),
146
+ change_action: parsed_line[:change]["action"]), depth: 1
147
+ when "planned_change"
148
+ if skip_plan_summary
149
+ log "" if first_in_group
150
+ else
151
+ first_in_group = !seen.call(parsed_line[:module], "resource_drift") &&
152
+ !seen.call(parsed_line[:module], "planned_change")
153
+ # {
154
+ # :change=>
155
+ # {"resource"=>
156
+ # {"addr"=>"module.application.kubectl_manifest.application",
157
+ # "module"=>"module.application",
158
+ # "resource"=>"kubectl_manifest.application",
159
+ # "implied_provider"=>"kubectl",
160
+ # "resource_type"=>"kubectl_manifest",
161
+ # "resource_name"=>"application",
162
+ # "resource_key"=>nil},
163
+ # "action"=>"create"},
164
+ # :type=>"planned_change",
165
+ # :level=>"info",
166
+ # :message=>"module.application.kubectl_manifest.application: Plan to create",
167
+ # :module=>"terraform.ui",
168
+ # :timestamp=>"2023-08-25T14:48:46.005185-07:00",
169
+ # }
170
+ if first_in_group
171
+ log ""
172
+ log ""
173
+ log "Planned Changes:"
174
+ end
175
+ log format("[%<action>s] %<addr>s",
176
+ action: PlanSummaryHandler.format_action(parsed_line[:change]["action"]),
177
+ addr: PlanSummaryHandler.format_address(parsed_line[:change]["resource"]["addr"])), depth: 1
178
+ end
179
+ when "change_summary"
180
+ if skip_plan_summary
181
+ log ""
182
+ else
183
+ # {
184
+ # :changes=>{"add"=>1, "change"=>0, "import"=>0, "remove"=>0, "operation"=>"plan"},
185
+ # :type=>"change_summary",
186
+ # :level=>"info",
187
+ # :message=>"Plan: 1 to add, 0 to change, 0 to destroy.",
188
+ # :module=>"terraform.ui",
189
+ # :timestamp=>"2023-08-25T14:48:46.005211-07:00",
190
+ # :stream=>:stdout
191
+ # }
192
+ log ""
193
+ # puts parsed_line[:message]
194
+ log "#{parsed_line[:changes]['operation'].capitalize} summary: " + parsed_line[:changes].without("operation").map { |k, v|
195
+ color = PlanSummaryHandler.color_for_action(k)
196
+ "#{pastel.yellow(v)} to #{pastel.decorate(k, color)}" if v.positive?
197
+ }.compact.join(" ")
198
+ end
199
+
200
+ when "output"
201
+ when "outputs"
202
+ # json plan output summary
203
+ false # handled when reading the plan json
204
+ else
205
+ print_plan_line(parsed_line, from: "parse_tf_ui_line,else")
206
+ end
207
+ end
208
+
174
209
  def pretty_plan_v2(filename, targets: [])
175
210
  meta = {}
176
211
  meta[:seen] = {
@@ -185,141 +220,25 @@ module MuxTf
185
220
  when "info"
186
221
  case parsed_line[:module]
187
222
  when "terraform.ui"
188
- case parsed_line[:type]
189
- when "version"
190
- meta[:terraform_version] = parsed_line[:terraform]
191
- meta[:terraform_ui_version] = parsed_line[:ui]
192
- when "apply_start", "refresh_start"
193
- first_in_group = !seen.call(parsed_line[:module], "apply_start") &&
194
- !seen.call(parsed_line[:module], "refresh_start")
195
- log "Refreshing ", depth: 1, newline: false if first_in_group
196
- # {
197
- # :hook=>{
198
- # "resource"=>{
199
- # "addr"=>"data.aws_eks_cluster_auth.this",
200
- # "module"=>"",
201
- # "resource"=>"data.aws_eks_cluster_auth.this",
202
- # "implied_provider"=>"aws",
203
- # "resource_type"=>"aws_eks_cluster_auth",
204
- # "resource_name"=>"this",
205
- # "resource_key"=>nil
206
- # },
207
- # "action"=>"read"
208
- # }
209
- # }
210
- log ".", newline: false
211
- when "apply_complete", "refresh_complete"
212
- # {
213
- # :hook=>{
214
- # "resource"=>{
215
- # "addr"=>"data.aws_eks_cluster_auth.this",
216
- # "module"=>"",
217
- # "resource"=>"data.aws_eks_cluster_auth.this",
218
- # "implied_provider"=>"aws",
219
- # "resource_type"=>"aws_eks_cluster_auth",
220
- # "resource_name"=>"this",
221
- # "resource_key"=>nil
222
- # },
223
- # "action"=>"read",
224
- # "id_key"=>"id",
225
- # "id_value"=>"admin",
226
- # "elapsed_seconds"=>0
227
- # }
228
- # }
229
- # noop
230
- when "resource_drift"
231
- first_in_group = !seen.call(parsed_line[:module], "resource_drift") &&
232
- !seen.call(parsed_line[:module], "planned_change")
233
- # {
234
- # :change=>{
235
- # "resource"=>{"addr"=>"module.application.kubectl_manifest.application", "module"=>"module.application", "resource"=>"kubectl_manifest.application", "implied_provider"=>"kubectl", "resource_type"=>"kubectl_manifest", "resource_name"=>"application", "resource_key"=>nil},
236
- # "action"=>"update"
237
- # }
238
- # }
239
- if first_in_group
240
- log ""
241
- log ""
242
- log "Planned Changes:"
243
- end
244
- # {
245
- # :change=>{
246
- # "resource"=>{"addr"=>"aws_iam_policy.crossplane_aws_ecr[0]", "module"=>"", "resource"=>"aws_iam_policy.crossplane_aws_ecr[0]", "implied_provider"=>"aws", "resource_type"=>"aws_iam_policy", "resource_name"=>"crossplane_aws_ecr", "resource_key"=>0},
247
- # "action"=>"update"
248
- # },
249
- # :type=>"resource_drift",
250
- # :level=>"info",
251
- # :message=>"aws_iam_policy.crossplane_aws_ecr[0]: Drift detected (update)",
252
- # :module=>"terraform.ui",
253
- # :timestamp=>"2023-09-26T17:11:46.340117-07:00",
254
- # :stream=>:stdout
255
- # }
256
-
257
- log format("[%<action>s] %<addr>s - Drift Detected (%<change_action>s)",
258
- action: PlanSummaryHandler.format_action(parsed_line[:change]["action"]),
259
- addr: PlanSummaryHandler.format_address(parsed_line[:change]["resource"]["addr"]),
260
- change_action: parsed_line[:change]["action"]), depth: 1
261
- when "planned_change"
262
- first_in_group = !seen.call(parsed_line[:module], "resource_drift") &&
263
- !seen.call(parsed_line[:module], "planned_change")
264
- # {
265
- # :change=>
266
- # {"resource"=>
267
- # {"addr"=>"module.application.kubectl_manifest.application",
268
- # "module"=>"module.application",
269
- # "resource"=>"kubectl_manifest.application",
270
- # "implied_provider"=>"kubectl",
271
- # "resource_type"=>"kubectl_manifest",
272
- # "resource_name"=>"application",
273
- # "resource_key"=>nil},
274
- # "action"=>"create"},
275
- # :type=>"planned_change",
276
- # :level=>"info",
277
- # :message=>"module.application.kubectl_manifest.application: Plan to create",
278
- # :module=>"terraform.ui",
279
- # :timestamp=>"2023-08-25T14:48:46.005185-07:00",
280
- # }
281
- if first_in_group
282
- log ""
283
- log ""
284
- log "Planned Changes:"
285
- end
286
- log format("[%<action>s] %<addr>s",
287
- action: PlanSummaryHandler.format_action(parsed_line[:change]["action"]),
288
- addr: PlanSummaryHandler.format_address(parsed_line[:change]["resource"]["addr"])), depth: 1
289
- when "change_summary"
290
- # {
291
- # :changes=>{"add"=>1, "change"=>0, "import"=>0, "remove"=>0, "operation"=>"plan"},
292
- # :type=>"change_summary",
293
- # :level=>"info",
294
- # :message=>"Plan: 1 to add, 0 to change, 0 to destroy.",
295
- # :module=>"terraform.ui",
296
- # :timestamp=>"2023-08-25T14:48:46.005211-07:00",
297
- # :stream=>:stdout
298
- # }
299
- log ""
300
- # puts parsed_line[:message]
301
- log "#{parsed_line[:changes]['operation'].capitalize} summary: " + parsed_line[:changes].without("operation").map { |k, v|
302
- color = PlanSummaryHandler.color_for_action(k)
303
- "#{pastel.yellow(v)} to #{pastel.decorate(k, color)}" if v.positive?
304
- }.compact.join(" ")
305
-
306
- else
307
- print_plan_line(parsed_line)
308
- end
223
+ parse_tf_ui_line(parsed_line, meta, seen, skip_plan_summary: true)
224
+ when "tofu.ui" # rubocop:disable Lint/DuplicateBranch
225
+ parse_tf_ui_line(parsed_line, meta, seen, skip_plan_summary: true)
309
226
  else
310
- print_plan_line(parsed_line)
227
+ print_plan_line(parsed_line, from: "pretty_plan_v2,info,else")
311
228
  end
312
229
  when "error"
313
230
  if parsed_line[:diagnostic]
314
231
  handled_error = false
315
232
  muted_error = false
233
+ current_meta_error = {}
316
234
  unless parsed_line[:module] == "terragrunt" && parsed_line[:type] == "tf_failed"
317
235
  meta[:errors] ||= []
318
- meta[:errors] << {
236
+ current_meta_error = {
319
237
  type: :error,
320
238
  message: parsed_line[:diagnostic]["summary"],
321
239
  body: parsed_line[:diagnostic]["detail"].split("\n")
322
240
  }
241
+ meta[:errors] << current_meta_error
323
242
  end
324
243
 
325
244
  if parsed_line[:diagnostic]["summary"] == "Error acquiring the state lock"
@@ -332,13 +251,19 @@ module MuxTf
332
251
 
333
252
  unless muted_error
334
253
  if handled_error
335
- print_plan_line(parsed_line, without: [:diagnostic])
254
+ print_plan_line(parsed_line, without: [:diagnostic], from: "pretty_plan_v2,error,handled")
336
255
  else
337
- print_plan_line(parsed_line)
256
+ # print_plan_line(parsed_line, from: "pretty_plan_v2,error,unhandled_error")
257
+ print_unhandled_error_line(parsed_line)
258
+ current_meta_error[:printed] = true
338
259
  end
339
260
  end
261
+ elsif parsed_line[:module] == :stderr && parsed_line[:type] == "unknown" && parsed_line[:message][0] == "{"
262
+ # probably a TG error line ...
263
+ # sometimes this could have multiple lines of json ..
264
+ print_tg_error_line(parsed_line)
340
265
  else
341
- print_plan_line(parsed_line)
266
+ print_plan_line(parsed_line, from: "pretty_plan_v2,error,else")
342
267
  end
343
268
  end
344
269
 
@@ -346,7 +271,6 @@ module MuxTf
346
271
  }
347
272
  [status.status, meta]
348
273
  end
349
- # rubocop:enable Metrics/AbcSize
350
274
 
351
275
  def setup_plan_v1_parser(parser)
352
276
  parser.state(:info, /^Acquiring state lock/)
@@ -365,17 +289,17 @@ module MuxTf
365
289
  parser.state(:output_info, /^Changes to Outputs:$/, [:none])
366
290
  parser.state(:none, /^$/, [:output_info])
367
291
 
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])
292
+ parser.state(:plan_info, /(?:Terraform|OpenTofu) will perform the following actions:/, [:none])
293
+ parser.state(:plan_info, /You can apply this plan to save these new output values to the (?:Terraform|OpenTofu) state/, [:none])
370
294
  parser.state(:plan_summary, /^Plan:/, [:plan_info])
371
295
 
372
- parser.state(:plan_legend, /^Terraform used the selected providers to generate the following execution$/)
296
+ parser.state(:plan_legend, /^(?:Terraform|OpenTofu) used the selected providers to generate the following execution$/)
373
297
  parser.state(:none, /^$/, [:plan_legend])
374
298
 
375
- parser.state(:plan_info, /Terraform planned the following actions, but then encountered a problem:/, [:none])
299
+ parser.state(:plan_info, /(?:Terraform|OpenTofu) planned the following actions, but then encountered a problem:/, [:none])
376
300
  parser.state(:plan_info, /No changes. Your infrastructure matches the configuration./, [:none])
377
301
 
378
- parser.state(:plan_error, /Planning failed. Terraform encountered an error while generating this plan./, [:refreshing, :none])
302
+ parser.state(:plan_error, /Planning failed. (?:Terraform|OpenTofu) encountered an error while generating this plan./, [:refreshing, :none])
379
303
 
380
304
  # this extends the error block to include the lock info
381
305
  parser.state(:error_lock_info, /Lock Info/, [:error_block_error])
@@ -469,7 +393,7 @@ module MuxTf
469
393
 
470
394
  parser = StatefulParser.new(normalizer: pastel.method(:strip))
471
395
 
472
- setup_init_parser(parser)
396
+ InitFormatter.setup_init_parser(parser)
473
397
  setup_plan_v1_parser(parser)
474
398
 
475
399
  setup_error_handling(parser,
@@ -492,7 +416,7 @@ module MuxTf
492
416
 
493
417
  if (handled = handle_plan_v1_line(state, line, meta, first_in_state: first_in_state, stripped_line: stripped_line))
494
418
  # great!
495
- elsif (handled = handle_init_line(state, line, meta, phase: init_phase, stripped_line: stripped_line))
419
+ elsif (handled = InitFormatter.handle_init_line(state, line, meta, phase: init_phase, stripped_line: stripped_line))
496
420
  init_phase = handled[:phase]
497
421
  elsif handle_error_states(meta, state, line)
498
422
  # no-op
@@ -518,201 +442,8 @@ module MuxTf
518
442
  [status.status, meta]
519
443
  end
520
444
 
521
- def init_status_to_remedies(status, meta)
522
- remedies = Set.new
523
- if status != 0
524
- remedies << :reconfigure if meta[:need_reconfigure]
525
- remedies << :auth if meta[:need_auth]
526
- log "!! expected meta[:errors] to be set, how did we get here?" unless meta[:errors]
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/)
529
- end
530
- if remedies.empty?
531
- log "!! don't know how to generate init remedies for this"
532
- log "!! Status: #{status}"
533
- log "!! Meta:"
534
- log meta.to_yaml.split("\n").map { |l| "!! #{l}" }.join("\n")
535
- remedies << :unknown
536
- end
537
- end
538
- remedies
539
- end
540
-
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])
551
- end
552
-
553
- def handle_init_line(state, line, meta, phase:, stripped_line:)
554
- case state
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
569
- else
570
- phase = state
571
- log "Initializing modules ", depth: 1
572
- end
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")
586
- end
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")
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 != ""
667
- else
668
- return false
669
- # log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
670
- end
671
-
672
- { phase: phase }
673
- end
674
-
675
- def run_tf_init(upgrade: nil, reconfigure: nil)
676
- phase = :init
677
-
678
- meta = {}
679
-
680
- parser = StatefulParser.new(normalizer: pastel.method(:strip))
681
-
682
- setup_init_parser(parser)
683
-
684
- setup_error_handling(parser, from_states: [:plugins, :modules_init])
685
-
686
- stderr_handler = StderrLineHandler.new(operation: :init)
687
-
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
701
- else
702
- log_unhandled_line(state, line, reason: "unexpected state")
703
- end
704
- end
705
- end
706
- }
707
-
708
- stderr_handler.flush
709
- stderr_handler.merge_meta_into(meta)
710
-
711
- [status.status, meta]
712
- end
713
-
714
445
  def print_validation_errors(info)
715
- return unless (info["error_count"]).positive? || (info["warning_count"]).positive?
446
+ return unless info["error_count"].positive? || info["warning_count"].positive?
716
447
 
717
448
  log "Encountered #{pastel.red(info['error_count'])} Errors and #{pastel.yellow(info['warning_count'])} Warnings!", depth: 2
718
449
  info["diagnostics"].each do |dinfo|
@@ -726,7 +457,7 @@ module MuxTf
726
457
  def process_validation(info)
727
458
  remedies = Set.new
728
459
 
729
- if (info["error_count"]).positive? || (info["warning_count"]).positive?
460
+ if info["error_count"].positive? || info["warning_count"].positive?
730
461
  info["diagnostics"].each do |dinfo| # rubocop:disable Metrics/BlockLength
731
462
  item_handled = false
732
463
 
@@ -736,7 +467,8 @@ module MuxTf
736
467
  /Module not installed/,
737
468
  /Module source has changed/,
738
469
  /Required plugins are not installed/,
739
- /Module version requirements have changed/
470
+ /Module version requirements have changed/,
471
+ /to install all modules required by this configuration/
740
472
  remedies << :init
741
473
  item_handled = true
742
474
  when /Missing required argument/,
@@ -751,7 +483,7 @@ module MuxTf
751
483
  item_handled = true
752
484
  end
753
485
 
754
- if dinfo["severity"] == "error" && dinfo["snippet"]
486
+ if !item_handled && dinfo["severity"] == "error" && dinfo["snippet"]
755
487
  # trying something new .. assuming anything with a snippet is a user error
756
488
  remedies << :user_error
757
489
  item_handled = true
@@ -778,83 +510,6 @@ module MuxTf
778
510
 
779
511
  remedies
780
512
  end
781
-
782
- private
783
-
784
- def format_validation_range(dinfo, color)
785
- range = dinfo["range"]
786
- # filename: "../../../modules/pods/jane_pod/main.tf"
787
- # start:
788
- # line: 151
789
- # column: 27
790
- # byte: 6632
791
- # end:
792
- # line: 151
793
- # column: 53
794
- # byte: 6658
795
-
796
- context_lines = 3
797
-
798
- lines = range["start"]["line"]..range["end"]["line"]
799
- columns = range["start"]["column"]..range["end"]["column"]
800
-
801
- # on ../../../modules/pods/jane_pod/main.tf line 151, in module "jane":
802
- # 151: jane_resources_preset = var.jane_resources_presetx
803
- output = []
804
- lines_info = if lines.size == 1
805
- "#{lines.first}:#{columns.first}"
806
- else
807
- "#{lines.first}:#{columns.first} to #{lines.last}:#{columns.last}"
808
- end
809
- output << "on: #{range['filename']} line#{lines.size > 1 ? 's' : ''}: #{lines_info}"
810
-
811
- # TODO: in terragrunt mode, we need to somehow figure out the path to the cache root, all the paths will end up being relative to that
812
- if File.exist?(range["filename"])
813
- file_lines = File.read(range["filename"]).split("\n")
814
- extract_range = (([lines.first - context_lines,
815
- 0].max)..([lines.last + context_lines, file_lines.length - 1].min))
816
- file_lines.each_with_index do |line, index|
817
- if extract_range.cover?(index + 1)
818
- if lines.cover?(index + 1)
819
- start_col = 1
820
- end_col = :max
821
- if index + 1 == lines.first
822
- start_col = columns.first
823
- elsif index + 1 == lines.last
824
- start_col = columns.last
825
- end
826
- painted_line = paint_line(line, color, start_col: start_col, end_col: end_col)
827
- output << "#{pastel.decorate('>', color)} #{index + 1}: #{painted_line}"
828
- else
829
- output << " #{index + 1}: #{line}"
830
- end
831
- end
832
- end
833
- elsif dinfo["snippet"]
834
- # {
835
- # "context"=>"locals",
836
- # "code"=>" aws_iam_policy.crossplane_aws_ecr.arn",
837
- # "start_line"=>72,
838
- # "highlight_start_offset"=>8,
839
- # "highlight_end_offset"=>41,
840
- # "values"=>[]
841
- # }
842
- output << "Code:"
843
- dinfo["snippet"]["code"].split("\n").each do |l|
844
- output << " > #{l}"
845
- end
846
- end
847
-
848
- output
849
- end
850
-
851
- def paint_line(line, *paint_options, start_col: 1, end_col: :max)
852
- end_col = line.length if end_col == :max
853
- prefix = line[0, start_col - 1]
854
- suffix = line[end_col..]
855
- middle = line[start_col - 1..end_col - 1]
856
- "#{prefix}#{pastel.decorate(middle, *paint_options)}#{suffix}"
857
- end
858
513
  end
859
514
  end
860
515
  end