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
data/lib/kontena/client.rb
CHANGED
@@ -59,16 +59,8 @@ module Kontena
|
|
59
59
|
require 'kontena/debug_instrumentor'
|
60
60
|
excon_opts[:instrumentor] = Kontena::DebugInstrumentor
|
61
61
|
end
|
62
|
-
|
63
|
-
|
64
|
-
if File.exist?(cert_file) && File.readable?(cert_file)
|
65
|
-
excon_opts[:ssl_ca_file] = cert_file
|
66
|
-
key = OpenSSL::X509::Certificate.new(File.read(cert_file))
|
67
|
-
if key.issuer.to_s == "/C=FI/O=Test/OU=Test/CN=Test"
|
68
|
-
debug { "Key looks like a self-signed cert made by Kontena CLI, setting verify_peer_host to 'Test'" }
|
69
|
-
excon_opts[:ssl_verify_peer_host] = 'Test'
|
70
|
-
end
|
71
|
-
end
|
62
|
+
excon_opts[:ssl_ca_file] = @options[:ssl_cert_path]
|
63
|
+
excon_opts[:ssl_verify_peer_host] = @options[:ssl_subject_cn]
|
72
64
|
|
73
65
|
debug { "Excon opts: #{excon_opts.inspect}" }
|
74
66
|
|
data/lib/kontena/command.rb
CHANGED
@@ -164,9 +164,10 @@ class Kontena::Command < Clamp::Command
|
|
164
164
|
retried ||= false
|
165
165
|
Kontena::Cli::Config.instance.require_current_master_token
|
166
166
|
rescue Kontena::Cli::Config::TokenExpiredError
|
167
|
-
|
168
|
-
|
169
|
-
|
167
|
+
server = Kontena::Cli::Config.instance.current_master
|
168
|
+
success = Kontena::Client.new(server.url, server.token,
|
169
|
+
ssl_cert_path: server.ssl_cert_path,
|
170
|
+
ssl_subject_cn: server.ssl_subject_cn,
|
170
171
|
).refresh_token
|
171
172
|
if success && !retried
|
172
173
|
retried = true
|
data/omnibus/Gemfile
CHANGED
@@ -1,22 +1,9 @@
|
|
1
1
|
source 'https://rubygems.org'
|
2
2
|
|
3
3
|
# Install omnibus
|
4
|
-
gem 'omnibus', '~> 5.
|
4
|
+
gem 'omnibus', '~> 5.6'
|
5
5
|
|
6
6
|
# Use Chef's software definitions. It is recommended that you write your own
|
7
7
|
# software definitions, but you can clone/fork Chef's to get you started.
|
8
|
-
|
8
|
+
gem 'omnibus-software', github: 'opscode/omnibus-software'
|
9
9
|
|
10
|
-
# This development group is installed by default when you run `bundle install`,
|
11
|
-
# but if you are using Omnibus in a CI-based infrastructure, you do not need
|
12
|
-
# the Test Kitchen-based build lab. You can skip these unnecessary dependencies
|
13
|
-
# by running `bundle install --without development` to speed up build times.
|
14
|
-
group :development do
|
15
|
-
# Use Berkshelf for resolving cookbook dependencies
|
16
|
-
gem 'berkshelf', '~> 3.3'
|
17
|
-
|
18
|
-
# Use Test Kitchen with Vagrant for converging the build environment
|
19
|
-
gem 'test-kitchen', '~> 1.4'
|
20
|
-
gem 'kitchen-vagrant', '~> 0.18'
|
21
|
-
gem 'omnibus-software', github: 'chef/omnibus-software'
|
22
|
-
end
|
data/omnibus/Gemfile.lock
CHANGED
@@ -1,107 +1,48 @@
|
|
1
1
|
GIT
|
2
|
-
remote: git://github.com/
|
3
|
-
revision:
|
2
|
+
remote: git://github.com/opscode/omnibus-software.git
|
3
|
+
revision: f315da39ad2ead2541fdaf21af1a555870109e7a
|
4
4
|
specs:
|
5
5
|
omnibus-software (4.0.0)
|
6
6
|
chef-sugar (>= 3.4.0)
|
7
|
-
omnibus (>= 5.
|
7
|
+
omnibus (>= 5.6.1)
|
8
8
|
|
9
9
|
GEM
|
10
10
|
remote: https://rubygems.org/
|
11
11
|
specs:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
awesome_print (1.8.0)
|
13
|
+
aws-sdk (2.10.17)
|
14
|
+
aws-sdk-resources (= 2.10.17)
|
15
|
+
aws-sdk-core (2.10.17)
|
16
|
+
aws-sigv4 (~> 1.0)
|
17
17
|
jmespath (~> 1.0)
|
18
|
-
aws-sdk-resources (2.
|
19
|
-
aws-sdk-core (= 2.
|
20
|
-
|
21
|
-
addressable (~> 2.3.4)
|
22
|
-
berkshelf-api-client (~> 1.2)
|
23
|
-
buff-config (~> 1.0)
|
24
|
-
buff-extensions (~> 1.0)
|
25
|
-
buff-shell_out (~> 0.1)
|
26
|
-
celluloid (~> 0.16.0)
|
27
|
-
celluloid-io (~> 0.16.1)
|
28
|
-
cleanroom (~> 1.0)
|
29
|
-
faraday (~> 0.9.0)
|
30
|
-
httpclient (~> 2.6.0)
|
31
|
-
minitar (~> 0.5.4)
|
32
|
-
octokit (~> 3.0)
|
33
|
-
retryable (~> 2.0)
|
34
|
-
ridley (~> 4.0)
|
35
|
-
solve (~> 1.1)
|
36
|
-
thor (~> 0.19)
|
37
|
-
berkshelf-api-client (1.3.1)
|
38
|
-
faraday (~> 0.9.1)
|
39
|
-
httpclient (~> 2.6.0)
|
40
|
-
buff-config (1.0.1)
|
41
|
-
buff-extensions (~> 1.0)
|
42
|
-
varia_model (~> 0.4)
|
43
|
-
buff-extensions (1.0.0)
|
44
|
-
buff-ignore (1.2.0)
|
45
|
-
buff-ruby_engine (0.1.0)
|
46
|
-
buff-shell_out (0.2.0)
|
47
|
-
buff-ruby_engine (~> 0.1.0)
|
48
|
-
celluloid (0.16.0)
|
49
|
-
timers (~> 4.0.0)
|
50
|
-
celluloid-io (0.16.2)
|
51
|
-
celluloid (>= 0.16.0)
|
52
|
-
nio4r (>= 1.1.0)
|
18
|
+
aws-sdk-resources (2.10.17)
|
19
|
+
aws-sdk-core (= 2.10.17)
|
20
|
+
aws-sigv4 (1.0.1)
|
53
21
|
chef-config (12.13.37)
|
54
22
|
fuzzyurl
|
55
23
|
mixlib-config (~> 2.0)
|
56
24
|
mixlib-shellout (~> 2.0)
|
57
|
-
chef-sugar (3.
|
25
|
+
chef-sugar (3.5.0)
|
58
26
|
cleanroom (1.0.0)
|
59
|
-
dep-selector-libgecode (1.3.1)
|
60
|
-
dep_selector (1.0.4)
|
61
|
-
dep-selector-libgecode (~> 1.0)
|
62
|
-
ffi (~> 1.9)
|
63
|
-
erubis (2.7.0)
|
64
|
-
faraday (0.9.2)
|
65
|
-
multipart-post (>= 1.2, < 3)
|
66
27
|
ffi (1.9.14)
|
67
|
-
ffi-yajl (2.3.
|
28
|
+
ffi-yajl (2.3.1)
|
68
29
|
libyajl2 (~> 1.2)
|
69
30
|
fuzzyurl (0.9.0)
|
70
|
-
|
71
|
-
hitimes (1.2.4)
|
72
|
-
httpclient (2.6.0.1)
|
31
|
+
iostruct (0.0.4)
|
73
32
|
ipaddress (0.8.3)
|
74
33
|
jmespath (1.3.1)
|
75
|
-
json (2.0.2)
|
76
|
-
kitchen-vagrant (0.20.0)
|
77
|
-
test-kitchen (~> 1.4)
|
78
34
|
libyajl2 (1.2.0)
|
79
|
-
license_scout (0.1.
|
35
|
+
license_scout (0.1.3)
|
80
36
|
ffi-yajl (~> 2.2)
|
81
37
|
mixlib-shellout (~> 2.2)
|
82
|
-
minitar (0.5.4)
|
83
|
-
mixlib-authentication (1.4.1)
|
84
|
-
mixlib-log
|
85
38
|
mixlib-cli (1.7.0)
|
86
39
|
mixlib-config (2.2.4)
|
87
|
-
mixlib-install (1.1.0)
|
88
|
-
artifactory
|
89
|
-
mixlib-shellout
|
90
|
-
mixlib-versioning
|
91
40
|
mixlib-log (1.7.1)
|
92
41
|
mixlib-shellout (2.2.7)
|
93
42
|
mixlib-versioning (1.1.0)
|
94
43
|
multipart-post (2.0.0)
|
95
|
-
|
96
|
-
|
97
|
-
net-ssh (3.2.0)
|
98
|
-
net-ssh-gateway (1.2.0)
|
99
|
-
net-ssh (>= 2.6.5)
|
100
|
-
nio4r (1.2.1)
|
101
|
-
octokit (3.8.0)
|
102
|
-
sawyer (~> 0.6.0, >= 0.5.3)
|
103
|
-
ohai (8.19.2)
|
104
|
-
chef-config (>= 12.5.0.alpha.1, < 13)
|
44
|
+
ohai (8.24.1)
|
45
|
+
chef-config (>= 12.5.0.alpha.1, < 14)
|
105
46
|
ffi (~> 1.9)
|
106
47
|
ffi-yajl (~> 2.2)
|
107
48
|
ipaddress
|
@@ -112,7 +53,7 @@ GEM
|
|
112
53
|
plist (~> 3.1)
|
113
54
|
systemu (~> 2.6.4)
|
114
55
|
wmi-lite (~> 1.0)
|
115
|
-
omnibus (5.
|
56
|
+
omnibus (5.6.1)
|
116
57
|
aws-sdk (~> 2)
|
117
58
|
chef-sugar (~> 3.3)
|
118
59
|
cleanroom (~> 1.0)
|
@@ -121,63 +62,29 @@ GEM
|
|
121
62
|
mixlib-shellout (~> 2.0)
|
122
63
|
mixlib-versioning
|
123
64
|
ohai (~> 8.0)
|
65
|
+
pedump
|
124
66
|
ruby-progressbar (~> 1.7)
|
125
67
|
thor (~> 0.18)
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
celluloid (~> 0.16.0)
|
135
|
-
celluloid-io (~> 0.16.1)
|
136
|
-
chef-config
|
137
|
-
erubis
|
138
|
-
faraday (~> 0.9.0)
|
139
|
-
hashie (>= 2.0.2, < 4.0.0)
|
140
|
-
httpclient (~> 2.6)
|
141
|
-
json (>= 1.7.7)
|
142
|
-
mixlib-authentication (>= 1.3.0)
|
143
|
-
retryable (~> 2.0)
|
144
|
-
semverse (~> 1.1)
|
145
|
-
varia_model (~> 0.4.0)
|
68
|
+
pedump (0.5.2)
|
69
|
+
awesome_print
|
70
|
+
iostruct (>= 0.0.4)
|
71
|
+
multipart-post (~> 2.0.0)
|
72
|
+
progressbar
|
73
|
+
zhexdump (>= 0.0.2)
|
74
|
+
plist (3.3.0)
|
75
|
+
progressbar (1.8.2)
|
146
76
|
ruby-progressbar (1.8.1)
|
147
|
-
safe_yaml (1.0.4)
|
148
|
-
sawyer (0.6.0)
|
149
|
-
addressable (~> 2.3.5)
|
150
|
-
faraday (~> 0.8, < 0.10)
|
151
|
-
semverse (1.2.1)
|
152
|
-
solve (1.2.1)
|
153
|
-
dep_selector (~> 1.0)
|
154
|
-
semverse (~> 1.1)
|
155
77
|
systemu (2.6.5)
|
156
|
-
test-kitchen (1.12.0)
|
157
|
-
mixlib-install (~> 1.0, >= 1.0.4)
|
158
|
-
mixlib-shellout (>= 1.2, < 3.0)
|
159
|
-
net-scp (~> 1.1)
|
160
|
-
net-ssh (>= 2.9, < 4.0)
|
161
|
-
net-ssh-gateway (~> 1.2.0)
|
162
|
-
safe_yaml (~> 1.0)
|
163
|
-
thor (~> 0.18)
|
164
78
|
thor (0.19.1)
|
165
|
-
timers (4.0.4)
|
166
|
-
hitimes
|
167
|
-
varia_model (0.4.1)
|
168
|
-
buff-extensions (~> 1.0)
|
169
|
-
hashie (>= 2.0.2, < 4.0.0)
|
170
79
|
wmi-lite (1.0.0)
|
80
|
+
zhexdump (0.0.2)
|
171
81
|
|
172
82
|
PLATFORMS
|
173
83
|
ruby
|
174
84
|
|
175
85
|
DEPENDENCIES
|
176
|
-
|
177
|
-
kitchen-vagrant (~> 0.18)
|
178
|
-
omnibus (~> 5.5)
|
86
|
+
omnibus (~> 5.6)
|
179
87
|
omnibus-software!
|
180
|
-
test-kitchen (~> 1.4)
|
181
88
|
|
182
89
|
BUNDLED WITH
|
183
|
-
1.
|
90
|
+
1.15.3
|
@@ -167,14 +167,15 @@ describe Kontena::Cli::Common do
|
|
167
167
|
end
|
168
168
|
|
169
169
|
describe '#use_refresh_token' do
|
170
|
+
let(:token) { double }
|
170
171
|
let(:server) do
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
172
|
+
double(:server,
|
173
|
+
name: 'example',
|
174
|
+
url: 'http://www.example.org',
|
175
|
+
token: token,
|
176
|
+
ssl_cert_path: nil,
|
177
|
+
ssl_subject_cn: nil,
|
178
|
+
)
|
178
179
|
end
|
179
180
|
|
180
181
|
let(:client) do
|
@@ -203,8 +204,7 @@ describe Kontena::Cli::Common do
|
|
203
204
|
end
|
204
205
|
|
205
206
|
it 'creates refresh_token request to given server' do
|
206
|
-
|
207
|
-
expect(Kontena::Client).to receive(:new).with('http://www.example.org', token).and_return(client)
|
207
|
+
expect(Kontena::Client).to receive(:new).with('http://www.example.org', token, ssl_cert_path: nil, ssl_subject_cn: nil).and_return(client)
|
208
208
|
expect(client).to receive(:refresh_token).once
|
209
209
|
subject.use_refresh_token(server)
|
210
210
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'kontena/cli/containers/exec_command'
|
2
|
+
|
3
|
+
describe Kontena::Cli::Containers::ExecCommand do
|
4
|
+
include ClientHelpers
|
5
|
+
|
6
|
+
it 'executes with defaults' do
|
7
|
+
expect(subject).to receive(:container_exec).with('test-grid/host/test-service-1', [ 'test' ],
|
8
|
+
interactive: false,
|
9
|
+
shell: false,
|
10
|
+
tty: false,
|
11
|
+
) { 0 }
|
12
|
+
|
13
|
+
subject.run(['host/test-service-1', 'test'])
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'executes with --shell' do
|
17
|
+
expect(subject).to receive(:container_exec).with('test-grid/host/test-service-1', [ 'echo', '$HOSTNAME' ],
|
18
|
+
interactive: false,
|
19
|
+
shell: true,
|
20
|
+
tty: false,
|
21
|
+
) { 0 }
|
22
|
+
|
23
|
+
subject.run(['--shell', 'host/test-service-1', 'echo', '$HOSTNAME'])
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'executes with -i' do
|
27
|
+
expect(subject).to receive(:container_exec).with('test-grid/host/test-service-1', [ 'test' ],
|
28
|
+
interactive: true,
|
29
|
+
shell: false,
|
30
|
+
tty: false,
|
31
|
+
) { 0 }
|
32
|
+
|
33
|
+
subject.run(['-i', 'host/test-service-1', 'test'])
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'executes with -it' do
|
37
|
+
expect(subject).to receive(:container_exec).with('test-grid/host/test-service-1', [ 'test' ],
|
38
|
+
interactive: true,
|
39
|
+
shell: false,
|
40
|
+
tty: true,
|
41
|
+
) { 0 }
|
42
|
+
|
43
|
+
subject.run(['-it', 'host/test-service-1', 'test'])
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'fails on error' do
|
47
|
+
expect(subject).to receive(:container_exec).with('test-grid/host/test-service-1', [ 'test' ],
|
48
|
+
interactive: false,
|
49
|
+
shell: false,
|
50
|
+
tty: false,
|
51
|
+
) { 1 }
|
52
|
+
|
53
|
+
expect{subject.run(['host/test-service-1', 'test'])}.to exit_with_error
|
54
|
+
end
|
55
|
+
end
|
@@ -1,44 +1,326 @@
|
|
1
1
|
require "kontena/cli/helpers/exec_helper"
|
2
2
|
|
3
3
|
describe Kontena::Cli::Helpers::ExecHelper do
|
4
|
-
|
5
4
|
include ClientHelpers
|
6
5
|
|
6
|
+
let(:master_url) { 'http://master.example.com/' }
|
7
|
+
|
7
8
|
let(:described_class) do
|
8
9
|
Class.new do
|
10
|
+
include Kontena::Cli::Common
|
9
11
|
include Kontena::Cli::Helpers::ExecHelper
|
10
|
-
|
12
|
+
end
|
13
|
+
end
|
14
|
+
subject { described_class.new }
|
15
|
+
let(:default_options) { Kontena::Cli::Helpers::ExecHelper::WEBSOCKET_CLIENT_OPTIONS }
|
16
|
+
|
17
|
+
let(:logger) { instance_double(Logger) }
|
18
|
+
|
19
|
+
before do
|
20
|
+
allow(subject).to receive(:logger).and_return(logger)
|
21
|
+
allow(logger).to receive(:debug)
|
22
|
+
Thread.abort_on_exception = true
|
23
|
+
end
|
24
|
+
|
25
|
+
describe '#read_stdin' do
|
26
|
+
context 'without tty' do
|
27
|
+
it 'yields lines from stdin.gets until eof' do
|
28
|
+
expect($stdin).to receive(:gets).and_return("line 1\n")
|
29
|
+
expect($stdin).to receive(:gets).and_return("line 2\n")
|
30
|
+
expect($stdin).to receive(:gets).and_return(nil)
|
31
|
+
|
32
|
+
expect{|b|subject.read_stdin(&b)}.to yield_successive_args(
|
33
|
+
"line 1\n",
|
34
|
+
"line 2\n"
|
35
|
+
)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'with tty' do
|
40
|
+
let(:stdin_raw) { instance_double(IO) }
|
41
|
+
|
42
|
+
before do
|
43
|
+
allow(STDIN).to receive(:raw) do |&block|
|
44
|
+
block.call(stdin_raw)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'yields from stdin.readpartial in raw mode until raising error' do
|
49
|
+
expect(stdin_raw).to receive(:readpartial).and_return("f")
|
50
|
+
expect(stdin_raw).to receive(:readpartial).and_return("oo")
|
51
|
+
expect(stdin_raw).to receive(:readpartial).and_return("\n")
|
52
|
+
expect(stdin_raw).to receive(:readpartial).and_raise(EOFError)
|
53
|
+
|
54
|
+
expect{|b| subject.read_stdin(tty: true, &b)}.to yield_successive_args(
|
55
|
+
"f",
|
56
|
+
"oo",
|
57
|
+
"\n",
|
58
|
+
).and raise_error(EOFError)
|
11
59
|
end
|
12
60
|
end
|
13
61
|
end
|
14
62
|
|
15
|
-
describe '#
|
16
|
-
|
17
|
-
|
18
|
-
|
63
|
+
describe '#websocket_exec' do
|
64
|
+
let(:websocket_url) { 'ws://master.example.com/v1/containers/test-grid/host-node/service-1/exec' }
|
65
|
+
let(:websocket_headers) { {
|
66
|
+
'Authorization' => 'Bearer 1234567',
|
67
|
+
}}
|
68
|
+
let(:websocket_options) { {
|
69
|
+
headers: websocket_headers,
|
70
|
+
ssl_params: {
|
71
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
72
|
+
ca_file: nil,
|
73
|
+
},
|
74
|
+
ssl_hostname: nil,
|
75
|
+
**default_options
|
76
|
+
}}
|
77
|
+
let(:websocket_client) { instance_double(Kontena::Websocket::Client) }
|
78
|
+
let(:write_thread) { instance_double(Thread) }
|
79
|
+
|
80
|
+
before do
|
81
|
+
allow(Kontena::Websocket::Client).to receive(:connect).with(websocket_url, websocket_options).and_yield(websocket_client)
|
19
82
|
end
|
20
83
|
|
21
|
-
it '
|
22
|
-
expect(
|
23
|
-
expect(
|
84
|
+
it 'connects and reads messages to stdout until exit success' do
|
85
|
+
expect(websocket_client).to receive(:send).with('{"cmd":["test"]}')
|
86
|
+
expect(websocket_client).to receive(:read) do |&block|
|
87
|
+
block.call('{"stream": "stdout", "chunk": "test\n"}')
|
88
|
+
block.call('{"exit": 0}')
|
89
|
+
end
|
90
|
+
|
91
|
+
expect{
|
92
|
+
exit_status = subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test' ])
|
93
|
+
|
94
|
+
expect(exit_status).to eq 0
|
95
|
+
}.to output("test\n").to_stdout
|
24
96
|
end
|
25
97
|
|
26
|
-
|
27
|
-
|
28
|
-
|
98
|
+
it 'connects and reads messages to stderr until exit error' do
|
99
|
+
expect(websocket_client).to receive(:send).with('{"cmd":["test-error"]}')
|
100
|
+
expect(websocket_client).to receive(:read) do |&block|
|
101
|
+
block.call('{"stream": "stderr", "chunk": "error\n"}')
|
102
|
+
block.call('{"exit": 1}')
|
29
103
|
end
|
30
104
|
|
31
|
-
|
32
|
-
|
105
|
+
expect{
|
106
|
+
exit_status = subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-error' ])
|
107
|
+
|
108
|
+
expect(exit_status).to eq 1
|
109
|
+
}.to output("error\n").to_stderr
|
110
|
+
end
|
111
|
+
|
112
|
+
context 'with shell' do
|
113
|
+
let(:websocket_url) { 'ws://master.example.com/v1/containers/test-grid/host-node/service-1/exec?shell=true' }
|
114
|
+
|
115
|
+
it 'connects with the shell query param' do
|
116
|
+
expect(websocket_client).to receive(:send).with('{"cmd":["test-shell"]}')
|
117
|
+
expect(subject).to receive(:websocket_exec_read).and_return(0)
|
118
|
+
|
119
|
+
subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-shell' ], shell: true)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'with https master' do
|
124
|
+
let(:master_url) { 'https://master.example.com/' }
|
125
|
+
let(:ssl_cert_path) { nil }
|
126
|
+
let(:ssl_subject_cn) { nil }
|
127
|
+
let(:master) { double(:master,
|
128
|
+
url: master_url,
|
129
|
+
ssl_cert_path: ssl_cert_path,
|
130
|
+
ssl_subject_cn: ssl_subject_cn,
|
131
|
+
) }
|
132
|
+
|
133
|
+
let(:websocket_url) { 'wss://master.example.com/v1/containers/test-grid/host-node/service-1/exec' }
|
134
|
+
let(:websocket_options) { {
|
135
|
+
headers: websocket_headers,
|
136
|
+
ssl_params: {
|
137
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
138
|
+
ca_file: nil,
|
139
|
+
},
|
140
|
+
ssl_hostname: nil,
|
141
|
+
**default_options
|
142
|
+
} }
|
143
|
+
|
144
|
+
before do
|
145
|
+
allow(subject).to receive(:require_current_master).and_return(master)
|
146
|
+
allow(ENV).to receive(:[]).with('SSL_IGNORE_ERRORS').and_return(nil)
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'verifies SSL by default' do
|
150
|
+
expect(Kontena::Websocket::Client).to receive(:connect).with(websocket_url, websocket_options).and_raise(Kontena::Websocket::SSLVerifyError.new(OpenSSL::X509::V_OK, nil, nil, "..."))
|
151
|
+
|
152
|
+
expect{
|
153
|
+
subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-ssl' ])
|
154
|
+
}.to exit_with_error.and output(/certificate verify failed/).to_stderr
|
33
155
|
end
|
34
156
|
|
35
|
-
|
36
|
-
|
157
|
+
context 'with a kontena cli cert' do
|
158
|
+
let(:ssl_cert_path) { '~/.kontena/certs/test.pem' }
|
159
|
+
let(:ssl_subject_cn) { 'Test' }
|
160
|
+
let(:websocket_options) { {
|
161
|
+
headers: websocket_headers,
|
162
|
+
ssl_params: {
|
163
|
+
verify_mode: OpenSSL::SSL::VERIFY_PEER,
|
164
|
+
ca_file: '~/.kontena/certs/test.pem',
|
165
|
+
},
|
166
|
+
ssl_hostname: 'Test',
|
167
|
+
**default_options
|
168
|
+
} }
|
169
|
+
|
170
|
+
it 'uses the cert' do
|
171
|
+
expect(Kontena::Websocket::Client).to receive(:connect).with(websocket_url, websocket_options).and_raise(Kontena::Websocket::Error, 'testing')
|
172
|
+
|
173
|
+
expect{
|
174
|
+
subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-ssl' ])
|
175
|
+
}.to exit_with_error.and output(/testing/).to_stderr
|
176
|
+
end
|
37
177
|
end
|
38
178
|
|
39
|
-
|
40
|
-
|
179
|
+
context 'with SSL_IGNORE_ERRORS' do
|
180
|
+
let(:websocket_url) { 'wss://master.example.com/v1/containers/test-grid/host-node/service-1/exec' }
|
181
|
+
let(:websocket_options) { {
|
182
|
+
headers: websocket_headers,
|
183
|
+
ssl_params: {
|
184
|
+
verify_mode: OpenSSL::SSL::VERIFY_NONE,
|
185
|
+
ca_file: nil,
|
186
|
+
},
|
187
|
+
ssl_hostname: nil,
|
188
|
+
**default_options
|
189
|
+
} }
|
190
|
+
|
191
|
+
before do
|
192
|
+
allow(ENV).to receive(:[]).with('SSL_IGNORE_ERRORS').and_return('true')
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'connects without ssl verify' do
|
196
|
+
expect(websocket_client).to receive(:send).with('{"cmd":["test-shell"]}')
|
197
|
+
expect(subject).to receive(:websocket_exec_read).and_return(0)
|
198
|
+
|
199
|
+
subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-shell' ])
|
200
|
+
end
|
41
201
|
end
|
42
202
|
end
|
203
|
+
|
204
|
+
context 'with interactive' do
|
205
|
+
let(:websocket_url) { 'ws://master.example.com/v1/containers/test-grid/host-node/service-1/exec?interactive=true' }
|
206
|
+
|
207
|
+
it 'connects and sends messages from stdin' do
|
208
|
+
stdin_eof = false
|
209
|
+
|
210
|
+
expect(STDIN).to receive(:gets).once.and_return "test 1\n"
|
211
|
+
expect(STDIN).to receive(:gets).once.and_return "test 2\n"
|
212
|
+
expect(STDIN).to receive(:gets).once.and_return nil
|
213
|
+
expect(STDIN).to_not receive(:gets)
|
214
|
+
|
215
|
+
expect(websocket_client).to receive(:send).with('{"cmd":["test-interactive"]}')
|
216
|
+
expect(websocket_client).to receive(:send).with('{"stdin":"test 1\n"}')
|
217
|
+
expect(websocket_client).to receive(:send).with('{"stdin":"test 2\n"}')
|
218
|
+
expect(websocket_client).to receive(:send).with('{"stdin":null}') do
|
219
|
+
stdin_eof = true
|
220
|
+
end
|
221
|
+
|
222
|
+
expect(websocket_client).to receive(:read) do |&block|
|
223
|
+
sleep 0.1 until stdin_eof
|
224
|
+
block.call('{"exit": 0}')
|
225
|
+
end
|
226
|
+
|
227
|
+
exit_status = subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-interactive' ], interactive: true)
|
228
|
+
|
229
|
+
expect(exit_status).to eq 0
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
context 'with interactive tty' do
|
234
|
+
let(:websocket_url) { 'ws://master.example.com/v1/containers/test-grid/host-node/service-1/exec?interactive=true&tty=true' }
|
235
|
+
|
236
|
+
it 'connects and sends messages from stdin' do
|
237
|
+
stdin_eol = false
|
238
|
+
|
239
|
+
expect(websocket_client).to receive(:send).once.with('{"cmd":["test-tty"]}')
|
240
|
+
|
241
|
+
expect(subject).to receive(:read_stdin).once.with(tty: true) do |&block|
|
242
|
+
expect(websocket_client).to receive(:send).once.with('{"stdin":"f"}')
|
243
|
+
block.call 'f'
|
244
|
+
|
245
|
+
expect(websocket_client).to receive(:send).once.with('{"stdin":"oo"}')
|
246
|
+
block.call 'oo'
|
247
|
+
|
248
|
+
expect(websocket_client).to receive(:send).once.with('{"stdin":"\n"}')
|
249
|
+
block.call "\n"
|
250
|
+
|
251
|
+
expect(websocket_client).to_not receive(:send)
|
252
|
+
stdin_eol = true
|
253
|
+
sleep
|
254
|
+
end
|
255
|
+
|
256
|
+
expect(websocket_client).to receive(:read) do |&block|
|
257
|
+
sleep 0.1 until stdin_eol
|
258
|
+
block.call('{"stream": "stdout", "chunk": "ok\n"}')
|
259
|
+
block.call('{"exit": 0}')
|
260
|
+
end
|
261
|
+
|
262
|
+
expect{
|
263
|
+
exit_status = subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-tty' ], interactive: true, tty: true)
|
264
|
+
|
265
|
+
expect(exit_status).to eq 0
|
266
|
+
}.to output("ok\n").to_stdout
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context 'with interactive stdin read errors' do
|
271
|
+
let(:websocket_url) { 'ws://master.example.com/v1/containers/test-grid/host-node/service-1/exec?interactive=true' }
|
272
|
+
|
273
|
+
it 'closes websocket and raises from connect block' do
|
274
|
+
stdin_err = false
|
275
|
+
|
276
|
+
expect(websocket_client).to receive(:send).with('{"cmd":["test-close"]}')
|
277
|
+
|
278
|
+
expect(STDIN).to receive(:gets).once.and_return "test\n"
|
279
|
+
expect(websocket_client).to receive(:send).with('{"stdin":"test\n"}')
|
280
|
+
|
281
|
+
expect(STDIN).to receive(:gets).once.and_raise Errno::EIO
|
282
|
+
expect(logger).to receive(:error).with(Errno::EIO)
|
283
|
+
expect(websocket_client).to receive(:close).with(1001, "stdin read Errno::EIO: Input/output error") do
|
284
|
+
stdin_err = true
|
285
|
+
end
|
286
|
+
expect(STDIN).to_not receive(:gets)
|
287
|
+
|
288
|
+
expect(websocket_client).to receive(:read) do |&block|
|
289
|
+
sleep 0.1 until stdin_err
|
290
|
+
end
|
291
|
+
expect(websocket_client).to receive(:close_reason).and_return "stdin read Errno::EIO: Input/output error"
|
292
|
+
expect(logger).to receive(:error)
|
293
|
+
|
294
|
+
expect{
|
295
|
+
subject.websocket_exec('containers/test-grid/host-node/service-1/exec', [ 'test-close' ], interactive: true)
|
296
|
+
}.to raise_error(RuntimeError, "stdin read Errno::EIO: Input/output error")
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
|
301
|
+
describe '#websocket_url' do
|
302
|
+
it 'returns a websocket URL without query params' do
|
303
|
+
expect(subject.websocket_url('containers/test-grid/host-node/service-1')).to eq 'ws://master.example.com/v1/containers/test-grid/host-node/service-1'
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'returns a websocket URL with query params' do
|
307
|
+
expect(subject.websocket_url('containers/test-grid/host-node/service-1', shell: true)).to eq 'ws://master.example.com/v1/containers/test-grid/host-node/service-1?shell=true'
|
308
|
+
end
|
309
|
+
|
310
|
+
context 'without a trailing slash in the master url' do
|
311
|
+
let(:master_url) { 'http://master2.example.com' } # TODO: https
|
312
|
+
|
313
|
+
it 'returns a websocket URL' do
|
314
|
+
expect(subject.websocket_url('containers/test-grid/host-node/service-1', shell: true)).to eq 'ws://master2.example.com/v1/containers/test-grid/host-node/service-1?shell=true'
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
describe '#container_exec' do
|
320
|
+
it 'uses the exec url for a container id' do
|
321
|
+
expect(subject).to receive(:websocket_exec).with('containers/test-grid/host-node/service-1/exec', ['test'], shell: true)
|
322
|
+
|
323
|
+
subject.container_exec('test-grid/host-node/service-1', [ 'test' ], shell: true)
|
324
|
+
end
|
43
325
|
end
|
44
326
|
end
|