mux_tf 0.14.2 → 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.
@@ -24,13 +24,174 @@ module MuxTf
24
24
  end
25
25
  end
26
26
 
27
- def run(_args)
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
- log "Enumerating folders ..."
31
- dirs = enumerate_terraform_dirs
184
+ if args[0] == "spawner"
185
+ run_spawner
186
+ else
187
+ run_create_session
188
+ end
189
+ end
32
190
 
33
- fail_with "Error: - no subfolders detected! Aborting." if dirs.empty?
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| d.in?(ignored) end
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
- import Coloring
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[:interactive]
36
- abort_message = catch(:abort) { plan.run_interactive }
37
- log pastel.red("Aborted: #{abort_message}") if abort_message
31
+ if options[:hierarchy]
32
+ plan.nested_summary.each do |line|
33
+ puts line
34
+ end
38
35
  else
39
- if options[:hierarchy]
40
- plan.nested_summary.each do |line|
41
- puts line
42
- end
43
- else
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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuxTf
4
+ module Handlers
5
+ class PlanHandler
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MuxTf
4
+ module Handlers
5
+ end
6
+ end