kontena-cli 1.4.0.pre1 → 1.4.0.pre2
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/VERSION +1 -1
- data/kontena-cli.gemspec +1 -1
- data/lib/kontena/cli/common.rb +7 -2
- data/lib/kontena/cli/config.rb +33 -0
- data/lib/kontena/cli/containers/exec_command.rb +12 -33
- data/lib/kontena/cli/helpers/exec_helper.rb +165 -62
- data/lib/kontena/cli/services/exec_command.rb +39 -90
- data/lib/kontena/client.rb +2 -10
- data/lib/kontena/command.rb +4 -3
- data/omnibus/Gemfile +2 -15
- data/omnibus/Gemfile.lock +30 -123
- data/spec/kontena/cli/common_spec.rb +9 -9
- data/spec/kontena/cli/containers/exec_command_spec.rb +55 -0
- data/spec/kontena/cli/helpers/exec_helper_spec.rb +300 -18
- data/spec/kontena/cli/services/exec_command_spec.rb +82 -153
- data/spec/support/client_helpers.rb +6 -3
- metadata +9 -9
- data/lib/kontena/websocket/client.rb +0 -12
- data/lib/kontena/websocket/client/connection.rb +0 -65
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c2f4001bbbebedb3f6e7840825f441c24d125457
|
4
|
+
data.tar.gz: 7fa42d09f45e5c62336691c9896ac0928f31635e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3d420649dce177b328eb57202875eff8d4929447ee77cd05091aaeda928327678c74f9bda74404b0fa17a95963b68eef549e51d9c871313e36af09ee56c0681
|
7
|
+
data.tar.gz: 4e259827e5ed4771a82e1d30267f49fb97e75ba00ec7ad68037f7b4e3f543b8a78084aa350826210c78ff134a89eee876ed97e85736170de2b79fc4200eeae57
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.4.0.
|
1
|
+
1.4.0.pre2
|
data/kontena-cli.gemspec
CHANGED
@@ -33,5 +33,5 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.add_runtime_dependency "semantic", "~> 1.5"
|
34
34
|
spec.add_runtime_dependency "liquid", "~> 4.0.0"
|
35
35
|
spec.add_runtime_dependency "tty-table", "~> 0.8.0"
|
36
|
-
spec.add_runtime_dependency "websocket-
|
36
|
+
spec.add_runtime_dependency "kontena-websocket-client", "~> 0.1.0"
|
37
37
|
end
|
data/lib/kontena/cli/common.rb
CHANGED
@@ -170,7 +170,10 @@ module Kontena
|
|
170
170
|
return unless server.token
|
171
171
|
return unless server.token.refresh_token
|
172
172
|
return if server.token.expired?
|
173
|
-
client = Kontena::Client.new(server.url, server.token
|
173
|
+
client = Kontena::Client.new(server.url, server.token,
|
174
|
+
ssl_cert_path: server.ssl_cert_path,
|
175
|
+
ssl_subject_cn: server.ssl_subject_cn,
|
176
|
+
)
|
174
177
|
logger.debug "Trying to invalidate refresh token on #{server.name}"
|
175
178
|
client.refresh_token
|
176
179
|
rescue => ex
|
@@ -205,7 +208,9 @@ module Kontena
|
|
205
208
|
|
206
209
|
@client = Kontena::Client.new(
|
207
210
|
api_url || require_current_master.url,
|
208
|
-
token || require_current_master.token
|
211
|
+
token || require_current_master.token,
|
212
|
+
ssl_cert_path: require_current_master.ssl_cert_path,
|
213
|
+
ssl_subject_cn: require_current_master.ssl_subject_cn,
|
209
214
|
)
|
210
215
|
end
|
211
216
|
|
data/lib/kontena/cli/config.rb
CHANGED
@@ -507,6 +507,39 @@ module Kontena
|
|
507
507
|
super
|
508
508
|
@table[:account] ||= 'master'
|
509
509
|
end
|
510
|
+
|
511
|
+
def uri
|
512
|
+
@uri ||= URI.parse(self.url)
|
513
|
+
end
|
514
|
+
|
515
|
+
# @return [String, nil] path to ~/.kontena/certs/*.pem
|
516
|
+
def ssl_cert_path
|
517
|
+
path = File.join(Dir.home, '.kontena', 'certs', "#{self.uri.host}.pem")
|
518
|
+
|
519
|
+
if File.exist?(path) && File.readable?(path)
|
520
|
+
return path
|
521
|
+
else
|
522
|
+
return nil
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
# @return [OpenSSL::X509::Certificate, nil]
|
527
|
+
def ssl_cert
|
528
|
+
if path = self.ssl_cert_path
|
529
|
+
return OpenSSL::X509::Certificate.new(File.read(path))
|
530
|
+
else
|
531
|
+
return nil
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
# @return [String, nil] ssl cert subject CN=
|
536
|
+
def ssl_subject_cn
|
537
|
+
if cert = self.ssl_cert
|
538
|
+
return cert.subject.to_a.select{|name, data, type| name == 'CN' }.map{|name, data, type| data }.first
|
539
|
+
else
|
540
|
+
nil
|
541
|
+
end
|
542
|
+
end
|
510
543
|
end
|
511
544
|
|
512
545
|
class Token < OpenStruct
|
@@ -9,42 +9,21 @@ module Kontena::Cli::Containers
|
|
9
9
|
parameter "CONTAINER_ID", "Container id"
|
10
10
|
parameter "CMD ...", "Command"
|
11
11
|
|
12
|
-
option ["--shell"], :flag, "Execute as a shell command"
|
13
|
-
option ["-i", "--interactive"], :flag, "Keep stdin open"
|
14
|
-
option ["-t", "--tty"], :flag, "Allocate a pseudo-TTY"
|
12
|
+
option ["--shell"], :flag, "Execute as a shell command", default: false
|
13
|
+
option ["-i", "--interactive"], :flag, "Keep stdin open", default: false
|
14
|
+
option ["-t", "--tty"], :flag, "Allocate a pseudo-TTY", default: false
|
15
|
+
|
16
|
+
requires_current_master
|
17
|
+
requires_current_grid
|
15
18
|
|
16
19
|
def execute
|
17
|
-
|
20
|
+
exit_status = container_exec("#{current_grid}/#{self.container_id}", self.cmd_list,
|
21
|
+
interactive: interactive?,
|
22
|
+
shell: shell?,
|
23
|
+
tty: tty?,
|
24
|
+
)
|
18
25
|
|
19
|
-
|
20
|
-
token = require_token
|
21
|
-
cmd = JSON.dump({cmd: cmd_list})
|
22
|
-
queue = Queue.new
|
23
|
-
stdin_reader = nil
|
24
|
-
url = ws_url("#{current_grid}/#{container_id}", interactive: interactive?, shell: shell?, tty: tty?)
|
25
|
-
ws = connect(url, token)
|
26
|
-
ws.on :message do |msg|
|
27
|
-
data = parse_message(msg)
|
28
|
-
queue << data if data.is_a?(Hash)
|
29
|
-
end
|
30
|
-
ws.on :open do
|
31
|
-
ws.text(cmd)
|
32
|
-
stdin_reader = self.stream_stdin_to_ws(ws, tty: self.tty?) if self.interactive?
|
33
|
-
end
|
34
|
-
ws.on :close do |e|
|
35
|
-
if e.reason.include?('code: 404')
|
36
|
-
queue << {'exit' => 1, 'message' => 'Not found'}
|
37
|
-
else
|
38
|
-
queue << {'exit' => 1}
|
39
|
-
end
|
40
|
-
end
|
41
|
-
ws.connect
|
42
|
-
while msg = queue.pop
|
43
|
-
self.handle_message(msg)
|
44
|
-
end
|
45
|
-
rescue SystemExit
|
46
|
-
stdin_reader.kill if stdin_reader
|
47
|
-
raise
|
26
|
+
exit exit_status unless exit_status.zero?
|
48
27
|
end
|
49
28
|
end
|
50
29
|
end
|
@@ -1,87 +1,190 @@
|
|
1
|
-
|
1
|
+
require 'io/console'
|
2
|
+
require 'kontena-websocket-client'
|
2
3
|
|
3
4
|
module Kontena::Cli::Helpers
|
4
5
|
module ExecHelper
|
5
6
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
7
|
+
websocket_log_level = if ENV["DEBUG"] == 'websocket'
|
8
|
+
Logger::DEBUG
|
9
|
+
elsif ENV["DEBUG"]
|
10
|
+
Logger::INFO
|
11
|
+
else
|
12
|
+
Logger::WARN
|
13
|
+
end
|
14
|
+
|
15
|
+
Kontena::Websocket::Logging.initialize_logger(STDERR, websocket_log_level)
|
16
|
+
|
17
|
+
WEBSOCKET_CLIENT_OPTIONS = {
|
18
|
+
connect_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 5.0,
|
19
|
+
open_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 5.0,
|
20
|
+
ping_interval: ENV["EXCON_READ_TIMEOUT"] ? ENV["EXCON_READ_TIMEOUT"].to_f : 30.0,
|
21
|
+
ping_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 5.0,
|
22
|
+
close_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_f : 5.0,
|
23
|
+
write_timeout: ENV["EXCON_WRITE_TIMEOUT"] ? ENV["EXCON_WRITE_TIMEOUT"].to_f : 5.0,
|
24
|
+
}
|
25
|
+
|
26
|
+
# @param ws [Kontena::Websocket::Client]
|
27
|
+
# @param tty [Boolean] read stdin in raw mode, sending tty escapes for remote pty
|
28
|
+
# @raise [ArgumentError] not a tty
|
29
|
+
# @yield [data]
|
30
|
+
# @yieldparam data [String] data from stdin
|
31
|
+
# @raise [ArgumentError] not a tty
|
32
|
+
# @return EOF on stdin (!tty)
|
33
|
+
def read_stdin(tty: nil)
|
34
|
+
if tty
|
35
|
+
raise ArgumentError, "the input device is not a TTY" unless STDIN.tty?
|
36
|
+
|
37
|
+
STDIN.raw { |io|
|
38
|
+
# we do not expect EOF on a TTY, ^D sends a tty escape to close the pty instead
|
39
|
+
loop do
|
40
|
+
# raises EOFError, SyscallError or IOError
|
41
|
+
yield io.readpartial(1024)
|
20
42
|
end
|
21
|
-
|
43
|
+
}
|
44
|
+
else
|
45
|
+
# line-buffered
|
46
|
+
while line = STDIN.gets
|
47
|
+
yield line
|
22
48
|
end
|
23
|
-
|
49
|
+
end
|
24
50
|
end
|
25
51
|
|
26
|
-
# @
|
27
|
-
def
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
52
|
+
# @return [String]
|
53
|
+
def websocket_url(path, query = nil)
|
54
|
+
url = URI.parse(require_current_master.url)
|
55
|
+
url.scheme = url.scheme.sub('http', 'ws')
|
56
|
+
url.path = '/v1/' + path
|
57
|
+
url.query = (query && !query.empty?) ? URI.encode_www_form(query) : nil
|
58
|
+
url.to_s
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param ws [Kontena::Websocket::Client]
|
62
|
+
# @return [Integer] exit code
|
63
|
+
def websocket_exec_read(ws)
|
64
|
+
ws.read do |msg|
|
65
|
+
msg = JSON.parse(msg)
|
66
|
+
|
67
|
+
logger.debug "websocket exec read: #{msg.inspect}"
|
68
|
+
|
69
|
+
if msg.has_key?('exit')
|
70
|
+
# breaks the read loop
|
71
|
+
return msg['exit'].to_i
|
72
|
+
elsif msg.has_key?('stream')
|
73
|
+
if msg['stream'] == 'stdout'
|
74
|
+
$stdout << msg['chunk']
|
75
|
+
else
|
76
|
+
$stderr << msg['chunk']
|
77
|
+
end
|
39
78
|
end
|
40
79
|
end
|
41
80
|
end
|
42
81
|
|
43
|
-
# @param [Websocket::
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
82
|
+
# @param ws [Kontena::Websocket::Client]
|
83
|
+
# @param msg [Hash]
|
84
|
+
def websocket_exec_write(ws, msg)
|
85
|
+
logger.debug "websocket exec write: #{msg.inspect}"
|
86
|
+
|
87
|
+
ws.send(JSON.dump(msg))
|
88
|
+
end
|
89
|
+
|
90
|
+
# Start thread to read from stdin, and write to websocket.
|
91
|
+
# Closes websocket on stdin read errors.
|
92
|
+
#
|
93
|
+
# @param ws [Kontena::Websocket::Client]
|
94
|
+
# @param tty [Boolean]
|
95
|
+
# @return [Thread]
|
96
|
+
def websocket_exec_write_thread(ws, tty: nil)
|
97
|
+
Thread.new do
|
98
|
+
begin
|
99
|
+
read_stdin(tty: tty) do |stdin|
|
100
|
+
websocket_exec_write(ws, 'stdin' => stdin)
|
101
|
+
end
|
102
|
+
websocket_exec_write(ws, 'stdin' => nil) # EOF
|
103
|
+
rescue => exc
|
104
|
+
logger.error exc
|
105
|
+
ws.close(1001, "stdin read #{exc.class}: #{exc}")
|
106
|
+
end
|
107
|
+
end
|
48
108
|
end
|
49
109
|
|
50
|
-
#
|
110
|
+
# Connect to server websocket, send from stdin, and write out messages
|
111
|
+
#
|
112
|
+
# @param paths [String]
|
113
|
+
# @param options [Hash] @see Kontena::Websocket::Client
|
114
|
+
# @param cmd [Array<String>] command to execute
|
51
115
|
# @param interactive [Boolean] Interactive TTY on/off
|
52
116
|
# @param shell [Boolean] Shell on/of
|
53
117
|
# @param tty [Boolean] TTY on/of
|
54
|
-
# @return [
|
55
|
-
def
|
56
|
-
|
57
|
-
|
118
|
+
# @return [Integer] exit code
|
119
|
+
def websocket_exec(path, cmd, interactive: false, shell: false, tty: false)
|
120
|
+
exit_status = nil
|
121
|
+
write_thread = nil
|
58
122
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
query = {}
|
64
|
-
query.merge!(interactive: true) if interactive
|
65
|
-
query.merge!(shell: true) if shell
|
66
|
-
query.merge!(tty: true) if tty
|
67
|
-
url.query = URI.encode_www_form(query)
|
68
|
-
end
|
69
|
-
url.to_s
|
70
|
-
end
|
123
|
+
query = {}
|
124
|
+
query[:interactive] = interactive if interactive
|
125
|
+
query[:shell] = shell if shell
|
126
|
+
query[:tty] = tty if tty
|
71
127
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
options = {
|
77
|
-
headers: {
|
128
|
+
server = require_current_master
|
129
|
+
url = websocket_url(path, query)
|
130
|
+
token = require_token
|
131
|
+
options = WEBSOCKET_CLIENT_OPTIONS.dup
|
132
|
+
options[:headers] = {
|
78
133
|
'Authorization' => "Bearer #{token.access_token}"
|
79
|
-
}
|
80
134
|
}
|
81
|
-
|
82
|
-
|
135
|
+
options[:ssl_params] = {
|
136
|
+
verify_mode: ENV['SSL_IGNORE_ERRORS'].to_s == 'true' ? OpenSSL::SSL::VERIFY_NONE : OpenSSL::SSL::VERIFY_PEER,
|
137
|
+
ca_file: server.ssl_cert_path,
|
138
|
+
}
|
139
|
+
options[:ssl_hostname] = server.ssl_subject_cn
|
140
|
+
|
141
|
+
logger.debug { "websocket exec connect... #{url}" }
|
142
|
+
|
143
|
+
# we do not expect CloseError, because the server will send an 'exit' message first,
|
144
|
+
# and we return before seeing the close frame
|
145
|
+
# TODO: handle HTTP 404 errors
|
146
|
+
Kontena::Websocket::Client.connect(url, **options) do |ws|
|
147
|
+
logger.debug { "websocket exec open" }
|
148
|
+
|
149
|
+
# first frame contains exec command
|
150
|
+
websocket_exec_write(ws, 'cmd' => cmd)
|
151
|
+
|
152
|
+
if interactive
|
153
|
+
# start new thread to write from stdin to websocket
|
154
|
+
write_thread = websocket_exec_write_thread(ws, tty: tty)
|
155
|
+
end
|
156
|
+
|
157
|
+
# blocks reading from websocket, returns with exec exit code
|
158
|
+
exit_status = websocket_exec_read(ws)
|
159
|
+
|
160
|
+
fail ws.close_reason unless exit_status
|
161
|
+
end
|
162
|
+
|
163
|
+
rescue Kontena::Websocket::Error => exc
|
164
|
+
exit_with_error(exc)
|
165
|
+
|
166
|
+
rescue => exc
|
167
|
+
logger.error { "websocket exec error: #{exc}" }
|
168
|
+
raise
|
169
|
+
|
170
|
+
else
|
171
|
+
logger.debug { "websocket exec exit: #{exit_status}"}
|
172
|
+
return exit_status
|
173
|
+
|
174
|
+
ensure
|
175
|
+
if write_thread
|
176
|
+
write_thread.kill
|
177
|
+
write_thread.join
|
83
178
|
end
|
84
|
-
|
179
|
+
end
|
180
|
+
|
181
|
+
# Execute command on container using websocket API.
|
182
|
+
#
|
183
|
+
# @param id [String] Container ID (grid/host/name)
|
184
|
+
# @param cmd [Array<String>] command to execute
|
185
|
+
# @return [Integer] exit code
|
186
|
+
def container_exec(id, cmd, **exec_options)
|
187
|
+
websocket_exec("containers/#{id}/exec", cmd, **exec_options)
|
85
188
|
end
|
86
189
|
end
|
87
190
|
end
|
@@ -10,14 +10,23 @@ module Kontena::Cli::Services
|
|
10
10
|
include Kontena::Cli::Helpers::ExecHelper
|
11
11
|
include ServicesHelper
|
12
12
|
|
13
|
+
class ExecExit < StandardError
|
14
|
+
attr_reader :exit_status
|
15
|
+
|
16
|
+
def initialize(exit_status, message = nil)
|
17
|
+
super(message)
|
18
|
+
@exit_status = exit_status
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
13
22
|
parameter "NAME", "Service name"
|
14
23
|
parameter "CMD ...", "Command"
|
15
24
|
|
16
25
|
option ["--instance"], "INSTANCE", "Exec on given numbered instance, default first running" do |value| Integer(value) end
|
17
26
|
option ["-a", "--all"], :flag, "Exec on all running instances"
|
18
|
-
option ["--shell"], :flag, "Execute as a shell command"
|
19
|
-
option ["-i", "--interactive"], :flag, "Keep stdin open"
|
20
|
-
option ["-t", "--tty"], :flag, "Allocate a pseudo-TTY"
|
27
|
+
option ["--shell"], :flag, "Execute as a shell command", default: false
|
28
|
+
option ["-i", "--interactive"], :flag, "Keep stdin open", default: false
|
29
|
+
option ["-t", "--tty"], :flag, "Allocate a pseudo-TTY", default: false
|
21
30
|
option ["--skip"], :flag, "Skip failed instances when executing --all"
|
22
31
|
option ["--silent"], :flag, "Do not show exec status"
|
23
32
|
option ["--verbose"], :flag, "Show exec status"
|
@@ -39,7 +48,7 @@ module Kontena::Cli::Services
|
|
39
48
|
ret = true
|
40
49
|
service_containers.each do |container|
|
41
50
|
if container['status'] == 'running'
|
42
|
-
if !
|
51
|
+
if !execute_container(container)
|
43
52
|
ret = false
|
44
53
|
end
|
45
54
|
else
|
@@ -52,105 +61,45 @@ module Kontena::Cli::Services
|
|
52
61
|
exit_with_error "Service #{name} does not have container instance #{instance}"
|
53
62
|
elsif container['status'] != 'running'
|
54
63
|
exit_with_error "Service #{name} container #{container['name']} is not running, it is #{container['status']}"
|
55
|
-
elsif interactive?
|
56
|
-
interactive_exec(container)
|
57
64
|
else
|
58
|
-
|
65
|
+
execute_container(container)
|
59
66
|
end
|
60
67
|
else
|
61
|
-
|
62
|
-
interactive_exec(running_containers.first)
|
63
|
-
else
|
64
|
-
exec_container(running_containers.first)
|
65
|
-
end
|
68
|
+
execute_container(running_containers.first)
|
66
69
|
end
|
67
70
|
end
|
68
71
|
|
69
|
-
#
|
70
|
-
#
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
spinner "Executing command on #{container['name']}" do
|
75
|
-
exit_status = normal_exec(container)
|
76
|
-
|
77
|
-
raise Kontena::Cli::SpinAbort if exit_status != 0
|
78
|
-
end
|
72
|
+
# Run block with spinner by default if --all, or when using --verbose.
|
73
|
+
# Do not use spinner if --silent.
|
74
|
+
def maybe_spinner(msg, &block)
|
75
|
+
if (all? || verbose?) && !silent?
|
76
|
+
spinner(msg, &block)
|
79
77
|
else
|
80
|
-
|
78
|
+
yield
|
81
79
|
end
|
82
|
-
|
83
|
-
exit exit_status if exit_status != 0 && !skip?
|
84
|
-
|
85
|
-
return exit_status == 0
|
86
80
|
end
|
87
81
|
|
88
82
|
# @param [Hash] container
|
89
|
-
# @
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
exit_status = data['exit'].to_i
|
101
|
-
elsif data['stream'] == 'stdout'
|
102
|
-
$stdout << data['chunk']
|
103
|
-
else
|
104
|
-
$stderr << data['chunk']
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
ws.on :open do
|
109
|
-
ws.text(cmd)
|
83
|
+
# @raise [SystemExit] if exec exits with non-zero status, and not --skip
|
84
|
+
# @return [true] exit exit status zero
|
85
|
+
# @return [false] exit exit status non-zero and --skip
|
86
|
+
def execute_container(container)
|
87
|
+
maybe_spinner "Executing command on #{container['name']}" do
|
88
|
+
exit_status = container_exec(container['id'], self.cmd_list,
|
89
|
+
interactive: interactive?,
|
90
|
+
shell: shell?,
|
91
|
+
tty: tty?,
|
92
|
+
)
|
93
|
+
raise ExecExit.new(exit_status) unless exit_status.zero?
|
110
94
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
sleep 0.01 until !exit_status.nil?
|
117
|
-
|
118
|
-
exit_status
|
119
|
-
end
|
120
|
-
|
121
|
-
# @param [Hash] container
|
122
|
-
def interactive_exec(container)
|
123
|
-
token = require_token
|
124
|
-
cmd = JSON.dump({ cmd: cmd_list })
|
125
|
-
queue = Queue.new
|
126
|
-
stdin_stream = nil
|
127
|
-
ws = connect(url(container['id']), token)
|
128
|
-
ws.on :message do |msg|
|
129
|
-
data = self.parse_message(msg)
|
130
|
-
queue << data if data.is_a?(Hash)
|
131
|
-
end
|
132
|
-
ws.on :open do
|
133
|
-
ws.text(cmd)
|
134
|
-
stdin_stream = self.stream_stdin_to_ws(ws, tty: self.tty?)
|
135
|
-
end
|
136
|
-
ws.on :close do |e|
|
137
|
-
if e.code != 1000
|
138
|
-
queue << {'exit' => 1}
|
139
|
-
else
|
140
|
-
queue << {'exit' => 0}
|
141
|
-
end
|
142
|
-
end
|
143
|
-
ws.connect
|
144
|
-
while msg = queue.pop
|
145
|
-
self.handle_message(msg)
|
95
|
+
rescue ExecExit => exc
|
96
|
+
if skip?
|
97
|
+
return false
|
98
|
+
else
|
99
|
+
exit exc.exit_status
|
146
100
|
end
|
147
|
-
|
148
|
-
|
149
|
-
raise
|
150
|
-
end
|
151
|
-
|
152
|
-
def url(container_id)
|
153
|
-
ws_url(container_id, shell: shell?, interactive: interactive?, tty: tty?)
|
101
|
+
else
|
102
|
+
return true
|
154
103
|
end
|
155
104
|
end
|
156
105
|
end
|