mux_tf 0.13.0 → 0.14.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/exe/tf_current +2 -0
- data/exe/tf_mux +2 -0
- data/exe/tf_plan_summary +2 -0
- data/lib/deps.rb +10 -1
- data/lib/mux_tf/cli/current.rb +192 -76
- data/lib/mux_tf/cli/plan_summary.rb +2 -1
- data/lib/mux_tf/cli.rb +3 -3
- data/lib/mux_tf/coloring.rb +23 -0
- data/lib/mux_tf/plan_formatter.rb +503 -80
- data/lib/mux_tf/plan_summary_handler.rb +99 -94
- data/lib/mux_tf/plan_utils.rb +360 -0
- data/lib/mux_tf/terraform_helpers.rb +51 -9
- data/lib/mux_tf/version.rb +1 -1
- data/lib/mux_tf.rb +18 -10
- data/mux_tf.gemspec +3 -1
- metadata +33 -3
@@ -5,26 +5,93 @@ module MuxTf
|
|
5
5
|
extend TerraformHelpers
|
6
6
|
include TerraformHelpers
|
7
7
|
include PiotrbCliUtils::Util
|
8
|
+
include Coloring
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
class << self
|
11
|
+
def from_file(file)
|
12
|
+
data = data_from_file(file)
|
13
|
+
new data
|
14
|
+
end
|
13
15
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
def data_from_file(file)
|
17
|
+
if File.exist?("#{file}.json") && File.mtime("#{file}.json").to_f >= File.mtime(file).to_f
|
18
|
+
JSON.parse(File.read("#{file}.json"))
|
19
|
+
else
|
20
|
+
puts "Analyzing changes ..."
|
21
|
+
result = tf_show(file, json: true)
|
22
|
+
data = result.parsed_output
|
23
|
+
File.write("#{file}.json", JSON.dump(data))
|
24
|
+
data
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def from_data(data)
|
29
|
+
new(data)
|
30
|
+
end
|
31
|
+
|
32
|
+
def color_for_action(action)
|
33
|
+
case action
|
34
|
+
when "create", "add"
|
35
|
+
:green
|
36
|
+
when "update", "change"
|
37
|
+
:yellow
|
38
|
+
when "delete", "remove"
|
39
|
+
:red
|
40
|
+
when "replace" # rubocop:disable Lint/DuplicateBranch
|
41
|
+
:red
|
42
|
+
when "replace (create before delete)" # rubocop:disable Lint/DuplicateBranch
|
43
|
+
:red
|
44
|
+
when "read"
|
45
|
+
:cyan
|
46
|
+
when "import" # rubocop:disable Lint/DuplicateBranch
|
47
|
+
:cyan
|
48
|
+
else
|
49
|
+
:reset
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def symbol_for_action(action)
|
54
|
+
case action
|
55
|
+
when "create"
|
56
|
+
"+"
|
57
|
+
when "update"
|
58
|
+
"~"
|
59
|
+
when "delete"
|
60
|
+
"-"
|
61
|
+
when "replace"
|
62
|
+
"∓"
|
63
|
+
when "replace (create before delete)"
|
64
|
+
"±"
|
65
|
+
when "read"
|
66
|
+
">"
|
67
|
+
else
|
68
|
+
action
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def format_action(action)
|
73
|
+
color = color_for_action(action)
|
74
|
+
symbol = symbol_for_action(action)
|
75
|
+
pastel.decorate(symbol, color)
|
23
76
|
end
|
24
|
-
end
|
25
77
|
|
26
|
-
|
27
|
-
|
78
|
+
def self.format_address(address)
|
79
|
+
result = []
|
80
|
+
parts = ResourceTokenizer.tokenize(address)
|
81
|
+
parts.each_with_index do |(part_type, part_value), index|
|
82
|
+
case part_type
|
83
|
+
when :rt
|
84
|
+
result << "." if index.positive?
|
85
|
+
result << pastel.cyan(part_value)
|
86
|
+
when :rn
|
87
|
+
result << "."
|
88
|
+
result << part_value
|
89
|
+
when :ri
|
90
|
+
result << pastel.green(part_value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
result.join
|
94
|
+
end
|
28
95
|
end
|
29
96
|
|
30
97
|
def initialize(data) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
@@ -139,8 +206,8 @@ module MuxTf
|
|
139
206
|
resource_summary[part[:action]] += 1
|
140
207
|
end
|
141
208
|
resource_pieces = resource_summary.map { |k, v|
|
142
|
-
color = color_for_action(k)
|
143
|
-
"#{
|
209
|
+
color = self.class.color_for_action(k)
|
210
|
+
"#{pastel.yellow(v)} to #{pastel.decorate(k, color)}"
|
144
211
|
}
|
145
212
|
|
146
213
|
# outputs
|
@@ -150,15 +217,15 @@ module MuxTf
|
|
150
217
|
output_summary[part[:action]] += 1
|
151
218
|
end
|
152
219
|
output_pieces = output_summary.map { |k, v|
|
153
|
-
color = color_for_action(k)
|
154
|
-
"#{
|
220
|
+
color = self.class.color_for_action(k)
|
221
|
+
"#{pastel.yellow(v)} to #{pastel.decorate(k, color)}"
|
155
222
|
}
|
156
223
|
|
157
224
|
if resource_pieces.any? || output_pieces.any?
|
158
225
|
[
|
159
226
|
"Plan Summary:",
|
160
|
-
resource_pieces.any? ? resource_pieces.join(
|
161
|
-
output_pieces.any? ? "Outputs: #{output_pieces.join(
|
227
|
+
resource_pieces.any? ? resource_pieces.join(pastel.gray(", ")) : nil,
|
228
|
+
output_pieces.any? ? "Outputs: #{output_pieces.join(pastel.gray(', '))}" : nil
|
162
229
|
].compact.join(" ")
|
163
230
|
else
|
164
231
|
"Plan Summary: no changes"
|
@@ -168,7 +235,7 @@ module MuxTf
|
|
168
235
|
def flat_summary
|
169
236
|
result = []
|
170
237
|
resource_parts.each do |part|
|
171
|
-
result << "[#{format_action(part[:action])}] #{format_address(part[:address])}"
|
238
|
+
result << "[#{self.class.format_action(part[:action])}] #{self.class.format_address(part[:address])}"
|
172
239
|
end
|
173
240
|
result
|
174
241
|
end
|
@@ -176,11 +243,11 @@ module MuxTf
|
|
176
243
|
def sensitive_summary(before_value, after_value)
|
177
244
|
# before vs after
|
178
245
|
if before_value && after_value
|
179
|
-
"(#{
|
246
|
+
"(#{pastel.yellow('sensitive')})"
|
180
247
|
elsif before_value
|
181
|
-
"(#{
|
248
|
+
"(#{pastel.red('-sensitive')})"
|
182
249
|
elsif after_value
|
183
|
-
"(#{
|
250
|
+
"(#{pastel.cyan('+sensitive')})"
|
184
251
|
end
|
185
252
|
end
|
186
253
|
|
@@ -188,8 +255,8 @@ module MuxTf
|
|
188
255
|
result = []
|
189
256
|
output_parts.each do |part|
|
190
257
|
pieces = [
|
191
|
-
"[#{format_action(part[:action])}]",
|
192
|
-
format_address("output.#{part[:address]}"),
|
258
|
+
"[#{self.class.format_action(part[:action])}]",
|
259
|
+
self.class.format_address("output.#{part[:address]}"),
|
193
260
|
part[:after_unknown] ? "(unknown)" : nil,
|
194
261
|
sensitive_summary(*part[:sensitive])
|
195
262
|
].compact
|
@@ -198,7 +265,7 @@ module MuxTf
|
|
198
265
|
result
|
199
266
|
end
|
200
267
|
|
201
|
-
def nested_summary # rubocop:disable Metrics/
|
268
|
+
def nested_summary # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
202
269
|
result = []
|
203
270
|
parts = resource_parts.deep_dup
|
204
271
|
until parts.empty?
|
@@ -209,7 +276,7 @@ module MuxTf
|
|
209
276
|
else
|
210
277
|
""
|
211
278
|
end
|
212
|
-
message = "[#{format_action(part[:action])}]#{indent} #{format_address(part[:address])}"
|
279
|
+
message = "[#{self.class.format_action(part[:action])}]#{indent} #{self.class.format_address(part[:address])}"
|
213
280
|
message += " - (needs: #{part[:met_deps].join(', ')})" if part[:met_deps]
|
214
281
|
result << message
|
215
282
|
parts.each do |ipart|
|
@@ -230,7 +297,7 @@ module MuxTf
|
|
230
297
|
prompt = TTY::Prompt.new
|
231
298
|
result = prompt.multi_select("Update resources:", per_page: 99, echo: false) { |menu|
|
232
299
|
resource_parts.each do |part|
|
233
|
-
label = "[#{format_action(part[:action])}] #{format_address(part[:address])}"
|
300
|
+
label = "[#{self.class.format_action(part[:action])}] #{self.class.format_address(part[:address])}"
|
234
301
|
menu.choice label, part[:address]
|
235
302
|
end
|
236
303
|
}
|
@@ -258,7 +325,7 @@ module MuxTf
|
|
258
325
|
when 2
|
259
326
|
[:changes, meta]
|
260
327
|
else
|
261
|
-
log
|
328
|
+
log pastel.yellow("terraform plan exited with an unknown exit code: #{exit_code}")
|
262
329
|
[:unknown, meta]
|
263
330
|
end
|
264
331
|
end
|
@@ -320,67 +387,5 @@ module MuxTf
|
|
320
387
|
[resource, parent_address]
|
321
388
|
end
|
322
389
|
end
|
323
|
-
|
324
|
-
def color_for_action(action)
|
325
|
-
case action
|
326
|
-
when "create"
|
327
|
-
:green
|
328
|
-
when "update"
|
329
|
-
:yellow
|
330
|
-
when "delete"
|
331
|
-
:red
|
332
|
-
when "replace" # rubocop:disable Lint/DuplicateBranch
|
333
|
-
:red
|
334
|
-
when "replace (create before delete)" # rubocop:disable Lint/DuplicateBranch
|
335
|
-
:red
|
336
|
-
when "read"
|
337
|
-
:cyan
|
338
|
-
else
|
339
|
-
:reset
|
340
|
-
end
|
341
|
-
end
|
342
|
-
|
343
|
-
def symbol_for_action(action)
|
344
|
-
case action
|
345
|
-
when "create"
|
346
|
-
"+"
|
347
|
-
when "update"
|
348
|
-
"~"
|
349
|
-
when "delete"
|
350
|
-
"-"
|
351
|
-
when "replace"
|
352
|
-
"∓"
|
353
|
-
when "replace (create before delete)"
|
354
|
-
"±"
|
355
|
-
when "read"
|
356
|
-
">"
|
357
|
-
else
|
358
|
-
action
|
359
|
-
end
|
360
|
-
end
|
361
|
-
|
362
|
-
def format_action(action)
|
363
|
-
color = color_for_action(action)
|
364
|
-
symbol = symbol_for_action(action)
|
365
|
-
Paint[symbol, color]
|
366
|
-
end
|
367
|
-
|
368
|
-
def format_address(address)
|
369
|
-
result = []
|
370
|
-
parts = ResourceTokenizer.tokenize(address)
|
371
|
-
parts.each_with_index do |(part_type, part_value), index|
|
372
|
-
case part_type
|
373
|
-
when :rt
|
374
|
-
result << "." if index.positive?
|
375
|
-
result << Paint[part_value, :cyan]
|
376
|
-
when :rn
|
377
|
-
result << "."
|
378
|
-
result << part_value
|
379
|
-
when :ri
|
380
|
-
result << Paint[part_value, :green]
|
381
|
-
end
|
382
|
-
end
|
383
|
-
result.join
|
384
|
-
end
|
385
390
|
end
|
386
391
|
end
|
@@ -0,0 +1,360 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MuxTf
|
4
|
+
class PlanUtils
|
5
|
+
extend TerraformHelpers
|
6
|
+
extend PiotrbCliUtils::Util
|
7
|
+
include Coloring
|
8
|
+
|
9
|
+
KNOWN_AFTER_APPLY = "(known after apply)"
|
10
|
+
SENSITIVE = "(sensitive value)"
|
11
|
+
|
12
|
+
class << self
|
13
|
+
def warning(message, binding_arg: binding)
|
14
|
+
stack = binding_arg.send(:caller)
|
15
|
+
stack_line = stack[0].match(/^(?<path>.+):(?<ln>\d+):in `(?<method>.+)'$/).named_captures
|
16
|
+
stack_line["path"].gsub!(MuxTf::ROOT, pastel.gray("{mux_tf}"))
|
17
|
+
msg = [
|
18
|
+
"#{pastel.orange('WARNING')}: #{message}",
|
19
|
+
"at #{pastel.cyan(stack_line['path'])}:#{pastel.white(stack_line['ln'])}:in `#{pastel.cyan(stack_line['method'])}'"
|
20
|
+
]
|
21
|
+
puts msg.join(" - ")
|
22
|
+
end
|
23
|
+
|
24
|
+
def update_placeholders(dst, src, placeholder) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
25
|
+
return unless src
|
26
|
+
|
27
|
+
case src
|
28
|
+
when Array
|
29
|
+
src.each_with_index do |v, index|
|
30
|
+
case v
|
31
|
+
when TrueClass
|
32
|
+
dst[index] = placeholder
|
33
|
+
when FalseClass
|
34
|
+
# do nothing
|
35
|
+
when Hash
|
36
|
+
dst[index] ||= {}
|
37
|
+
update_placeholders(dst[index], v, placeholder)
|
38
|
+
else
|
39
|
+
warning "Unknown array value (index: #{index}) for sensitive: #{v.inspect}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
when Hash
|
43
|
+
src.each do |key, value|
|
44
|
+
case value
|
45
|
+
when TrueClass
|
46
|
+
dst[key] = placeholder
|
47
|
+
when FalseClass
|
48
|
+
# do nothing
|
49
|
+
when Array
|
50
|
+
dst[key] ||= []
|
51
|
+
update_placeholders(dst[key], value, placeholder)
|
52
|
+
when Hash
|
53
|
+
dst[key] ||= {}
|
54
|
+
update_placeholders(dst[key], value, placeholder)
|
55
|
+
else
|
56
|
+
warning "Unknown value (key: #{key}) for sensitive: #{value.inspect}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def tf_show_json_resource_diff(resource)
|
63
|
+
before = resource["change"]["before"] || {}
|
64
|
+
after = resource["change"]["after"] || {}
|
65
|
+
|
66
|
+
update_placeholders(after, resource["change"]["after_unknown"], KNOWN_AFTER_APPLY)
|
67
|
+
|
68
|
+
before = before.sort.to_h
|
69
|
+
after = after.sort.to_h
|
70
|
+
|
71
|
+
update_placeholders(before, resource["change"]["before_sensitive"], SENSITIVE)
|
72
|
+
update_placeholders(after, resource["change"]["after_sensitive"], SENSITIVE)
|
73
|
+
|
74
|
+
# hash_diff = HashDiff::Comparison.new(before, after)
|
75
|
+
# similarity: 0.0, numeric_tolerance: 1, array_path: true,
|
76
|
+
Hashdiff.diff(before, after, use_lcs: false)
|
77
|
+
end
|
78
|
+
|
79
|
+
def string_diff(value1, value2)
|
80
|
+
value1 = value1.split("\n")
|
81
|
+
value2 = value2.split("\n")
|
82
|
+
|
83
|
+
output = []
|
84
|
+
diffs = Diff::LCS.diff value1, value2
|
85
|
+
diffs.each do |diff|
|
86
|
+
hunk = Diff::LCS::Hunk.new(value1, value2, diff, 5, 0)
|
87
|
+
diff_lines = hunk.diff(:unified).split("\n")
|
88
|
+
# diff_lines.shift # remove the first line
|
89
|
+
output += diff_lines.map { |line| " #{line}" }
|
90
|
+
end
|
91
|
+
output
|
92
|
+
end
|
93
|
+
|
94
|
+
def valid_json?(value)
|
95
|
+
value.is_a?(String) && !!JSON.parse(value)
|
96
|
+
rescue JSON::ParserError
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def valid_yaml?(value)
|
101
|
+
if value.is_a?(String)
|
102
|
+
parsed = YAML.safe_load(value)
|
103
|
+
parsed.is_a?(Hash) || parsed.is_a?(Array)
|
104
|
+
else
|
105
|
+
false
|
106
|
+
end
|
107
|
+
rescue Psych::SyntaxError => e
|
108
|
+
ap e
|
109
|
+
false
|
110
|
+
end
|
111
|
+
|
112
|
+
def colorize_symbol(symbol)
|
113
|
+
case symbol
|
114
|
+
when "+"
|
115
|
+
pastel.green(symbol)
|
116
|
+
when "~"
|
117
|
+
pastel.yellow(symbol)
|
118
|
+
else
|
119
|
+
warning "Unknown symbol: #{symbol.inspect}"
|
120
|
+
symbol
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def wrap(text, prefix: "(", suffix: ")", newline: false, color: nil, indent: 0)
|
125
|
+
result = String.new
|
126
|
+
result << (color ? pastel.decorate(prefix, color) : prefix)
|
127
|
+
result << "\n" if newline
|
128
|
+
result << text.split("\n").map { |line|
|
129
|
+
"#{' ' * indent}#{line}"
|
130
|
+
}.join("\n")
|
131
|
+
result << "\n" if newline
|
132
|
+
result << (color ? pastel.decorate(suffix, color) : suffix)
|
133
|
+
result
|
134
|
+
end
|
135
|
+
|
136
|
+
def indent(text, indent: 2, first_line_indent: 0)
|
137
|
+
text.split("\n").map.with_index { |line, index|
|
138
|
+
if index.zero?
|
139
|
+
"#{' ' * first_line_indent}#{line}"
|
140
|
+
else
|
141
|
+
"#{' ' * indent}#{line}"
|
142
|
+
end
|
143
|
+
}.join("\n")
|
144
|
+
end
|
145
|
+
|
146
|
+
def in_display_representation(value) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize, Metrics/MethodLength
|
147
|
+
if valid_json?(value)
|
148
|
+
json_body = JSON.pretty_generate(JSON.parse(value))
|
149
|
+
wrap(json_body, prefix: "json(", suffix: ")", color: :gray)
|
150
|
+
elsif valid_yaml?(value)
|
151
|
+
yaml_body = YAML.dump(YAML.safe_load(value))
|
152
|
+
yaml_body.gsub!(/^---\n/, "")
|
153
|
+
wrap(yaml_body, prefix: "yaml(", suffix: ")", newline: true, color: :gray, indent: 2)
|
154
|
+
elsif [KNOWN_AFTER_APPLY, SENSITIVE].include?(value)
|
155
|
+
pastel.gray(value)
|
156
|
+
elsif value.is_a?(String) && value.include?("\n")
|
157
|
+
wrap(value, prefix: "<<- EOT", suffix: "EOT", newline: true, color: :gray)
|
158
|
+
# elsif value.is_a?(Array)
|
159
|
+
# body = value.ai.rstrip
|
160
|
+
# wrap(body, prefix: "", suffix: "", newline: false, color: :gray, indent: 2)
|
161
|
+
elsif value.is_a?(Array)
|
162
|
+
max_key = value.length.to_s.length
|
163
|
+
body = "["
|
164
|
+
value.each_with_index do |v, _index|
|
165
|
+
# body += "\n #{in_display_representation(index).ljust(max_key)}: "
|
166
|
+
body += "\n"
|
167
|
+
body += indent(in_display_representation(v), indent: 2, first_line_indent: 2)
|
168
|
+
body += ","
|
169
|
+
end
|
170
|
+
body += "\n]"
|
171
|
+
body
|
172
|
+
elsif value.is_a?(Hash)
|
173
|
+
max_key = value.keys.map(&:length).max
|
174
|
+
body = "{"
|
175
|
+
value.each do |k, v|
|
176
|
+
body += "\n #{in_display_representation(k).ljust(max_key)}: "
|
177
|
+
body += indent(in_display_representation(v), indent: 2, first_line_indent: 0)
|
178
|
+
body += ","
|
179
|
+
end
|
180
|
+
body += "\n}"
|
181
|
+
body
|
182
|
+
else
|
183
|
+
value.inspect
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
def format_value_diff(mode, value_arg)
|
188
|
+
case mode
|
189
|
+
when :both
|
190
|
+
vleft = in_display_representation(value_arg[0])
|
191
|
+
vright = in_display_representation(value_arg[1])
|
192
|
+
if [vleft, vright].any? { |v| v.is_a?(String) && v.include?("\n") }
|
193
|
+
if pastel.strip(vright) == KNOWN_AFTER_APPLY
|
194
|
+
"#{vleft} -> #{vright}".split("\n")
|
195
|
+
else
|
196
|
+
string_diff(pastel.strip(vleft), pastel.strip(vright))
|
197
|
+
end
|
198
|
+
else
|
199
|
+
"#{vleft} -> #{vright}".split("\n")
|
200
|
+
end
|
201
|
+
when :right
|
202
|
+
vright = in_display_representation(value_arg[1])
|
203
|
+
vright.split("\n")
|
204
|
+
when :left, :first
|
205
|
+
vleft = in_display_representation(value_arg[0])
|
206
|
+
vleft.split("\n")
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def format_value(change)
|
211
|
+
symbol, _key, *value_arg = change
|
212
|
+
|
213
|
+
mode = :both
|
214
|
+
case symbol
|
215
|
+
when "+", "-"
|
216
|
+
mode = :first
|
217
|
+
when "~"
|
218
|
+
mode = :both
|
219
|
+
else
|
220
|
+
warning "Unknown symbol: #{symbol.inspect}"
|
221
|
+
end
|
222
|
+
|
223
|
+
format_value_diff(mode, value_arg)
|
224
|
+
end
|
225
|
+
|
226
|
+
# def format_value(value_arg, symbol)
|
227
|
+
# case value_arg
|
228
|
+
# when Array
|
229
|
+
# mode = :both
|
230
|
+
# case symbol
|
231
|
+
# when "+"
|
232
|
+
# mode = :right
|
233
|
+
# when "~"
|
234
|
+
# mode = :both
|
235
|
+
# else
|
236
|
+
# warning "Unknown symbol: #{symbol.inspect}"
|
237
|
+
# end
|
238
|
+
|
239
|
+
# format_value_diff(mode, value_arg)
|
240
|
+
# when Hash
|
241
|
+
# if value_arg.keys.all? { |k| k.is_a?(Integer) }
|
242
|
+
# # assuming its a hash notation of array keys changes
|
243
|
+
# value_arg.keys.sort.map { |k| "[#{k}] #{format_value(value_arg[k], symbol)[0]}" }
|
244
|
+
# else
|
245
|
+
# [value_arg.inspect]
|
246
|
+
# end
|
247
|
+
# else
|
248
|
+
# [value_arg.inspect]
|
249
|
+
# end
|
250
|
+
# end
|
251
|
+
|
252
|
+
def get_pretty_action_and_symbol(actions)
|
253
|
+
case actions
|
254
|
+
when ["update"]
|
255
|
+
pretty_action = "updated in-place"
|
256
|
+
symbol = "~"
|
257
|
+
when ["create"]
|
258
|
+
pretty_action = "created"
|
259
|
+
symbol = "+"
|
260
|
+
else
|
261
|
+
warning "Unknown action: #{actions.inspect}"
|
262
|
+
pretty_action = actions.inspect
|
263
|
+
symbol = "?"
|
264
|
+
end
|
265
|
+
|
266
|
+
[pretty_action, symbol]
|
267
|
+
end
|
268
|
+
|
269
|
+
# Example
|
270
|
+
# # kubectl_manifest.crossplane-provider-controller-config["aws-ecr"] will be updated in-place
|
271
|
+
# ~ resource "kubectl_manifest" "crossplane-provider-controller-config" {
|
272
|
+
# id = "/apis/pkg.crossplane.io/v1alpha1/controllerconfigs/aws-ecr-config"
|
273
|
+
# name = "aws-ecr-config"
|
274
|
+
# ~ yaml_body = (sensitive value)
|
275
|
+
# ~ yaml_body_parsed = <<-EOT
|
276
|
+
# apiVersion: pkg.crossplane.io/v1alpha1
|
277
|
+
# kind: ControllerConfig
|
278
|
+
# metadata:
|
279
|
+
# annotations:
|
280
|
+
# - eks.amazonaws.com/role-arn: <AWS_PROVIDER_ARN> <<- irsa
|
281
|
+
# + eks.amazonaws.com/role-arn: arn:aws:iam::852088082597:role/admin-crossplane-provider-aws-ecr
|
282
|
+
# name: aws-ecr-config
|
283
|
+
# spec:
|
284
|
+
# podSecurityContext:
|
285
|
+
# fsGroup: 2000
|
286
|
+
# EOT
|
287
|
+
# # (12 unchanged attributes hidden)
|
288
|
+
# }
|
289
|
+
def tf_show_json_resource(resource) # rubocop:disable Metrics/AbcSize
|
290
|
+
pretty_action, symbol = get_pretty_action_and_symbol(resource["change"]["actions"])
|
291
|
+
|
292
|
+
output = []
|
293
|
+
|
294
|
+
global_indent = " " * 2
|
295
|
+
|
296
|
+
output << ""
|
297
|
+
output << "#{global_indent}#{pastel.bold("# #{resource['address']}")} will be #{pretty_action}"
|
298
|
+
output << "#{global_indent}#{colorize_symbol(symbol)} resource \"#{resource['type']}\" \"#{resource['name']}\" {"
|
299
|
+
diff = tf_show_json_resource_diff(resource)
|
300
|
+
max_diff_key_length = diff.map { |change| change[1].length }.max
|
301
|
+
diff.each do |change|
|
302
|
+
change_symbol, key, *_values = change
|
303
|
+
prefix = format("#{global_indent} #{colorize_symbol(change_symbol)} %s = ", key.ljust(max_diff_key_length))
|
304
|
+
blank_prefix = " " * pastel.strip(prefix).length
|
305
|
+
format_value(change).each_with_index do |line, index|
|
306
|
+
output << if index.zero?
|
307
|
+
"#{prefix}#{line}"
|
308
|
+
else
|
309
|
+
"#{blank_prefix}#{line}"
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
# max_diff_key_length = diff.keys.map(&:length).max
|
314
|
+
# diff.each do |key, value|
|
315
|
+
# prefix = format("#{global_indent} #{colorize_symbol(symbol)} %s = ", key.ljust(max_diff_key_length))
|
316
|
+
# blank_prefix = " " * pastel.strip(prefix).length
|
317
|
+
# format_value(value, symbol).each_with_index do |line, index|
|
318
|
+
# output << if index.zero?
|
319
|
+
# "#{prefix}#{line}"
|
320
|
+
# else
|
321
|
+
# "#{blank_prefix}#{line}"
|
322
|
+
# end
|
323
|
+
# end
|
324
|
+
# end
|
325
|
+
output << "#{global_indent}}"
|
326
|
+
|
327
|
+
output.join("\n")
|
328
|
+
end
|
329
|
+
|
330
|
+
def text_version_of_plan_show(plan_filename)
|
331
|
+
result = tf_show(plan_filename, capture: true, json: true)
|
332
|
+
data = result.parsed_output
|
333
|
+
|
334
|
+
# Plan: 0 to add, 1 to change, 0 to destroy.
|
335
|
+
|
336
|
+
output = []
|
337
|
+
|
338
|
+
output << "Terraform will perform the following actions:"
|
339
|
+
|
340
|
+
if data["resource_drift"]
|
341
|
+
output << ""
|
342
|
+
output << "Resource Drift:"
|
343
|
+
data["resource_drift"].each do |resource|
|
344
|
+
output << tf_show_json_resource(resource)
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
if data["resource_changes"]
|
349
|
+
output << ""
|
350
|
+
output << "Resource Changes:"
|
351
|
+
data["resource_changes"].each do |resource|
|
352
|
+
output << tf_show_json_resource(resource) if resource["change"]["actions"] != ["no-op"]
|
353
|
+
end
|
354
|
+
end
|
355
|
+
|
356
|
+
output.join("\n")
|
357
|
+
end
|
358
|
+
end
|
359
|
+
end
|
360
|
+
end
|