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
@@ -0,0 +1,306 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MuxTf
|
4
|
+
class InitFormatter
|
5
|
+
extend TerraformHelpers
|
6
|
+
extend PiotrbCliUtils::Util
|
7
|
+
include Coloring
|
8
|
+
|
9
|
+
extend ErrorHandlingMethods
|
10
|
+
extend FormatterCommon
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def parse_tf_ui_line(parsed_line, meta, parser, phase:)
|
14
|
+
# p(parsed_line)
|
15
|
+
case parsed_line[:type]
|
16
|
+
when "version"
|
17
|
+
meta[:terraform_version] = parsed_line[:terraform] if parsed_line[:terraform]
|
18
|
+
meta[:ui_version] = parsed_line[:ui] if parsed_line[:ui]
|
19
|
+
meta[:tofu_version] = parsed_line[:tofu] if parsed_line[:tofu]
|
20
|
+
|
21
|
+
when "output", "unknown"
|
22
|
+
raw_line = parsed_line[:message]
|
23
|
+
stripped_line = pastel.strip(raw_line.rstrip)
|
24
|
+
parser.parse(raw_line.rstrip) do |state, line|
|
25
|
+
if (handled = handle_init_line(state, line, meta, phase: phase, stripped_line: stripped_line))
|
26
|
+
phase = handled[:phase]
|
27
|
+
elsif handle_error_states(meta, state, line)
|
28
|
+
# no-op
|
29
|
+
else
|
30
|
+
log_unhandled_line(state, line, reason: "unexpected state")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
else
|
35
|
+
print_init_line(parsed_line, from: "parse_tf_ui_line,else")
|
36
|
+
end
|
37
|
+
phase
|
38
|
+
end
|
39
|
+
|
40
|
+
def init_status_to_remedies(status, meta)
|
41
|
+
remedies = Set.new
|
42
|
+
if status != 0
|
43
|
+
remedies << :reconfigure if meta[:need_reconfigure]
|
44
|
+
remedies << :auth if meta[:need_auth]
|
45
|
+
log "!! expected meta[:errors] to be set, how did we get here?" unless meta[:errors]
|
46
|
+
meta[:errors]&.each do |error|
|
47
|
+
remedies << :add_provider_constraint if error[:body].grep(/Could not retrieve the list of available versions for provider/)
|
48
|
+
remedies << :user_error if error[:body].grep(/Unreadable module directory/)
|
49
|
+
end
|
50
|
+
if remedies.empty?
|
51
|
+
log "!! don't know how to generate init remedies for this"
|
52
|
+
log "!! Status: #{status}"
|
53
|
+
log "!! Meta:"
|
54
|
+
log meta.to_yaml.split("\n").map { |l| "!! #{l}" }.join("\n")
|
55
|
+
remedies << :unknown
|
56
|
+
end
|
57
|
+
end
|
58
|
+
remedies
|
59
|
+
end
|
60
|
+
|
61
|
+
def setup_init_parser(parser)
|
62
|
+
parser.state(:modules_init, /^Initializing modules\.\.\./, [:none, :backend])
|
63
|
+
parser.state(:modules_upgrade, /^Upgrading modules\.\.\./)
|
64
|
+
parser.state(:backend, /^Initializing the backend\.\.\./, [:none, :modules_init, :modules_upgrade])
|
65
|
+
parser.state(:plugins, /^Initializing provider plugins\.\.\./, [:backend, :modules_init])
|
66
|
+
|
67
|
+
parser.state(:backend_error, /Error when retrieving token from sso/, [:backend])
|
68
|
+
|
69
|
+
parser.state(:plugin_warnings, /^$/, [:plugins])
|
70
|
+
parser.state(:backend_error, /Error:/, [:backend])
|
71
|
+
end
|
72
|
+
|
73
|
+
def handle_init_line(state, line, meta, phase:, stripped_line:)
|
74
|
+
case state
|
75
|
+
when :modules_init
|
76
|
+
if phase == state
|
77
|
+
case stripped_line
|
78
|
+
when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
|
79
|
+
print "D"
|
80
|
+
when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
|
81
|
+
print "D"
|
82
|
+
when /^- (?<module>[^ ]+) in(?: (?<path>.+))?$/
|
83
|
+
print "."
|
84
|
+
when ""
|
85
|
+
puts
|
86
|
+
else
|
87
|
+
log_unhandled_line(state, line, reason: "unexpected line in :modules_init state")
|
88
|
+
end
|
89
|
+
else
|
90
|
+
phase = state
|
91
|
+
log "Initializing modules ", depth: 1
|
92
|
+
end
|
93
|
+
when :modules_upgrade
|
94
|
+
if phase == state
|
95
|
+
case stripped_line
|
96
|
+
when /^- (?<module>[^ ]+) in (?<path>.+)$/
|
97
|
+
print "."
|
98
|
+
when /^Downloading (?<repo>[^ ]+) (?<version>[^ ]+) for (?<module>[^ ]+)\.\.\./
|
99
|
+
print "D"
|
100
|
+
when /^Downloading (?<repo>[^ ]+) for (?<module>[^ ]+)\.\.\./ # rubocop:disable Lint/DuplicateBranch
|
101
|
+
print "D"
|
102
|
+
when ""
|
103
|
+
puts
|
104
|
+
else
|
105
|
+
log_unhandled_line(state, line, reason: "unexpected line in :modules_upgrade state")
|
106
|
+
end
|
107
|
+
else
|
108
|
+
# first line
|
109
|
+
phase = state
|
110
|
+
log "Upgrding modules ", depth: 1, newline: false
|
111
|
+
end
|
112
|
+
when :backend
|
113
|
+
if phase == state
|
114
|
+
case stripped_line
|
115
|
+
when /^Successfully configured/
|
116
|
+
log line, depth: 2
|
117
|
+
when /unless the backend/ # rubocop:disable Lint/DuplicateBranch
|
118
|
+
log line, depth: 2
|
119
|
+
when ""
|
120
|
+
puts
|
121
|
+
else
|
122
|
+
log_unhandled_line(state, line, reason: "unexpected line in :backend state")
|
123
|
+
end
|
124
|
+
else
|
125
|
+
# first line
|
126
|
+
phase = state
|
127
|
+
log "Initializing the backend ", depth: 1 # , newline: false
|
128
|
+
end
|
129
|
+
when :backend_error
|
130
|
+
if raw_line.match "terraform init -reconfigure"
|
131
|
+
meta[:need_reconfigure] = true
|
132
|
+
log pastel.red("module needs to be reconfigured"), depth: 2
|
133
|
+
end
|
134
|
+
if raw_line.match "Error when retrieving token from sso"
|
135
|
+
meta[:need_auth] = true
|
136
|
+
log pastel.red("authentication problem"), depth: 2
|
137
|
+
end
|
138
|
+
when :plugins
|
139
|
+
if phase == state
|
140
|
+
case stripped_line
|
141
|
+
when /^- Reusing previous version of (?<module>.+) from the dependency lock file$/
|
142
|
+
info = $LAST_MATCH_INFO.named_captures
|
143
|
+
log "- [FROM-LOCK] #{info['module']}", depth: 2
|
144
|
+
when /^- (?<module>.+) is built in to Terraform$/
|
145
|
+
info = $LAST_MATCH_INFO.named_captures
|
146
|
+
log "- [BUILTIN] #{info['module']}", depth: 2
|
147
|
+
when /^- Finding (?<module>[^ ]+) versions matching "(?<version>.+)"\.\.\./
|
148
|
+
info = $LAST_MATCH_INFO.named_captures
|
149
|
+
log "- [FIND] #{info['module']} matching #{info['version'].inspect}", depth: 2
|
150
|
+
when /^- Finding latest version of (?<module>.+)\.\.\.$/
|
151
|
+
info = $LAST_MATCH_INFO.named_captures
|
152
|
+
log "- [FIND] #{info['module']}", depth: 2
|
153
|
+
when /^- Installing (?<module>[^ ]+) v(?<version>.+)\.\.\.$/
|
154
|
+
info = $LAST_MATCH_INFO.named_captures
|
155
|
+
log "- [INSTALLING] #{info['module']} v#{info['version']}", depth: 2
|
156
|
+
when /^- Installed (?<module>[^ ]+) v(?<version>[^ ]+) \((?:signed, key ID)?(?:, | by)?(?: a)? (?<signed>.+)\)$/
|
157
|
+
info = $LAST_MATCH_INFO.named_captures
|
158
|
+
log "- [INSTALLED] #{info['module']} v#{info['version']} (#{info['signed']})", depth: 2
|
159
|
+
when /^- Using previously-installed (?<module>[^ ]+) v(?<version>.+)$/
|
160
|
+
info = $LAST_MATCH_INFO.named_captures
|
161
|
+
log "- [USING] #{info['module']} v#{info['version']}", depth: 2
|
162
|
+
when /^- Downloading plugin for provider "(?<provider>[^"]+)" \((?<provider_path>[^)]+)\) (?<version>.+)\.\.\.$/
|
163
|
+
info = $LAST_MATCH_INFO.named_captures
|
164
|
+
log "- #{info['provider']} #{info['version']}", depth: 2
|
165
|
+
when /^- Using (?<provider>[^ ]+) v(?<version>.+) from the shared cache directory$/
|
166
|
+
info = $LAST_MATCH_INFO.named_captures
|
167
|
+
log "- [CACHE HIT] #{info['provider']} #{info['version']}", depth: 2
|
168
|
+
when "- Checking for available provider plugins..."
|
169
|
+
# noop
|
170
|
+
when %r{^- terraform\.io/builtin/terraform is built in to OpenTofu}
|
171
|
+
# noop
|
172
|
+
when /Providers are signed by their developers./
|
173
|
+
# noop
|
174
|
+
when /OpenTofu has created a lock file/
|
175
|
+
# noop
|
176
|
+
else
|
177
|
+
log_unhandled_line(state, line, reason: "unexpected line in :plugins state")
|
178
|
+
end
|
179
|
+
else
|
180
|
+
# first line
|
181
|
+
phase = state
|
182
|
+
log "Initializing provider plugins ...", depth: 1
|
183
|
+
end
|
184
|
+
when :plugin_warnings
|
185
|
+
if phase == state
|
186
|
+
log pastel.yellow(line), depth: 1
|
187
|
+
else
|
188
|
+
# first line
|
189
|
+
phase = state
|
190
|
+
end
|
191
|
+
when :none
|
192
|
+
log_unhandled_line(state, line, reason: "unexpected line in :none state") if line != ""
|
193
|
+
else
|
194
|
+
return false
|
195
|
+
# log_unhandled_line(state, line, reason: "unexpected state") unless handle_error_states(meta, state, line)
|
196
|
+
end
|
197
|
+
|
198
|
+
{ phase: phase }
|
199
|
+
end
|
200
|
+
|
201
|
+
def run_tf_init(upgrade: nil, reconfigure: nil)
|
202
|
+
phase = :init
|
203
|
+
meta = {}
|
204
|
+
meta[:seen] = {
|
205
|
+
module_and_type: Set.new
|
206
|
+
}
|
207
|
+
|
208
|
+
parser = StatefulParser.new(normalizer: pastel.method(:strip))
|
209
|
+
|
210
|
+
setup_init_parser(parser)
|
211
|
+
|
212
|
+
setup_error_handling(parser, from_states: [:plugins, :modules_init])
|
213
|
+
|
214
|
+
status = tf_init_json(upgrade: upgrade, reconfigure: reconfigure) { |parsed_line|
|
215
|
+
# seen = proc { |module_arg, type_arg| meta[:seen][:module_and_type].include?([module_arg, type_arg]) }
|
216
|
+
|
217
|
+
case parsed_line[:level]
|
218
|
+
when "info"
|
219
|
+
# p parsed_line
|
220
|
+
case parsed_line[:module]
|
221
|
+
when "terraform.ui", "tofu.ui"
|
222
|
+
phase = parse_tf_ui_line(parsed_line, meta, parser, phase: phase)
|
223
|
+
when "non-json-log"
|
224
|
+
print_init_line(parsed_line)
|
225
|
+
else
|
226
|
+
print_init_line(parsed_line, from: "run_tf_init_v2,info,else")
|
227
|
+
end
|
228
|
+
when "error"
|
229
|
+
if parsed_line[:diagnostic]
|
230
|
+
handled_error = false
|
231
|
+
muted_error = false
|
232
|
+
current_meta_error = {}
|
233
|
+
unless parsed_line[:module] == "terragrunt" && parsed_line[:type] == "tf_failed"
|
234
|
+
meta[:errors] ||= []
|
235
|
+
current_meta_error = {
|
236
|
+
type: :error,
|
237
|
+
message: parsed_line[:diagnostic]["summary"],
|
238
|
+
body: parsed_line[:diagnostic]["detail"].split("\n")
|
239
|
+
}
|
240
|
+
meta[:errors] << current_meta_error
|
241
|
+
end
|
242
|
+
|
243
|
+
if parsed_line[:diagnostic]["summary"] == "Error acquiring the state lock"
|
244
|
+
meta["error"] = "lock"
|
245
|
+
meta.merge!(parse_lock_info(parsed_line[:diagnostic]["detail"]))
|
246
|
+
handled_error = true
|
247
|
+
elsif parsed_line[:module] == "terragrunt" && parsed_line[:type] == "tf_failed"
|
248
|
+
muted_error = true
|
249
|
+
end
|
250
|
+
|
251
|
+
unless muted_error
|
252
|
+
if handled_error
|
253
|
+
print_init_line(parsed_line, without: [:diagnostic], from: "run_tf_init_v2,error,handled")
|
254
|
+
else
|
255
|
+
# print_init_line(parsed_line, from: "run_tf_init_v2,error,unhandled_error")
|
256
|
+
print_unhandled_error_line(parsed_line)
|
257
|
+
current_meta_error[:printed] = true
|
258
|
+
end
|
259
|
+
end
|
260
|
+
elsif parsed_line[:message] =~ /^\[reset\]/
|
261
|
+
print_unhandled_error_line(parsed_line)
|
262
|
+
elsif parsed_line[:module] == :stderr && parsed_line[:type] == "unknown" && parsed_line[:message][0] == "{"
|
263
|
+
print_tg_error_line(parsed_line)
|
264
|
+
else
|
265
|
+
print_init_line(parsed_line, from: "run_tf_init_v2,error,else")
|
266
|
+
end
|
267
|
+
end
|
268
|
+
}
|
269
|
+
[status.status, meta]
|
270
|
+
end
|
271
|
+
|
272
|
+
def tf_init_json(upgrade: nil, reconfigure: nil, &block)
|
273
|
+
tf_cmd_json(proc { |handler|
|
274
|
+
tf_init(upgrade: upgrade, reconfigure: reconfigure, json: true, &handler)
|
275
|
+
}, &block)
|
276
|
+
end
|
277
|
+
|
278
|
+
def print_init_line(parsed_line, without: [], from: nil)
|
279
|
+
default_without = [
|
280
|
+
:level,
|
281
|
+
:module,
|
282
|
+
:type,
|
283
|
+
:stream,
|
284
|
+
:message,
|
285
|
+
:timestamp,
|
286
|
+
:terraform,
|
287
|
+
:ui
|
288
|
+
]
|
289
|
+
extra = parsed_line.without(*default_without, *without)
|
290
|
+
data = parsed_line.merge(extra: extra).merge(from: from)
|
291
|
+
log_line = [
|
292
|
+
"%<from>s",
|
293
|
+
"%<level>-6s",
|
294
|
+
"%<module>-12s",
|
295
|
+
"%<type>-10s",
|
296
|
+
"%<message>s",
|
297
|
+
"%<extra>s"
|
298
|
+
].map { |format_string|
|
299
|
+
field = format_string.match(/%<([^>]+)>/)[1].to_sym
|
300
|
+
data[field].present? ? format(format_string, data) : nil
|
301
|
+
}.compact.join(" | ")
|
302
|
+
log log_line
|
303
|
+
end
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|