mux_tf 0.13.0 → 0.14.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|