mux_tf 0.15.0 → 0.16.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 +123 -0
- data/lib/mux_tf/cli/current.rb +92 -171
- 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/handlers/plan_handler.rb +8 -0
- data/lib/mux_tf/handlers.rb +6 -0
- data/lib/mux_tf/plan_formatter.rb +285 -257
- data/lib/mux_tf/plan_summary_handler.rb +36 -29
- data/lib/mux_tf/plan_utils.rb +20 -4
- 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 +44 -6
- data/lib/mux_tf/tmux.rb +55 -6
- data/lib/mux_tf/version.rb +1 -1
- data/lib/mux_tf/version_check.rb +1 -1
- data/lib/mux_tf.rb +6 -12
- data/mux_tf.gemspec +7 -1
- metadata +69 -8
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
|