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
data/lib/mux_tf/cli/mux.rb
CHANGED
@@ -24,13 +24,174 @@ module MuxTf
|
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
def run(
|
27
|
+
def run(args)
|
28
|
+
if ENV["MUX_V2"]
|
29
|
+
run_v2(args)
|
30
|
+
else
|
31
|
+
run_v1(args)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_create_session
|
36
|
+
project = File.basename(Dir.getwd)
|
37
|
+
|
38
|
+
if Tmux.session_running?(project)
|
39
|
+
log "Killing existing session ..."
|
40
|
+
Tmux.kill_session(project)
|
41
|
+
end
|
42
|
+
|
43
|
+
log "Starting new session ..."
|
44
|
+
with_clean_env do
|
45
|
+
Tmux.new_session project
|
46
|
+
end
|
47
|
+
Tmux.select_pane "initial"
|
48
|
+
|
49
|
+
window_id = Tmux.list_windows.first[:id]
|
50
|
+
|
51
|
+
Tmux.set "remain-on-exit", "on"
|
52
|
+
|
53
|
+
Tmux.set_hook "pane-exited", "select-layout tiled"
|
54
|
+
Tmux.set_hook "window-pane-changed", "select-layout tiled"
|
55
|
+
Tmux.set_hook "pane-exited", "select-layout tiled"
|
56
|
+
|
57
|
+
Tmux.set "mouse", "on"
|
58
|
+
|
59
|
+
puts "\e]0;tmux: #{project}\007"
|
60
|
+
|
61
|
+
Tmux.split_window :horizontal, "#{project}:#{window_id}", cwd: Dir.getwd,
|
62
|
+
cmd: File.expand_path(File.join(__dir__, "..", "..", "..", "exe", "tf_mux spawner"))
|
63
|
+
Tmux.select_pane "spawner"
|
64
|
+
|
65
|
+
initial_pane = Tmux.find_pane("initial")
|
66
|
+
Tmux.kill_pane initial_pane[:id]
|
67
|
+
Tmux.tile!
|
68
|
+
|
69
|
+
log "Attaching ..."
|
70
|
+
Tmux.attach(project)
|
71
|
+
log "Done!"
|
72
|
+
end
|
73
|
+
|
74
|
+
def parse_control_line(line)
|
75
|
+
keyword, remainder = line.split(" ", 2)
|
76
|
+
case keyword
|
77
|
+
when "%begin"
|
78
|
+
# n1, n2, n3, remainder = remainder.split(" ", 4)
|
79
|
+
p [:begin, n1, n2, n3, remainder]
|
80
|
+
when "%end"
|
81
|
+
# n1, n2, n3, remainder = remainder.split(" ", 4)
|
82
|
+
p [:end, n1, n2, n3, remainder]
|
83
|
+
when "%session-changed"
|
84
|
+
# n1, s1, remainder = remainder.split(" ", 3)
|
85
|
+
# p [:session_changed, n1, s1, remainder]
|
86
|
+
when "%window-pane-changed"
|
87
|
+
# n1, n2, remainder = remainder.split(" ", 3)
|
88
|
+
# p [:window_pane_changed, n1, n2, remainder]
|
89
|
+
when "%layout-change"
|
90
|
+
# ignore
|
91
|
+
# p [:layout_change, remainder]
|
92
|
+
when "%pane-mode-changed"
|
93
|
+
# ignore
|
94
|
+
p [:layout_change, remainder]
|
95
|
+
when "%subscription-changed"
|
96
|
+
sub_name, n1, n2, n3, n4, _, remainder = remainder.split(" ", 7)
|
97
|
+
if sub_name == "pane-info"
|
98
|
+
pane_id, pane_index, pane_title, pane_dead_status = remainder.strip.split(",", 4)
|
99
|
+
if pane_dead_status != ""
|
100
|
+
p [:pane_exited, pane_id, pane_index, pane_title, pane_dead_status]
|
101
|
+
Tmux.kill_pane(pane_id)
|
102
|
+
panes = Tmux.list_panes
|
103
|
+
if panes.length == 1 && panes.first[:name] == "spawner"
|
104
|
+
Tmux.kill_pane(panes.first[:id])
|
105
|
+
# its the last pane, so the whole thing should exit
|
106
|
+
end
|
107
|
+
end
|
108
|
+
else
|
109
|
+
p [:subscription_changed, sub_name, n1, n2, n3, n4, remainder]
|
110
|
+
end
|
111
|
+
when "%output"
|
112
|
+
pane, = remainder.split(" ", 2)
|
113
|
+
if pane == "%1"
|
114
|
+
# skip own output
|
115
|
+
# else
|
116
|
+
# p [:output, pane, remainder]
|
117
|
+
end
|
118
|
+
else
|
119
|
+
p [keyword, remainder]
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
def run_spawner
|
124
|
+
project = File.basename(Dir.getwd)
|
125
|
+
|
126
|
+
control_thread = Thread.new do
|
127
|
+
puts "Control Thread Started"
|
128
|
+
Tmux.attach_control(project, on_spawn: lambda { |stdin|
|
129
|
+
stdin.write("refresh-client -B \"pane-info:%*:\#{pane_id},\#{pane_index},\#{pane_title},\#{pane_dead_status}\"\n")
|
130
|
+
stdin.flush
|
131
|
+
}, on_line: lambda { |stream, line|
|
132
|
+
if stream == :stdout
|
133
|
+
parse_control_line(line)
|
134
|
+
# p info
|
135
|
+
else
|
136
|
+
p [stream, line]
|
137
|
+
end
|
138
|
+
})
|
139
|
+
puts "Control Thread Exited"
|
140
|
+
end
|
141
|
+
|
142
|
+
begin
|
143
|
+
log "Enumerating folders ..."
|
144
|
+
dirs = enumerate_terraform_dirs
|
145
|
+
|
146
|
+
fail_with "Error: - no subfolders detected! Aborting." if dirs.empty?
|
147
|
+
|
148
|
+
tasks = dirs.map { |dir|
|
149
|
+
{
|
150
|
+
name: dir,
|
151
|
+
cwd: dir,
|
152
|
+
cmd: File.expand_path(File.join(__dir__, "..", "..", "..", "exe", "tf_current"))
|
153
|
+
}
|
154
|
+
}
|
155
|
+
|
156
|
+
if ENV["MUX_TF_AUTH_WRAPPER"]
|
157
|
+
log "Warming up AWS connection ..."
|
158
|
+
words = Shellwords.shellsplit(ENV["MUX_TF_AUTH_WRAPPER"])
|
159
|
+
result = capture_shell([*words, "aws", "sts", "get-caller-identity"], raise_on_error: true)
|
160
|
+
p JSON.parse(result)
|
161
|
+
end
|
162
|
+
|
163
|
+
window_id = Tmux.list_windows.first[:id]
|
164
|
+
|
165
|
+
return if tasks.empty?
|
166
|
+
|
167
|
+
tasks.each do |task|
|
168
|
+
log "launching task: #{task[:name]} ...", depth: 2
|
169
|
+
Tmux.split_window :horizontal, "#{project}:#{window_id}", cmd: task[:cmd], cwd: task[:cwd]
|
170
|
+
Tmux.select_pane task[:name]
|
171
|
+
Tmux.tile!
|
172
|
+
task[:commands]&.each do |cmd|
|
173
|
+
Tmux.send_keys cmd, enter: true
|
174
|
+
end
|
175
|
+
end
|
176
|
+
ensure
|
177
|
+
control_thread.join
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def run_v2(args)
|
28
182
|
Dotenv.load(".env.mux")
|
29
183
|
|
30
|
-
|
31
|
-
|
184
|
+
if args[0] == "spawner"
|
185
|
+
run_spawner
|
186
|
+
else
|
187
|
+
run_create_session
|
188
|
+
end
|
189
|
+
end
|
32
190
|
|
33
|
-
|
191
|
+
def run_v1(_args)
|
192
|
+
Dotenv.load(".env.mux")
|
193
|
+
|
194
|
+
dirs = enumerate_terraform_dirs
|
34
195
|
|
35
196
|
tasks = dirs.map { |dir|
|
36
197
|
{
|
@@ -104,7 +265,9 @@ module MuxTf
|
|
104
265
|
ignored += ENV["MUX_IGNORE"].split(",") if ENV["MUX_IGNORE"]
|
105
266
|
|
106
267
|
dirs = Dir["**/.terraform.lock.hcl"].map { |f| File.dirname(f) }
|
107
|
-
dirs.reject! do |d|
|
268
|
+
dirs.reject! do |d|
|
269
|
+
d.in?(ignored)
|
270
|
+
end
|
108
271
|
|
109
272
|
dirs
|
110
273
|
end
|
@@ -6,19 +6,15 @@ module MuxTf
|
|
6
6
|
extend PiotrbCliUtils::Util
|
7
7
|
extend PiotrbCliUtils::ShellHelpers
|
8
8
|
extend TerraformHelpers
|
9
|
-
|
9
|
+
include Coloring
|
10
10
|
|
11
11
|
class << self
|
12
12
|
def run(args)
|
13
13
|
options = {
|
14
|
-
interactive: false,
|
15
14
|
hierarchy: false
|
16
15
|
}
|
17
16
|
|
18
17
|
args = OptionParser.new { |opts|
|
19
|
-
opts.on("-i") do |v|
|
20
|
-
options[:interactive] = v
|
21
|
-
end
|
22
18
|
opts.on("-h") do |v|
|
23
19
|
options[:hierarchy] = v
|
24
20
|
end
|
@@ -32,25 +28,20 @@ module MuxTf
|
|
32
28
|
PlanSummaryHandler.from_data(JSON.parse($stdin.read))
|
33
29
|
end
|
34
30
|
|
35
|
-
if options[:
|
36
|
-
|
37
|
-
|
31
|
+
if options[:hierarchy]
|
32
|
+
plan.nested_summary.each do |line|
|
33
|
+
puts line
|
34
|
+
end
|
38
35
|
else
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
plan.flat_summary.each do |line|
|
45
|
-
puts line
|
46
|
-
end
|
47
|
-
plan.output_summary.each do |line|
|
48
|
-
puts line
|
49
|
-
end
|
36
|
+
plan.flat_summary.each do |line|
|
37
|
+
puts line
|
38
|
+
end
|
39
|
+
plan.output_summary.each do |line|
|
40
|
+
puts line
|
50
41
|
end
|
51
|
-
puts
|
52
|
-
puts plan.summary
|
53
42
|
end
|
43
|
+
puts
|
44
|
+
puts plan.summary
|
54
45
|
end
|
55
46
|
end
|
56
47
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MuxTf
|
4
|
+
module ErrorHandlingMethods
|
5
|
+
def setup_error_handling(parser, from_states:)
|
6
|
+
parser.state(:init_error, /^Terraform encountered problems during initialisation/, [:none])
|
7
|
+
parser.state(:error_block, /^╷/, from_states | [:after_error, :init_error])
|
8
|
+
parser.state(:error_block_error, /^│ Error: /, [:error_block, :init_error])
|
9
|
+
parser.state(:error_block_warning, /^│ Warning: /, [:error_block, :init_error])
|
10
|
+
parser.state(:after_error, /^╵/, [:error_block, :error_block_error, :error_block_warning, :init_error])
|
11
|
+
end
|
12
|
+
|
13
|
+
def handle_error_states(meta, state, line)
|
14
|
+
case state
|
15
|
+
when :error_block
|
16
|
+
if meta[:init_error]
|
17
|
+
# turn this into an error first
|
18
|
+
meta[:errors] ||= []
|
19
|
+
meta[:errors] << {
|
20
|
+
type: :error,
|
21
|
+
message: meta[:init_error].join("\n")
|
22
|
+
# body: meta[:init_error]
|
23
|
+
}
|
24
|
+
meta.delete(:init_error)
|
25
|
+
end
|
26
|
+
meta[:current_error] = {
|
27
|
+
type: :unknown,
|
28
|
+
body: []
|
29
|
+
}
|
30
|
+
when :error_block_error, :error_block_warning
|
31
|
+
clean_line = pastel.strip(line).gsub(/^│ /, "")
|
32
|
+
if clean_line =~ /^(Warning|Error): (.+)$/
|
33
|
+
meta[:current_error][:type] = $LAST_MATCH_INFO[1].downcase.to_sym
|
34
|
+
meta[:current_error][:message] = $LAST_MATCH_INFO[2]
|
35
|
+
elsif clean_line == ""
|
36
|
+
# skip double empty lines
|
37
|
+
meta[:current_error][:body] << clean_line if meta[:current_error][:body].last != ""
|
38
|
+
else
|
39
|
+
meta[:current_error][:body] ||= []
|
40
|
+
meta[:current_error][:body] << clean_line
|
41
|
+
end
|
42
|
+
when :after_error
|
43
|
+
case pastel.strip(line)
|
44
|
+
when "╵" # closing of an error block
|
45
|
+
if meta[:current_error][:type] == :error
|
46
|
+
meta[:errors] ||= []
|
47
|
+
meta[:errors] << meta[:current_error]
|
48
|
+
end
|
49
|
+
if meta[:current_error][:type] == :warning
|
50
|
+
meta[:warnings] ||= []
|
51
|
+
meta[:warnings] << meta[:current_error]
|
52
|
+
end
|
53
|
+
meta.delete(:current_error)
|
54
|
+
end
|
55
|
+
when :init_error
|
56
|
+
meta[:init_error] ||= []
|
57
|
+
meta[:init_error] << line
|
58
|
+
else
|
59
|
+
return false
|
60
|
+
end
|
61
|
+
true
|
62
|
+
end
|
63
|
+
|
64
|
+
def log_unhandled_line(state, line, reason: nil)
|
65
|
+
p [state, pastel.strip(line), reason]
|
66
|
+
end
|
67
|
+
|
68
|
+
def print_errors(meta)
|
69
|
+
meta[:errors]&.each do |error|
|
70
|
+
log "-" * 20
|
71
|
+
log pastel.red("Error: #{error[:message]}")
|
72
|
+
error[:body]&.each do |line|
|
73
|
+
log pastel.red(line), depth: 1
|
74
|
+
end
|
75
|
+
log ""
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,257 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module MuxTf
|
4
|
+
module FormatterCommon
|
5
|
+
def tf_cmd_json(cmd_call_proc, &block)
|
6
|
+
last_stderr_line = nil
|
7
|
+
handler = proc { |(stream, raw_line)| # rubocop:disable Metrics/BlockLength
|
8
|
+
case stream
|
9
|
+
when :command
|
10
|
+
log "Running command: #{raw_line.strip} ...", depth: 2
|
11
|
+
when :stdout
|
12
|
+
begin
|
13
|
+
parsed_line = JSON.parse(raw_line)
|
14
|
+
parsed_line.keys.each do |key|
|
15
|
+
if key[0] == "@"
|
16
|
+
parsed_line[key[1..]] = parsed_line[key]
|
17
|
+
parsed_line.delete(key)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
parsed_line.symbolize_keys!
|
21
|
+
parsed_line[:stream] = stream
|
22
|
+
if last_stderr_line
|
23
|
+
emit_line_helper(last_stderr_line, &block)
|
24
|
+
last_stderr_line = nil
|
25
|
+
end
|
26
|
+
emit_line_helper(parsed_line, &block)
|
27
|
+
rescue JSON::ParserError
|
28
|
+
# eg: "[WARN] Provider spacelift-io/spacelift (registry.opentofu.org) gpg key expired, this will fail in future versions of OpenTofu\n"
|
29
|
+
# treat the line as a normal log line
|
30
|
+
parsed_line = {
|
31
|
+
message: raw_line.strip,
|
32
|
+
module: "non-json-log"
|
33
|
+
}
|
34
|
+
parsed_line[:level] = raw_line[0..-2].strip if raw_line.match?(/^[A-Z]+/)
|
35
|
+
parsed_line[:stream] = stream
|
36
|
+
emit_line_helper(parsed_line, &block)
|
37
|
+
end
|
38
|
+
when :stderr
|
39
|
+
parsed_line = parse_non_json_plan_line(raw_line)
|
40
|
+
parsed_line[:stream] = stream
|
41
|
+
|
42
|
+
if parsed_line[:blank]
|
43
|
+
if last_stderr_line
|
44
|
+
emit_line_helper(last_stderr_line, &block)
|
45
|
+
last_stderr_line = nil
|
46
|
+
end
|
47
|
+
elsif parsed_line[:merge_up]
|
48
|
+
if last_stderr_line
|
49
|
+
last_stderr_line[:message] += "\n#{parsed_line[:message]}"
|
50
|
+
else
|
51
|
+
# this is just a standalone message then
|
52
|
+
parsed_line.delete(:merge_up)
|
53
|
+
last_stderr_line = parsed_line
|
54
|
+
end
|
55
|
+
elsif last_stderr_line
|
56
|
+
emit_line_helper(last_stderr_line, &block)
|
57
|
+
last_stderr_line = parsed_line
|
58
|
+
else
|
59
|
+
last_stderr_line = parsed_line
|
60
|
+
end
|
61
|
+
end
|
62
|
+
}
|
63
|
+
|
64
|
+
status = cmd_call_proc.call(handler)
|
65
|
+
|
66
|
+
emit_line_helper(last_stderr_line, &block) if last_stderr_line
|
67
|
+
status
|
68
|
+
end
|
69
|
+
|
70
|
+
def parse_non_json_plan_line(raw_line)
|
71
|
+
result = {}
|
72
|
+
|
73
|
+
if raw_line.match(/^time=(?<timestamp>[^ ]+) level=(?<level>[^ ]+) msg=(?<message>.+?)(?: prefix=\[(?<prefix>.+?)\])?\s*$/)
|
74
|
+
result.merge!($LAST_MATCH_INFO.named_captures.symbolize_keys)
|
75
|
+
result[:module] = "terragrunt"
|
76
|
+
result.delete(:prefix) unless result[:prefix]
|
77
|
+
result[:prefix] = Pathname.new(result[:prefix]).relative_path_from(Dir.getwd).to_s if result[:prefix]
|
78
|
+
|
79
|
+
result[:merge_up] = true if result[:message].match(/^\d+ errors? occurred:$/)
|
80
|
+
elsif raw_line.strip == ""
|
81
|
+
result[:blank] = true
|
82
|
+
else
|
83
|
+
result[:message] = raw_line
|
84
|
+
result[:merge_up] = true
|
85
|
+
end
|
86
|
+
|
87
|
+
# 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]
|
88
|
+
# time=2023-08-25T11:44:41-07:00 level=error msg=1 error occurred:
|
89
|
+
# * [/Users/piotr/Work/janepods/.terragrunt-cache/BM86IAj5tW4bZga2lXeYT8tdOKI/V0IEypKSfyl-kHfCnRNAqyX02V8/modules/event-bus] exit status 2
|
90
|
+
#
|
91
|
+
#
|
92
|
+
result
|
93
|
+
end
|
94
|
+
|
95
|
+
def emit_line_helper(result, &block)
|
96
|
+
result[:level] ||= result[:stream] == :stderr ? "error" : "info"
|
97
|
+
result[:module] ||= result[:stream]
|
98
|
+
result[:type] ||= "unknown"
|
99
|
+
|
100
|
+
result[:message].lstrip! if result[:message] =~ /^\n/
|
101
|
+
|
102
|
+
if result[:message].match(/^Terraform invocation failed in (.+)/)
|
103
|
+
result[:type] = "tf_failed"
|
104
|
+
|
105
|
+
lines = result[:message].split("\n")
|
106
|
+
result[:diagnostic] = {
|
107
|
+
"summary" => "Terraform invocation failed",
|
108
|
+
"detail" => result[:message],
|
109
|
+
roots: [],
|
110
|
+
extra: []
|
111
|
+
}
|
112
|
+
|
113
|
+
lines.each do |line|
|
114
|
+
if line.match(/^\s+\* \[(.+)\] exit status (\d+)$/)
|
115
|
+
result[:diagnostic][:roots] << {
|
116
|
+
path: $LAST_MATCH_INFO[1],
|
117
|
+
status: $LAST_MATCH_INFO[2].to_i
|
118
|
+
}
|
119
|
+
elsif line.match(/^\d+ errors? occurred$/)
|
120
|
+
# noop
|
121
|
+
else
|
122
|
+
result[:diagnostic][:extra] << line
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
result[:message] = "Terraform invocation failed"
|
127
|
+
end
|
128
|
+
|
129
|
+
block.call(result)
|
130
|
+
end
|
131
|
+
|
132
|
+
def print_unhandled_error_line(parsed_line)
|
133
|
+
if parsed_line[:diagnostic]
|
134
|
+
color = :red
|
135
|
+
dinfo = parsed_line[:diagnostic]
|
136
|
+
|
137
|
+
log "#{pastel.decorate(dinfo['severity'].capitalize, color)}: #{dinfo['summary']}", depth: 3
|
138
|
+
log dinfo["detail"].split("\n"), depth: 4 if dinfo["detail"]
|
139
|
+
log format_validation_range(dinfo, color), depth: 4 if dinfo["range"]
|
140
|
+
elsif parsed_line[:message] =~ /^\[reset\]/
|
141
|
+
log pastel.red(parsed_line[:message].gsub(/^\[reset\]/, "")), depth: 3
|
142
|
+
else
|
143
|
+
p parsed_line
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def format_validation_range(dinfo, color)
|
148
|
+
range = dinfo["range"]
|
149
|
+
|
150
|
+
# filename: "../../../modules/pods/jane_pod/main.tf"
|
151
|
+
# start:
|
152
|
+
# line: 151
|
153
|
+
# column: 27
|
154
|
+
# byte: 6632
|
155
|
+
# end:
|
156
|
+
# line: 151
|
157
|
+
# column: 53
|
158
|
+
# byte: 6658
|
159
|
+
|
160
|
+
context_lines = 3
|
161
|
+
|
162
|
+
lines = range["start"]["line"]..range["end"]["line"]
|
163
|
+
columns = range["start"]["column"]..range["end"]["column"]
|
164
|
+
|
165
|
+
# on ../../../modules/pods/jane_pod/main.tf line 151, in module "jane":
|
166
|
+
# 151: jane_resources_preset = var.jane_resources_presetx
|
167
|
+
output = []
|
168
|
+
lines_info = if lines.size == 1
|
169
|
+
"#{lines.first}:#{columns.first}"
|
170
|
+
else
|
171
|
+
"#{lines.first}:#{columns.first} to #{lines.last}:#{columns.last}"
|
172
|
+
end
|
173
|
+
output << "on: #{range['filename']} line#{lines.size > 1 ? 's' : ''}: #{lines_info}"
|
174
|
+
|
175
|
+
# 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
|
176
|
+
if File.exist?(range["filename"])
|
177
|
+
file_lines = File.read(range["filename"]).split("\n")
|
178
|
+
extract_range = (([lines.first - context_lines,
|
179
|
+
0].max)..([lines.last + context_lines, file_lines.length - 1].min))
|
180
|
+
file_lines.each_with_index do |line, index|
|
181
|
+
if extract_range.cover?(index + 1)
|
182
|
+
if lines.cover?(index + 1)
|
183
|
+
start_col = 1
|
184
|
+
end_col = :max
|
185
|
+
if index + 1 == lines.first
|
186
|
+
start_col = columns.first
|
187
|
+
elsif index + 1 == lines.last
|
188
|
+
start_col = columns.last
|
189
|
+
end
|
190
|
+
painted_line = paint_line(line, color, start_col: start_col, end_col: end_col)
|
191
|
+
output << "#{pastel.decorate('>', color)} #{index + 1}: #{painted_line}"
|
192
|
+
else
|
193
|
+
output << " #{index + 1}: #{line}"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
end
|
197
|
+
elsif dinfo["snippet"]
|
198
|
+
snippet = dinfo["snippet"]
|
199
|
+
# {
|
200
|
+
# "context"=>"locals",
|
201
|
+
# "code"=>" aws_iam_policy.crossplane_aws_ecr.arn",
|
202
|
+
# "start_line"=>72,
|
203
|
+
# "highlight_start_offset"=>8,
|
204
|
+
# "highlight_end_offset"=>41,
|
205
|
+
# "values"=>[]
|
206
|
+
# }
|
207
|
+
output << "Code:"
|
208
|
+
output << "in #{snippet['context']}" if snippet["context"]
|
209
|
+
line = snippet["start_line"]
|
210
|
+
snippet["code"].split("\n").each do |l|
|
211
|
+
output << "#{line}: #{l}"
|
212
|
+
line += 1
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
if dinfo["snippet"] && dinfo["snippet"]["values"]&.any?
|
217
|
+
snippet = dinfo["snippet"]
|
218
|
+
output << ""
|
219
|
+
output << "Values:"
|
220
|
+
snippet["values"].each do |value|
|
221
|
+
output << " #{pastel.bold(value['traversal'])} #{value['statement']}"
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
output
|
226
|
+
end
|
227
|
+
|
228
|
+
def paint_line(line, *paint_options, start_col: 1, end_col: :max)
|
229
|
+
end_col = line.length if end_col == :max
|
230
|
+
prefix = line[0, start_col - 1]
|
231
|
+
suffix = line[end_col..]
|
232
|
+
middle = line[start_col - 1..end_col - 1]
|
233
|
+
"#{prefix}#{pastel.decorate(middle, *paint_options)}#{suffix}"
|
234
|
+
end
|
235
|
+
|
236
|
+
def print_tg_error_line(parsed_line)
|
237
|
+
parsed_line[:message].split("\n").each do |line|
|
238
|
+
next if line.strip == ""
|
239
|
+
|
240
|
+
parsed_message = JSON.parse(line)
|
241
|
+
raw_prefix = "[TG ERROR] "
|
242
|
+
prefix = pastel.red(raw_prefix)
|
243
|
+
index = 0
|
244
|
+
lines = parsed_message["msg"].split("\n").map { |l|
|
245
|
+
l = if index.zero?
|
246
|
+
prefix + l
|
247
|
+
else
|
248
|
+
(" " * raw_prefix.length) + l
|
249
|
+
end
|
250
|
+
index += 1
|
251
|
+
l
|
252
|
+
}
|
253
|
+
log lines, depth: 1
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
257
|
+
end
|