kontena-cli 1.4.0.pre1 → 1.4.0.pre2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|