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.
@@ -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