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.
@@ -59,16 +59,8 @@ module Kontena
59
59
  require 'kontena/debug_instrumentor'
60
60
  excon_opts[:instrumentor] = Kontena::DebugInstrumentor
61
61
  end
62
-
63
- cert_file = File.join(Dir.home, "/.kontena/certs/#{uri.host}.pem")
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
 
@@ -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
- success = Kontena::Client.new(
168
- Kontena::Cli::Config.instance.current_master.url,
169
- Kontena::Cli::Config.instance.current_master.token
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.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
- # gem 'omnibus-software', github: 'opscode/omnibus-software'
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/chef/omnibus-software.git
3
- revision: fae57713431ef3a325a0662272be85cfc1d157a4
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.5.0)
7
+ omnibus (>= 5.6.1)
8
8
 
9
9
  GEM
10
10
  remote: https://rubygems.org/
11
11
  specs:
12
- addressable (2.3.8)
13
- artifactory (2.3.3)
14
- aws-sdk (2.5.8)
15
- aws-sdk-resources (= 2.5.8)
16
- aws-sdk-core (2.5.8)
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.5.8)
19
- aws-sdk-core (= 2.5.8)
20
- berkshelf (3.3.0)
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.4.0)
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.0)
28
+ ffi-yajl (2.3.1)
68
29
  libyajl2 (~> 1.2)
69
30
  fuzzyurl (0.9.0)
70
- hashie (3.4.4)
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.2)
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
- net-scp (1.2.1)
96
- net-ssh (>= 2.6.5)
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.5.0)
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
- plist (3.2.0)
127
- retryable (2.0.4)
128
- ridley (4.4.2)
129
- addressable
130
- buff-config (~> 1.0)
131
- buff-extensions (~> 1.0)
132
- buff-ignore (~> 1.1)
133
- buff-shell_out (~> 0.1)
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
- berkshelf (~> 3.3)
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.12.5
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
- spy
172
- end
173
-
174
- let(:token) do
175
- token = double
176
- allow(server).to receive(:token).and_return(token)
177
- token
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
- allow(server).to receive(:url).and_return('http://www.example.org')
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
- def initialize(*args)
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 '#ws_url' do
16
- it 'returns an exec url for a container id' do
17
- expect(subject).to receive(:require_current_master).and_return(double(url: 'http://someurl/'))
18
- expect(subject.ws_url('abcd1234')).to eq 'ws://someurl/v1/containers/abcd1234/exec'
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 'also works when the url does not have a trailing slash' do
22
- expect(subject).to receive(:require_current_master).and_return(double(url: 'http://someurl'))
23
- expect(subject.ws_url('abcd1234')).to eq 'ws://someurl/v1/containers/abcd1234/exec'
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
- context 'query params' do
27
- before(:each) do
28
- allow(subject).to receive(:require_current_master).and_return(double(url: 'http://someurl'))
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
- it 'can add the interactive query param' do
32
- expect(subject.ws_url('abcd1234', interactive: true)).to eq 'ws://someurl/v1/containers/abcd1234/exec?interactive=true'
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
- it 'can add the shell query param' do
36
- expect(subject.ws_url('abcd1234', shell: true)).to eq 'ws://someurl/v1/containers/abcd1234/exec?shell=true'
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
- it 'can add both query params' do
40
- expect(subject.ws_url('abcd1234', shell: true, interactive: true)).to eq 'ws://someurl/v1/containers/abcd1234/exec?interactive=true&shell=true'
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