mux_tf 0.15.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 +131 -0
- data/lib/mux_tf/cli/current.rb +172 -206
- data/lib/mux_tf/cli/mux.rb +168 -5
- data/lib/mux_tf/cli/plan_summary.rb +12 -21
- data/lib/mux_tf/error_handling_methods.rb +79 -0
- data/lib/mux_tf/formatter_common.rb +257 -0
- data/lib/mux_tf/handlers/plan_handler.rb +8 -0
- data/lib/mux_tf/handlers.rb +6 -0
- data/lib/mux_tf/init_formatter.rb +306 -0
- data/lib/mux_tf/plan_formatter.rb +285 -602
- data/lib/mux_tf/plan_summary_handler.rb +52 -31
- data/lib/mux_tf/plan_utils.rb +215 -56
- data/lib/mux_tf/resource_tokenizer.rb +1 -1
- data/lib/mux_tf/stderr_line_handler.rb +145 -0
- data/lib/mux_tf/terraform_helpers.rb +46 -7
- data/lib/mux_tf/tmux.rb +55 -6
- data/lib/mux_tf/version.rb +1 -1
- data/lib/mux_tf/yaml_cache.rb +51 -34
- data/lib/mux_tf.rb +6 -12
- data/mux_tf.gemspec +12 -1
- metadata +110 -9
@@ -1,16 +1,15 @@
|
|
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
|
-
|
10
|
-
|
11
|
-
p [state, pastel.strip(line), reason]
|
12
|
-
end
|
9
|
+
extend ErrorHandlingMethods
|
10
|
+
extend FormatterCommon
|
13
11
|
|
12
|
+
class << self
|
14
13
|
def pretty_plan(filename, targets: [])
|
15
14
|
if ENV["JSON_PLAN"]
|
16
15
|
pretty_plan_v2(filename, targets: targets)
|
@@ -19,117 +18,12 @@ module MuxTf
|
|
19
18
|
end
|
20
19
|
end
|
21
20
|
|
22
|
-
def parse_non_json_plan_line(raw_line)
|
23
|
-
result = {}
|
24
|
-
|
25
|
-
if raw_line.match(/^time=(?<timestamp>[^ ]+) level=(?<level>[^ ]+) msg=(?<message>.+?)(?: prefix=\[(?<prefix>.+?)\])?\s*$/)
|
26
|
-
result.merge!($LAST_MATCH_INFO.named_captures.symbolize_keys)
|
27
|
-
result[:module] = "terragrunt"
|
28
|
-
result.delete(:prefix) unless result[:prefix]
|
29
|
-
result[:prefix] = Pathname.new(result[:prefix]).relative_path_from(Dir.getwd).to_s if result[:prefix]
|
30
|
-
|
31
|
-
result[:merge_up] = true if result[:message].match(/^\d+ errors? occurred:$/)
|
32
|
-
elsif raw_line.strip == ""
|
33
|
-
result[:blank] = true
|
34
|
-
else
|
35
|
-
result[:message] = raw_line
|
36
|
-
result[:merge_up] = true
|
37
|
-
end
|
38
|
-
|
39
|
-
# 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]
|
40
|
-
# time=2023-08-25T11:44:41-07:00 level=error msg=1 error occurred:
|
41
|
-
# * [/Users/piotr/Work/janepods/.terragrunt-cache/BM86IAj5tW4bZga2lXeYT8tdOKI/V0IEypKSfyl-kHfCnRNAqyX02V8/modules/event-bus] exit status 2
|
42
|
-
#
|
43
|
-
#
|
44
|
-
result
|
45
|
-
end
|
46
|
-
|
47
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
48
21
|
def tf_plan_json(out:, targets: [], &block)
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
if result[:message].match(/^Terraform invocation failed in (.+)/)
|
55
|
-
result[:type] = "tf_failed"
|
56
|
-
|
57
|
-
lines = result[:message].split("\n")
|
58
|
-
result[:diagnostic] = {
|
59
|
-
"summary" => "Terraform invocation failed",
|
60
|
-
"detail" => result[:message],
|
61
|
-
roots: [],
|
62
|
-
extra: []
|
63
|
-
}
|
64
|
-
|
65
|
-
lines.each do |line|
|
66
|
-
if line.match(/^\s+\* \[(.+)\] exit status (\d+)$/)
|
67
|
-
result[:diagnostic][:roots] << {
|
68
|
-
path: $LAST_MATCH_INFO[1],
|
69
|
-
status: $LAST_MATCH_INFO[2].to_i
|
70
|
-
}
|
71
|
-
elsif line.match(/^\d+ errors? occurred$/)
|
72
|
-
# noop
|
73
|
-
else
|
74
|
-
result[:diagnostic][:extra] << line
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
result[:message] = "Terraform invocation failed"
|
79
|
-
end
|
80
|
-
|
81
|
-
block.call(result)
|
82
|
-
}
|
83
|
-
last_stderr_line = nil
|
84
|
-
status = tf_plan(out: out, detailed_exitcode: true, color: true, compact_warnings: false, json: true, input: false,
|
85
|
-
targets: targets) { |(stream, raw_line)|
|
86
|
-
case stream
|
87
|
-
# when :command
|
88
|
-
# puts raw_line
|
89
|
-
when :stdout
|
90
|
-
parsed_line = JSON.parse(raw_line)
|
91
|
-
parsed_line.keys.each do |key| # rubocop:disable Style/HashEachMethods -- intentional, allow adding keys to hash while iterating
|
92
|
-
if key[0] == "@"
|
93
|
-
parsed_line[key[1..]] = parsed_line[key]
|
94
|
-
parsed_line.delete(key)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
parsed_line.symbolize_keys!
|
98
|
-
parsed_line[:stream] = stream
|
99
|
-
if last_stderr_line
|
100
|
-
emit_line.call(last_stderr_line)
|
101
|
-
last_stderr_line = nil
|
102
|
-
end
|
103
|
-
emit_line.call(parsed_line)
|
104
|
-
when :stderr
|
105
|
-
parsed_line = parse_non_json_plan_line(raw_line)
|
106
|
-
parsed_line[:stream] = stream
|
107
|
-
|
108
|
-
if parsed_line[:blank]
|
109
|
-
if last_stderr_line
|
110
|
-
emit_line.call(last_stderr_line)
|
111
|
-
last_stderr_line = nil
|
112
|
-
end
|
113
|
-
elsif parsed_line[:merge_up]
|
114
|
-
if last_stderr_line
|
115
|
-
last_stderr_line[:message] += "\n#{parsed_line[:message]}"
|
116
|
-
else
|
117
|
-
# this is just a standalone message then
|
118
|
-
parsed_line.delete(:merge_up)
|
119
|
-
last_stderr_line = parsed_line
|
120
|
-
end
|
121
|
-
elsif last_stderr_line
|
122
|
-
emit_line.call(last_stderr_line)
|
123
|
-
last_stderr_line = parsed_line
|
124
|
-
else
|
125
|
-
last_stderr_line = parsed_line
|
126
|
-
end
|
127
|
-
end
|
128
|
-
}
|
129
|
-
emit_line.call(last_stderr_line) if last_stderr_line
|
130
|
-
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)
|
131
26
|
end
|
132
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
133
27
|
|
134
28
|
def parse_lock_info(detail)
|
135
29
|
# Lock Info:
|
@@ -148,7 +42,7 @@ module MuxTf
|
|
148
42
|
result
|
149
43
|
end
|
150
44
|
|
151
|
-
def print_plan_line(parsed_line, without: [])
|
45
|
+
def print_plan_line(parsed_line, without: [], from: nil)
|
152
46
|
default_without = [
|
153
47
|
:level,
|
154
48
|
:module,
|
@@ -160,8 +54,9 @@ module MuxTf
|
|
160
54
|
:ui
|
161
55
|
]
|
162
56
|
extra = parsed_line.without(*default_without, *without)
|
163
|
-
data = parsed_line.merge(extra: extra)
|
57
|
+
data = parsed_line.merge(extra: extra).merge(from: from)
|
164
58
|
log_line = [
|
59
|
+
"%<from>s",
|
165
60
|
"%<level>-6s",
|
166
61
|
"%<module>-12s",
|
167
62
|
"%<type>-10s",
|
@@ -174,7 +69,143 @@ module MuxTf
|
|
174
69
|
log log_line
|
175
70
|
end
|
176
71
|
|
177
|
-
|
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
|
+
|
178
209
|
def pretty_plan_v2(filename, targets: [])
|
179
210
|
meta = {}
|
180
211
|
meta[:seen] = {
|
@@ -189,141 +220,25 @@ module MuxTf
|
|
189
220
|
when "info"
|
190
221
|
case parsed_line[:module]
|
191
222
|
when "terraform.ui"
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
meta[:terraform_ui_version] = parsed_line[:ui]
|
196
|
-
when "apply_start", "refresh_start"
|
197
|
-
first_in_group = !seen.call(parsed_line[:module], "apply_start") &&
|
198
|
-
!seen.call(parsed_line[:module], "refresh_start")
|
199
|
-
log "Refreshing ", depth: 1, newline: false if first_in_group
|
200
|
-
# {
|
201
|
-
# :hook=>{
|
202
|
-
# "resource"=>{
|
203
|
-
# "addr"=>"data.aws_eks_cluster_auth.this",
|
204
|
-
# "module"=>"",
|
205
|
-
# "resource"=>"data.aws_eks_cluster_auth.this",
|
206
|
-
# "implied_provider"=>"aws",
|
207
|
-
# "resource_type"=>"aws_eks_cluster_auth",
|
208
|
-
# "resource_name"=>"this",
|
209
|
-
# "resource_key"=>nil
|
210
|
-
# },
|
211
|
-
# "action"=>"read"
|
212
|
-
# }
|
213
|
-
# }
|
214
|
-
log ".", newline: false
|
215
|
-
when "apply_complete", "refresh_complete"
|
216
|
-
# {
|
217
|
-
# :hook=>{
|
218
|
-
# "resource"=>{
|
219
|
-
# "addr"=>"data.aws_eks_cluster_auth.this",
|
220
|
-
# "module"=>"",
|
221
|
-
# "resource"=>"data.aws_eks_cluster_auth.this",
|
222
|
-
# "implied_provider"=>"aws",
|
223
|
-
# "resource_type"=>"aws_eks_cluster_auth",
|
224
|
-
# "resource_name"=>"this",
|
225
|
-
# "resource_key"=>nil
|
226
|
-
# },
|
227
|
-
# "action"=>"read",
|
228
|
-
# "id_key"=>"id",
|
229
|
-
# "id_value"=>"admin",
|
230
|
-
# "elapsed_seconds"=>0
|
231
|
-
# }
|
232
|
-
# }
|
233
|
-
# noop
|
234
|
-
when "resource_drift"
|
235
|
-
first_in_group = !seen.call(parsed_line[:module], "resource_drift") &&
|
236
|
-
!seen.call(parsed_line[:module], "planned_change")
|
237
|
-
# {
|
238
|
-
# :change=>{
|
239
|
-
# "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},
|
240
|
-
# "action"=>"update"
|
241
|
-
# }
|
242
|
-
# }
|
243
|
-
if first_in_group
|
244
|
-
log ""
|
245
|
-
log ""
|
246
|
-
log "Planned Changes:"
|
247
|
-
end
|
248
|
-
# {
|
249
|
-
# :change=>{
|
250
|
-
# "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},
|
251
|
-
# "action"=>"update"
|
252
|
-
# },
|
253
|
-
# :type=>"resource_drift",
|
254
|
-
# :level=>"info",
|
255
|
-
# :message=>"aws_iam_policy.crossplane_aws_ecr[0]: Drift detected (update)",
|
256
|
-
# :module=>"terraform.ui",
|
257
|
-
# :timestamp=>"2023-09-26T17:11:46.340117-07:00",
|
258
|
-
# :stream=>:stdout
|
259
|
-
# }
|
260
|
-
|
261
|
-
log format("[%<action>s] %<addr>s - Drift Detected (%<change_action>s)",
|
262
|
-
action: PlanSummaryHandler.format_action(parsed_line[:change]["action"]),
|
263
|
-
addr: PlanSummaryHandler.format_address(parsed_line[:change]["resource"]["addr"]),
|
264
|
-
change_action: parsed_line[:change]["action"]), depth: 1
|
265
|
-
when "planned_change"
|
266
|
-
first_in_group = !seen.call(parsed_line[:module], "resource_drift") &&
|
267
|
-
!seen.call(parsed_line[:module], "planned_change")
|
268
|
-
# {
|
269
|
-
# :change=>
|
270
|
-
# {"resource"=>
|
271
|
-
# {"addr"=>"module.application.kubectl_manifest.application",
|
272
|
-
# "module"=>"module.application",
|
273
|
-
# "resource"=>"kubectl_manifest.application",
|
274
|
-
# "implied_provider"=>"kubectl",
|
275
|
-
# "resource_type"=>"kubectl_manifest",
|
276
|
-
# "resource_name"=>"application",
|
277
|
-
# "resource_key"=>nil},
|
278
|
-
# "action"=>"create"},
|
279
|
-
# :type=>"planned_change",
|
280
|
-
# :level=>"info",
|
281
|
-
# :message=>"module.application.kubectl_manifest.application: Plan to create",
|
282
|
-
# :module=>"terraform.ui",
|
283
|
-
# :timestamp=>"2023-08-25T14:48:46.005185-07:00",
|
284
|
-
# }
|
285
|
-
if first_in_group
|
286
|
-
log ""
|
287
|
-
log ""
|
288
|
-
log "Planned Changes:"
|
289
|
-
end
|
290
|
-
log format("[%<action>s] %<addr>s",
|
291
|
-
action: PlanSummaryHandler.format_action(parsed_line[:change]["action"]),
|
292
|
-
addr: PlanSummaryHandler.format_address(parsed_line[:change]["resource"]["addr"])), depth: 1
|
293
|
-
when "change_summary"
|
294
|
-
# {
|
295
|
-
# :changes=>{"add"=>1, "change"=>0, "import"=>0, "remove"=>0, "operation"=>"plan"},
|
296
|
-
# :type=>"change_summary",
|
297
|
-
# :level=>"info",
|
298
|
-
# :message=>"Plan: 1 to add, 0 to change, 0 to destroy.",
|
299
|
-
# :module=>"terraform.ui",
|
300
|
-
# :timestamp=>"2023-08-25T14:48:46.005211-07:00",
|
301
|
-
# :stream=>:stdout
|
302
|
-
# }
|
303
|
-
log ""
|
304
|
-
# puts parsed_line[:message]
|
305
|
-
log "#{parsed_line[:changes]['operation'].capitalize} summary: " + parsed_line[:changes].without("operation").map { |k, v|
|
306
|
-
color = PlanSummaryHandler.color_for_action(k)
|
307
|
-
"#{pastel.yellow(v)} to #{pastel.decorate(k, color)}" if v.positive?
|
308
|
-
}.compact.join(" ")
|
309
|
-
|
310
|
-
else
|
311
|
-
print_plan_line(parsed_line)
|
312
|
-
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)
|
313
226
|
else
|
314
|
-
print_plan_line(parsed_line)
|
227
|
+
print_plan_line(parsed_line, from: "pretty_plan_v2,info,else")
|
315
228
|
end
|
316
229
|
when "error"
|
317
230
|
if parsed_line[:diagnostic]
|
318
231
|
handled_error = false
|
319
232
|
muted_error = false
|
233
|
+
current_meta_error = {}
|
320
234
|
unless parsed_line[:module] == "terragrunt" && parsed_line[:type] == "tf_failed"
|
321
235
|
meta[:errors] ||= []
|
322
|
-
|
236
|
+
current_meta_error = {
|
323
237
|
type: :error,
|
324
238
|
message: parsed_line[:diagnostic]["summary"],
|
325
239
|
body: parsed_line[:diagnostic]["detail"].split("\n")
|
326
240
|
}
|
241
|
+
meta[:errors] << current_meta_error
|
327
242
|
end
|
328
243
|
|
329
244
|
if parsed_line[:diagnostic]["summary"] == "Error acquiring the state lock"
|
@@ -336,13 +251,19 @@ module MuxTf
|
|
336
251
|
|
337
252
|
unless muted_error
|
338
253
|
if handled_error
|
339
|
-
print_plan_line(parsed_line, without: [:diagnostic])
|
254
|
+
print_plan_line(parsed_line, without: [:diagnostic], from: "pretty_plan_v2,error,handled")
|
340
255
|
else
|
341
|
-
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
|
342
259
|
end
|
343
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)
|
344
265
|
else
|
345
|
-
print_plan_line(parsed_line)
|
266
|
+
print_plan_line(parsed_line, from: "pretty_plan_v2,error,else")
|
346
267
|
end
|
347
268
|
end
|
348
269
|
|
@@ -350,339 +271,179 @@ module MuxTf
|
|
350
271
|
}
|
351
272
|
[status.status, meta]
|
352
273
|
end
|
353
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
354
|
-
|
355
|
-
def pretty_plan_v1(filename, targets: []) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
356
|
-
meta = {}
|
357
274
|
|
358
|
-
|
275
|
+
def setup_plan_v1_parser(parser)
|
359
276
|
parser.state(:info, /^Acquiring state lock/)
|
360
277
|
parser.state(:error, /(Error locking state|Error:)/, [:none, :blank, :info, :reading])
|
361
278
|
parser.state(:reading, /: (Reading...|Read complete after)/, [:none, :info, :reading])
|
362
279
|
parser.state(:none, /^$/, [:reading])
|
363
|
-
parser.state(:refreshing, /^.+: Refreshing state... \[id=/, [:none, :info, :reading])
|
280
|
+
parser.state(:refreshing, /^.+: Refreshing state... \[id=/, [:none, :info, :reading, :import])
|
364
281
|
parser.state(:refreshing, /Refreshing Terraform state in-memory prior to plan.../,
|
365
282
|
[:none, :blank, :info, :reading])
|
366
283
|
parser.state(:none, /^----------+$/, [:refreshing])
|
367
284
|
parser.state(:none, /^$/, [:refreshing])
|
368
285
|
|
286
|
+
parser.state(:import, /".+: Preparing import... \[id=.+\]$/, [:none, :import])
|
287
|
+
parser.state(:none, /^$/, [:import])
|
288
|
+
|
369
289
|
parser.state(:output_info, /^Changes to Outputs:$/, [:none])
|
370
290
|
parser.state(:none, /^$/, [:output_info])
|
371
291
|
|
372
|
-
parser.state(:plan_info, /Terraform will perform the following actions:/, [: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])
|
373
294
|
parser.state(:plan_summary, /^Plan:/, [:plan_info])
|
374
295
|
|
375
|
-
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$/)
|
376
297
|
parser.state(:none, /^$/, [:plan_legend])
|
377
298
|
|
378
|
-
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])
|
379
300
|
parser.state(:plan_info, /No changes. Your infrastructure matches the configuration./, [:none])
|
380
301
|
|
381
|
-
parser.state(:plan_error, /Planning failed. Terraform encountered an error while generating this plan./, [:refreshing])
|
302
|
+
parser.state(:plan_error, /Planning failed. (?:Terraform|OpenTofu) encountered an error while generating this plan./, [:refreshing, :none])
|
382
303
|
|
383
304
|
# this extends the error block to include the lock info
|
384
305
|
parser.state(:error_lock_info, /Lock Info/, [:error_block_error])
|
385
306
|
parser.state(:after_error, /^╵/, [:error_lock_info])
|
386
|
-
|
387
|
-
setup_error_handling(parser, from_states: [:plan_error, :none, :blank, :info, :reading, :plan_summary, :refreshing])
|
388
|
-
|
389
|
-
last_state = nil
|
390
|
-
|
391
|
-
status = tf_plan(out: filename, detailed_exitcode: true, compact_warnings: true, targets: targets) { |raw_line|
|
392
|
-
parser.parse(raw_line.rstrip) do |state, line|
|
393
|
-
first_in_state = last_state != state
|
394
|
-
|
395
|
-
case state
|
396
|
-
when :none
|
397
|
-
if line.blank?
|
398
|
-
# nothing
|
399
|
-
elsif raw_line.match(/Error when retrieving token from sso/) || raw_line.match(/Error loading SSO Token/)
|
400
|
-
meta[:need_auth] = true
|
401
|
-
log pastel.red("authentication problem"), depth: 2
|
402
|
-
else
|
403
|
-
log_unhandled_line(state, line, reason: "unexpected non blank line in :none state")
|
404
|
-
end
|
405
|
-
when :reading
|
406
|
-
clean_line = pastel.strip(line)
|
407
|
-
if clean_line.match(/^(.+): Reading...$/)
|
408
|
-
log "Reading: #{$LAST_MATCH_INFO[1]} ...", depth: 2
|
409
|
-
elsif clean_line.match(/^(.+): Read complete after ([^\[]+)(?: \[(.+)\])?$/)
|
410
|
-
if $LAST_MATCH_INFO[3]
|
411
|
-
log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]} [#{$LAST_MATCH_INFO[3]}]", depth: 3
|
412
|
-
else
|
413
|
-
log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]}", depth: 3
|
414
|
-
end
|
415
|
-
else
|
416
|
-
log_unhandled_line(state, line, reason: "unexpected line in :reading state")
|
417
|
-
end
|
418
|
-
when :info
|
419
|
-
if /Acquiring state lock. This may take a few moments.../.match?(line)
|
420
|
-
log "Acquiring state lock ...", depth: 2
|
421
|
-
else
|
422
|
-
log_unhandled_line(state, line, reason: "unexpected line in :info state")
|
423
|
-
end
|
424
|
-
when :plan_error
|
425
|
-
case pastel.strip(line)
|
426
|
-
when ""
|
427
|
-
# skip empty line
|
428
|
-
when /Releasing state lock. This may take a few moments"/
|
429
|
-
log line, depth: 2
|
430
|
-
when /Planning failed./ # rubocop:disable Lint/DuplicateBranch
|
431
|
-
log line, depth: 2
|
432
|
-
else
|
433
|
-
log_unhandled_line(state, line, reason: "unexpected line in :plan_error state")
|
434
|
-
end
|
435
|
-
when :error_lock_info
|
436
|
-
meta["error"] = "lock"
|
437
|
-
meta[$LAST_MATCH_INFO[1]] = $LAST_MATCH_INFO[2] if line =~ /([A-Z]+\S+)+:\s+(.+)$/
|
438
|
-
clean_line = pastel.strip(line).gsub(/^│ /, "")
|
439
|
-
if clean_line == ""
|
440
|
-
meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
|
441
|
-
else
|
442
|
-
meta[:current_error][:body] << clean_line
|
443
|
-
end
|
444
|
-
when :refreshing
|
445
|
-
if first_in_state
|
446
|
-
log "Refreshing state ", depth: 2, newline: false
|
447
|
-
else
|
448
|
-
print "."
|
449
|
-
end
|
450
|
-
when :plan_legend
|
451
|
-
puts if first_in_state
|
452
|
-
log line, depth: 2
|
453
|
-
when :refresh_done
|
454
|
-
puts if first_in_state
|
455
|
-
when :plan_info # rubocop:disable Lint/DuplicateBranch
|
456
|
-
puts if first_in_state
|
457
|
-
log line, depth: 2
|
458
|
-
when :output_info # rubocop:disable Lint/DuplicateBranch
|
459
|
-
puts if first_in_state
|
460
|
-
log line, depth: 2
|
461
|
-
when :plan_summary
|
462
|
-
log line, depth: 2
|
463
|
-
else
|
464
|
-
log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
|
465
|
-
end
|
466
|
-
last_state = state
|
467
|
-
end
|
468
|
-
}
|
469
|
-
[status.status, meta]
|
470
307
|
end
|
471
308
|
|
472
|
-
def
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
309
|
+
def handle_plan_v1_line(state, line, meta, first_in_state:, stripped_line:)
|
310
|
+
case state
|
311
|
+
when :none
|
312
|
+
if line.blank?
|
313
|
+
# nothing
|
314
|
+
elsif stripped_line.match(/Error when retrieving token from sso/) || stripped_line.match(/Error loading SSO Token/)
|
315
|
+
meta[:need_auth] = true
|
316
|
+
log pastel.red("authentication problem"), depth: 2
|
317
|
+
else
|
318
|
+
log_unhandled_line(state, line, reason: "unexpected non blank line in :none state")
|
319
|
+
end
|
320
|
+
when :reading
|
321
|
+
if stripped_line.match(/^(.+): Reading...$/)
|
322
|
+
log "Reading: #{$LAST_MATCH_INFO[1]} ...", depth: 2
|
323
|
+
elsif stripped_line.match(/^(.+): Read complete after ([^\[]+)(?: \[(.+)\])?$/)
|
324
|
+
if $LAST_MATCH_INFO[3]
|
325
|
+
log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]} [#{$LAST_MATCH_INFO[3]}]", depth: 3
|
326
|
+
else
|
327
|
+
log "Reading Complete: #{$LAST_MATCH_INFO[1]} after #{$LAST_MATCH_INFO[2]}", depth: 3
|
481
328
|
end
|
329
|
+
else
|
330
|
+
log_unhandled_line(state, line, reason: "unexpected line in :reading state")
|
482
331
|
end
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
log
|
488
|
-
|
332
|
+
when :import
|
333
|
+
# github_repository_topics.this[\"tf-k8s-infra-modules\"]: Preparing import... [id=tf-k8s-infra-modules]
|
334
|
+
matches = stripped_line.match(/^(?<resource>.+): Preparing import... \[id=(?<id>.+)\]$/)
|
335
|
+
if matches
|
336
|
+
log "Importing #{pastel.cyan(matches[:resource])} (id=#{pastel.yellow(matches[:id])}) ...", depth: 2
|
337
|
+
else
|
338
|
+
p [:import, "couldn't parse line:", stripped_line]
|
489
339
|
end
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
494
|
-
def setup_error_handling(parser, from_states:)
|
495
|
-
parser.state(:error_block, /^╷/, from_states | [:after_error])
|
496
|
-
parser.state(:error_block_error, /^│ Error: /, [:error_block])
|
497
|
-
parser.state(:error_block_warning, /^│ Warning: /, [:error_block])
|
498
|
-
parser.state(:after_error, /^╵/, [:error_block, :error_block_error, :error_block_warning])
|
499
|
-
end
|
500
|
-
|
501
|
-
def handle_error_states(meta, state, line) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
502
|
-
case state
|
503
|
-
when :error_block
|
504
|
-
meta[:current_error] = {
|
505
|
-
type: :unknown,
|
506
|
-
body: []
|
507
|
-
}
|
508
|
-
when :error_block_error, :error_block_warning
|
509
|
-
clean_line = pastel.strip(line).gsub(/^│ /, "")
|
510
|
-
if clean_line =~ /^(Warning|Error): (.+)$/
|
511
|
-
meta[:current_error][:type] = $LAST_MATCH_INFO[1].downcase.to_sym
|
512
|
-
meta[:current_error][:message] = $LAST_MATCH_INFO[2]
|
513
|
-
elsif clean_line == ""
|
514
|
-
# skip double empty lines
|
515
|
-
meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
|
340
|
+
when :info
|
341
|
+
if /Acquiring state lock. This may take a few moments.../.match?(line)
|
342
|
+
log "Acquiring state lock ...", depth: 2
|
516
343
|
else
|
517
|
-
|
518
|
-
meta[:current_error][:body] << clean_line
|
344
|
+
log_unhandled_line(state, line, reason: "unexpected line in :info state")
|
519
345
|
end
|
520
|
-
when :
|
346
|
+
when :plan_error
|
521
347
|
case pastel.strip(line)
|
522
|
-
when "
|
523
|
-
|
524
|
-
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
end
|
531
|
-
meta.delete(:current_error)
|
348
|
+
when ""
|
349
|
+
# skip empty line
|
350
|
+
when /Releasing state lock. This may take a few moments"/
|
351
|
+
log line, depth: 2
|
352
|
+
when /Planning failed./ # rubocop:disable Lint/DuplicateBranch
|
353
|
+
log line, depth: 2
|
354
|
+
else
|
355
|
+
log_unhandled_line(state, line, reason: "unexpected line in :plan_error state")
|
532
356
|
end
|
357
|
+
when :error_lock_info
|
358
|
+
meta["error"] = "lock"
|
359
|
+
meta[$LAST_MATCH_INFO[1]] = $LAST_MATCH_INFO[2] if line =~ /([A-Z]+\S+)+:\s+(.+)$/
|
360
|
+
if stripped_line == ""
|
361
|
+
meta[:current_error][:body] << stripped_line if meta[:current_error][:body].last != ""
|
362
|
+
else
|
363
|
+
meta[:current_error][:body] << stripped_line
|
364
|
+
end
|
365
|
+
when :refreshing
|
366
|
+
if first_in_state
|
367
|
+
log "Refreshing state ", depth: 2, newline: false
|
368
|
+
else
|
369
|
+
print "."
|
370
|
+
end
|
371
|
+
when :plan_legend
|
372
|
+
puts if first_in_state
|
373
|
+
log line, depth: 2
|
374
|
+
when :refresh_done
|
375
|
+
puts if first_in_state
|
376
|
+
when :plan_info # rubocop:disable Lint/DuplicateBranch
|
377
|
+
puts if first_in_state
|
378
|
+
log line, depth: 2
|
379
|
+
when :output_info # rubocop:disable Lint/DuplicateBranch
|
380
|
+
puts if first_in_state
|
381
|
+
log line, depth: 2
|
382
|
+
when :plan_summary
|
383
|
+
log line, depth: 2
|
533
384
|
else
|
534
385
|
return false
|
535
386
|
end
|
536
387
|
true
|
537
388
|
end
|
538
389
|
|
539
|
-
def
|
540
|
-
phase = :init
|
541
|
-
|
390
|
+
def pretty_plan_v1(filename, targets: [])
|
542
391
|
meta = {}
|
392
|
+
init_phase = :none
|
543
393
|
|
544
394
|
parser = StatefulParser.new(normalizer: pastel.method(:strip))
|
545
395
|
|
546
|
-
|
547
|
-
parser
|
548
|
-
parser.state(:backend, /^Initializing the backend\.\.\./, [:none, :modules_init, :modules_upgrade])
|
549
|
-
parser.state(:plugins, /^Initializing provider plugins\.\.\./, [:backend, :modules_init])
|
396
|
+
InitFormatter.setup_init_parser(parser)
|
397
|
+
setup_plan_v1_parser(parser)
|
550
398
|
|
551
|
-
parser
|
399
|
+
setup_error_handling(parser,
|
400
|
+
from_states: [:plan_error, :none, :blank, :info, :reading, :plan_summary, :refreshing] + [:plugins, :modules_init])
|
552
401
|
|
553
|
-
|
554
|
-
parser.state(:backend_error, /Error:/, [:backend])
|
555
|
-
|
556
|
-
setup_error_handling(parser, from_states: [:plugins, :modules_init])
|
402
|
+
last_state = nil
|
557
403
|
|
558
|
-
|
559
|
-
stripped_line = pastel.strip(raw_line.rstrip)
|
404
|
+
stderr_handler = StderrLineHandler.new(operation: :plan)
|
560
405
|
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
576
|
-
|
577
|
-
|
578
|
-
else
|
579
|
-
log_unhandled_line(state, line, reason: "unexpected line in :modules_init state")
|
580
|
-
end
|
581
|
-
when :modules_upgrade
|
582
|
-
if phase != state
|
583
|
-
# first line
|
584
|
-
phase = state
|
585
|
-
log "Upgrding modules ", depth: 1, newline: false
|
586
|
-
next
|
587
|
-
end
|
588
|
-
case stripped_line
|
589
|
-
when /^- (?<module>[^ ]+) in (?<path>.+)$/
|
590
|
-
print "."
|
591
|
-
when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
|
592
|
-
print "D"
|
593
|
-
when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
|
594
|
-
print "D"
|
595
|
-
when ""
|
596
|
-
puts
|
597
|
-
else
|
598
|
-
log_unhandled_line(state, line, reason: "unexpected line in :modules_upgrade state")
|
599
|
-
end
|
600
|
-
when :backend
|
601
|
-
if phase != state
|
602
|
-
# first line
|
603
|
-
phase = state
|
604
|
-
log "Initializing the backend ", depth: 1 # , newline: false
|
605
|
-
next
|
606
|
-
end
|
607
|
-
case stripped_line
|
608
|
-
when /^Successfully configured/
|
609
|
-
log line, depth: 2
|
610
|
-
when /unless the backend/ # rubocop:disable Lint/DuplicateBranch
|
611
|
-
log line, depth: 2
|
612
|
-
when ""
|
613
|
-
puts
|
614
|
-
else
|
615
|
-
log_unhandled_line(state, line, reason: "unexpected line in :backend state")
|
616
|
-
end
|
617
|
-
when :backend_error
|
618
|
-
if raw_line.match "terraform init -reconfigure"
|
619
|
-
meta[:need_reconfigure] = true
|
620
|
-
log pastel.red("module needs to be reconfigured"), depth: 2
|
621
|
-
end
|
622
|
-
if raw_line.match "Error when retrieving token from sso"
|
623
|
-
meta[:need_auth] = true
|
624
|
-
log pastel.red("authentication problem"), depth: 2
|
625
|
-
end
|
626
|
-
when :plugins
|
627
|
-
if phase != state
|
628
|
-
# first line
|
629
|
-
phase = state
|
630
|
-
log "Initializing provider plugins ...", depth: 1
|
631
|
-
next
|
632
|
-
end
|
633
|
-
case stripped_line
|
634
|
-
when /^- Reusing previous version of (?<module>.+) from the dependency lock file$/
|
635
|
-
info = $LAST_MATCH_INFO.named_captures
|
636
|
-
log "- [FROM-LOCK] #{info['module']}", depth: 2
|
637
|
-
when /^- (?<module>.+) is built in to Terraform$/
|
638
|
-
info = $LAST_MATCH_INFO.named_captures
|
639
|
-
log "- [BUILTIN] #{info['module']}", depth: 2
|
640
|
-
when /^- Finding (?<module>[^ ]+) versions matching "(?<version>.+)"\.\.\./
|
641
|
-
info = $LAST_MATCH_INFO.named_captures
|
642
|
-
log "- [FIND] #{info['module']} matching #{info['version'].inspect}", depth: 2
|
643
|
-
when /^- Finding latest version of (?<module>.+)\.\.\.$/
|
644
|
-
info = $LAST_MATCH_INFO.named_captures
|
645
|
-
log "- [FIND] #{info['module']}", depth: 2
|
646
|
-
when /^- Installing (?<module>[^ ]+) v(?<version>.+)\.\.\.$/
|
647
|
-
info = $LAST_MATCH_INFO.named_captures
|
648
|
-
log "- [INSTALLING] #{info['module']} v#{info['version']}", depth: 2
|
649
|
-
when /^- Installed (?<module>[^ ]+) v(?<version>.+) \(signed by(?: a)? (?<signed>.+)\)$/
|
650
|
-
info = $LAST_MATCH_INFO.named_captures
|
651
|
-
log "- [INSTALLED] #{info['module']} v#{info['version']} (#{info['signed']})", depth: 2
|
652
|
-
when /^- Using previously-installed (?<module>[^ ]+) v(?<version>.+)$/
|
653
|
-
info = $LAST_MATCH_INFO.named_captures
|
654
|
-
log "- [USING] #{info['module']} v#{info['version']}", depth: 2
|
655
|
-
when /^- Downloading plugin for provider "(?<provider>[^"]+)" \((?<provider_path>[^)]+)\) (?<version>.+)\.\.\.$/
|
656
|
-
info = $LAST_MATCH_INFO.named_captures
|
657
|
-
log "- #{info['provider']} #{info['version']}", depth: 2
|
658
|
-
when "- Checking for available provider plugins..."
|
659
|
-
# noop
|
406
|
+
status = tf_plan(out: filename, detailed_exitcode: true, compact_warnings: true, targets: targets, split_streams: true) { |(stream, raw_line)|
|
407
|
+
case stream
|
408
|
+
when :command
|
409
|
+
log "Running command: #{raw_line.strip} ...", depth: 2
|
410
|
+
when :stderr
|
411
|
+
stderr_handler.handle(raw_line)
|
412
|
+
when :stdout
|
413
|
+
stripped_line = pastel.strip(raw_line.rstrip)
|
414
|
+
parser.parse(raw_line.rstrip) do |state, line|
|
415
|
+
first_in_state = last_state != state
|
416
|
+
|
417
|
+
if (handled = handle_plan_v1_line(state, line, meta, first_in_state: first_in_state, stripped_line: stripped_line))
|
418
|
+
# great!
|
419
|
+
elsif (handled = InitFormatter.handle_init_line(state, line, meta, phase: init_phase, stripped_line: stripped_line))
|
420
|
+
init_phase = handled[:phase]
|
421
|
+
elsif handle_error_states(meta, state, line)
|
422
|
+
# no-op
|
660
423
|
else
|
661
|
-
log_unhandled_line(state, line, reason: "unexpected
|
662
|
-
end
|
663
|
-
when :plugin_warnings
|
664
|
-
if phase != state
|
665
|
-
# first line
|
666
|
-
phase = state
|
667
|
-
next
|
424
|
+
log_unhandled_line(state, line, reason: "unexpected state")
|
668
425
|
end
|
669
426
|
|
670
|
-
|
671
|
-
when :none
|
672
|
-
next if line == ""
|
673
|
-
|
674
|
-
log_unhandled_line(state, line, reason: "unexpected line in :none state")
|
675
|
-
else
|
676
|
-
log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
|
427
|
+
last_state = state
|
677
428
|
end
|
678
429
|
end
|
679
430
|
}
|
680
431
|
|
432
|
+
stderr_handler.flush
|
433
|
+
stderr_handler.merge_meta_into(meta)
|
434
|
+
|
435
|
+
meta[:errors]&.each do |error|
|
436
|
+
if error[:message] == "Error acquiring the state lock"
|
437
|
+
meta["error"] = "lock"
|
438
|
+
meta.merge!(parse_lock_info(error[:body].join("\n")))
|
439
|
+
end
|
440
|
+
end
|
441
|
+
|
681
442
|
[status.status, meta]
|
682
443
|
end
|
683
444
|
|
684
445
|
def print_validation_errors(info)
|
685
|
-
return unless
|
446
|
+
return unless info["error_count"].positive? || info["warning_count"].positive?
|
686
447
|
|
687
448
|
log "Encountered #{pastel.red(info['error_count'])} Errors and #{pastel.yellow(info['warning_count'])} Warnings!", depth: 2
|
688
449
|
info["diagnostics"].each do |dinfo|
|
@@ -693,11 +454,10 @@ module MuxTf
|
|
693
454
|
end
|
694
455
|
end
|
695
456
|
|
696
|
-
|
697
|
-
def process_validation(info) # rubocop:disable Metrics/CyclomaticComplexity
|
457
|
+
def process_validation(info)
|
698
458
|
remedies = Set.new
|
699
459
|
|
700
|
-
if
|
460
|
+
if info["error_count"].positive? || info["warning_count"].positive?
|
701
461
|
info["diagnostics"].each do |dinfo| # rubocop:disable Metrics/BlockLength
|
702
462
|
item_handled = false
|
703
463
|
|
@@ -707,7 +467,8 @@ module MuxTf
|
|
707
467
|
/Module not installed/,
|
708
468
|
/Module source has changed/,
|
709
469
|
/Required plugins are not installed/,
|
710
|
-
/Module version requirements have changed
|
470
|
+
/Module version requirements have changed/,
|
471
|
+
/to install all modules required by this configuration/
|
711
472
|
remedies << :init
|
712
473
|
item_handled = true
|
713
474
|
when /Missing required argument/,
|
@@ -722,7 +483,7 @@ module MuxTf
|
|
722
483
|
item_handled = true
|
723
484
|
end
|
724
485
|
|
725
|
-
if dinfo["severity"] == "error" && dinfo["snippet"]
|
486
|
+
if !item_handled && dinfo["severity"] == "error" && dinfo["snippet"]
|
726
487
|
# trying something new .. assuming anything with a snippet is a user error
|
727
488
|
remedies << :user_error
|
728
489
|
item_handled = true
|
@@ -749,84 +510,6 @@ module MuxTf
|
|
749
510
|
|
750
511
|
remedies
|
751
512
|
end
|
752
|
-
# rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
753
|
-
|
754
|
-
private
|
755
|
-
|
756
|
-
def format_validation_range(dinfo, color) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
757
|
-
range = dinfo["range"]
|
758
|
-
# filename: "../../../modules/pods/jane_pod/main.tf"
|
759
|
-
# start:
|
760
|
-
# line: 151
|
761
|
-
# column: 27
|
762
|
-
# byte: 6632
|
763
|
-
# end:
|
764
|
-
# line: 151
|
765
|
-
# column: 53
|
766
|
-
# byte: 6658
|
767
|
-
|
768
|
-
context_lines = 3
|
769
|
-
|
770
|
-
lines = range["start"]["line"]..range["end"]["line"]
|
771
|
-
columns = range["start"]["column"]..range["end"]["column"]
|
772
|
-
|
773
|
-
# on ../../../modules/pods/jane_pod/main.tf line 151, in module "jane":
|
774
|
-
# 151: jane_resources_preset = var.jane_resources_presetx
|
775
|
-
output = []
|
776
|
-
lines_info = if lines.size == 1
|
777
|
-
"#{lines.first}:#{columns.first}"
|
778
|
-
else
|
779
|
-
"#{lines.first}:#{columns.first} to #{lines.last}:#{columns.last}"
|
780
|
-
end
|
781
|
-
output << "on: #{range['filename']} line#{lines.size > 1 ? 's' : ''}: #{lines_info}"
|
782
|
-
|
783
|
-
# 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
|
784
|
-
if File.exist?(range["filename"])
|
785
|
-
file_lines = File.read(range["filename"]).split("\n")
|
786
|
-
extract_range = (([lines.first - context_lines,
|
787
|
-
0].max)..([lines.last + context_lines, file_lines.length - 1].min))
|
788
|
-
file_lines.each_with_index do |line, index|
|
789
|
-
if extract_range.cover?(index + 1)
|
790
|
-
if lines.cover?(index + 1)
|
791
|
-
start_col = 1
|
792
|
-
end_col = :max
|
793
|
-
if index + 1 == lines.first
|
794
|
-
start_col = columns.first
|
795
|
-
elsif index + 1 == lines.last
|
796
|
-
start_col = columns.last
|
797
|
-
end
|
798
|
-
painted_line = paint_line(line, color, start_col: start_col, end_col: end_col)
|
799
|
-
output << "#{pastel.decorate('>', color)} #{index + 1}: #{painted_line}"
|
800
|
-
else
|
801
|
-
output << " #{index + 1}: #{line}"
|
802
|
-
end
|
803
|
-
end
|
804
|
-
end
|
805
|
-
elsif dinfo["snippet"]
|
806
|
-
# {
|
807
|
-
# "context"=>"locals",
|
808
|
-
# "code"=>" aws_iam_policy.crossplane_aws_ecr.arn",
|
809
|
-
# "start_line"=>72,
|
810
|
-
# "highlight_start_offset"=>8,
|
811
|
-
# "highlight_end_offset"=>41,
|
812
|
-
# "values"=>[]
|
813
|
-
# }
|
814
|
-
output << "Code:"
|
815
|
-
dinfo["snippet"]["code"].split("\n").each do |l|
|
816
|
-
output << " > #{l}"
|
817
|
-
end
|
818
|
-
end
|
819
|
-
|
820
|
-
output
|
821
|
-
end
|
822
|
-
|
823
|
-
def paint_line(line, *paint_options, start_col: 1, end_col: :max)
|
824
|
-
end_col = line.length if end_col == :max
|
825
|
-
prefix = line[0, start_col - 1]
|
826
|
-
suffix = line[end_col..]
|
827
|
-
middle = line[start_col - 1..end_col - 1]
|
828
|
-
"#{prefix}#{pastel.decorate(middle, *paint_options)}#{suffix}"
|
829
|
-
end
|
830
513
|
end
|
831
514
|
end
|
832
515
|
end
|