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.
- checksums.yaml +4 -4
- data/lib/mux_tf/cli/current/plan_command.rb +18 -10
- data/lib/mux_tf/cli/current.rb +94 -49
- data/lib/mux_tf/formatter_common.rb +257 -0
- data/lib/mux_tf/init_formatter.rb +306 -0
- data/lib/mux_tf/plan_formatter.rb +174 -519
- data/lib/mux_tf/plan_summary_handler.rb +16 -2
- data/lib/mux_tf/plan_utils.rb +198 -55
- data/lib/mux_tf/stderr_line_handler.rb +1 -1
- data/lib/mux_tf/terraform_helpers.rb +2 -1
- data/lib/mux_tf/version.rb +1 -1
- data/lib/mux_tf/version_check.rb +1 -1
- data/lib/mux_tf/yaml_cache.rb +51 -34
- data/mux_tf.gemspec +6 -1
- metadata +49 -9
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module MuxTf
|
4
|
-
class PlanFormatter
|
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
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
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
|
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
|
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
|