kontena-cli 0.16.0.pre8 → 0.16.0.pre9
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/Gemfile +1 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/lib/kontena/cli/cloud/master/remove_command.rb +0 -1
- data/lib/kontena/cli/services/services_helper.rb +4 -0
- data/lib/kontena/client.rb +28 -21
- data/lib/kontena/scripts/completer +44 -4
- data/lib/kontena/scripts/init +11 -1
- data/spec/kontena/client_spec.rb +86 -1
- data/spec/spec_helper.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fa76bec3b6a5ed5df0158f25414e20867de2a4a9
|
4
|
+
data.tar.gz: 37751c168d7c698026f87cc6399bc8a2d7a81f65
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e58a8e4e643e9ca4abad3630d3f0bb68d6006504f27338e7cca4374c85dc8227a9c66a2606da26fc04092e5a076812ab02121f209126f4d3af71fe4e3e166dfe
|
7
|
+
data.tar.gz: 834031757d11996be1724deffdd172fbc1dd2ef431b17792276a66000bdfcc35e434dbfe5002ca40aa66d38b239c03d5fc074b5d448f3df7b5bdee6da92a1cbc
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -10,7 +10,7 @@ Install it yourself as:
|
|
10
10
|
|
11
11
|
$ gem install kontena-cli
|
12
12
|
|
13
|
-
To enable tab-completion for bash, add this to your `.bashrc` scripts:
|
13
|
+
To enable tab-completion for bash, add this to your `.bashrc` scripts (or `.zshrc` for zsh):
|
14
14
|
|
15
15
|
```
|
16
16
|
which kontena > /dev/null && . "$( kontena whoami --bash-completion-path )"
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.16.0.
|
1
|
+
0.16.0.pre9
|
@@ -192,6 +192,10 @@ module Kontena
|
|
192
192
|
puts " dns: #{container['name']}.#{grid}.kontena.local"
|
193
193
|
puts " ip: #{container['overlay_cidr'].to_s.split('/')[0]}"
|
194
194
|
puts " public ip: #{container['node']['public_ip'] rescue 'unknown'}"
|
195
|
+
if container['health_status']
|
196
|
+
health_time = Time.now - Time.parse(container.dig('health_status', 'updated_at'))
|
197
|
+
puts " health: #{container.dig('health_status', 'status')} (#{health_time.to_i}s ago)"
|
198
|
+
end
|
195
199
|
if container['status'] == 'unknown'
|
196
200
|
puts " status: #{container['status'].colorize(:yellow)}"
|
197
201
|
else
|
data/lib/kontena/client.rb
CHANGED
@@ -5,6 +5,7 @@ require 'base64'
|
|
5
5
|
require 'socket'
|
6
6
|
require 'openssl'
|
7
7
|
require 'uri'
|
8
|
+
require 'time'
|
8
9
|
require_relative 'errors'
|
9
10
|
require_relative 'cli/version'
|
10
11
|
begin
|
@@ -276,7 +277,7 @@ module Kontena
|
|
276
277
|
# @param expects [Array] raises unless response status code matches this list.
|
277
278
|
# @param auth [Boolean] use token authentication default = true
|
278
279
|
# @return [Hash, String] response parsed response object
|
279
|
-
def request(http_method: :get, path:'/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201], host: nil, port: nil, auth: true)
|
280
|
+
def request(http_method: :get, path:'/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true)
|
280
281
|
|
281
282
|
retried ||= false
|
282
283
|
|
@@ -286,11 +287,13 @@ module Kontena
|
|
286
287
|
|
287
288
|
request_headers = request_headers(headers, auth)
|
288
289
|
|
289
|
-
|
290
|
-
|
291
|
-
request_headers.delete(
|
290
|
+
if body.nil?
|
291
|
+
body_content = ''
|
292
|
+
request_headers.delete(CONTENT_TYPE)
|
293
|
+
else
|
294
|
+
body_content = encode_body(body, request_headers[CONTENT_TYPE])
|
295
|
+
request_headers.merge!('Content-Length' => body_content.bytesize)
|
292
296
|
end
|
293
|
-
request_headers.merge!('Content-Length' => body_content.bytesize)
|
294
297
|
|
295
298
|
uri = URI.parse(path)
|
296
299
|
host_options = {}
|
@@ -319,7 +322,7 @@ module Kontena
|
|
319
322
|
# Store the response into client.last_response
|
320
323
|
@last_response = http_client.request(request_options)
|
321
324
|
|
322
|
-
parse_response
|
325
|
+
parse_response(@last_response)
|
323
326
|
rescue Excon::Errors::Unauthorized
|
324
327
|
if token
|
325
328
|
logger.debug 'Server reports access token expired'
|
@@ -332,13 +335,10 @@ module Kontena
|
|
332
335
|
retry if refresh_token
|
333
336
|
end
|
334
337
|
raise Kontena::Errors::StandardError.new(401, 'Unauthorized')
|
335
|
-
rescue Excon::Errors::
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
rescue
|
340
|
-
logger.debug "Request exception: #{$!} - #{$!.message}\n#{$!.backtrace.join("\n")}"
|
341
|
-
handle_error_response
|
338
|
+
rescue Excon::Errors::HTTPStatusError => error
|
339
|
+
logger.debug "Request #{error.request[:method].upcase} #{error.request[:path]}: #{error.response.status} #{error.response.reason_phrase}: #{error.response.body}"
|
340
|
+
|
341
|
+
handle_error_response(error.response)
|
342
342
|
end
|
343
343
|
|
344
344
|
# Build a token refresh request param hash
|
@@ -469,13 +469,13 @@ module Kontena
|
|
469
469
|
# Parse response. If the respons is JSON, returns a Hash representation.
|
470
470
|
# Otherwise returns the raw body.
|
471
471
|
#
|
472
|
-
# @param [
|
472
|
+
# @param [Excon::Response]
|
473
473
|
# @return [Hash,String]
|
474
|
-
def parse_response
|
475
|
-
if
|
476
|
-
parse_json(
|
474
|
+
def parse_response(response)
|
475
|
+
if response.headers[CONTENT_TYPE] =~ JSON_REGEX
|
476
|
+
parse_json(response.body)
|
477
477
|
else
|
478
|
-
|
478
|
+
response.body
|
479
479
|
end
|
480
480
|
end
|
481
481
|
|
@@ -504,9 +504,16 @@ module Kontena
|
|
504
504
|
end
|
505
505
|
|
506
506
|
# @param [Excon::Response] response
|
507
|
-
def handle_error_response
|
508
|
-
|
509
|
-
|
507
|
+
def handle_error_response(response)
|
508
|
+
data = parse_response(response)
|
509
|
+
|
510
|
+
if data.is_a?(Hash) && data.has_key?('error')
|
511
|
+
raise Kontena::Errors::StandardError.new(response.status, data['error'])
|
512
|
+
elsif data.is_a?(String) && !data.empty?
|
513
|
+
raise Kontena::Errors::StandardError.new(response.status, data)
|
514
|
+
else
|
515
|
+
raise Kontena::Errors::StandardError.new(response.status, response.reason_phrase)
|
516
|
+
end
|
510
517
|
end
|
511
518
|
|
512
519
|
# Convert expires_in into expires_at
|
@@ -86,9 +86,33 @@ words = ARGV
|
|
86
86
|
words.delete_at(0)
|
87
87
|
|
88
88
|
completion = []
|
89
|
-
completion.push %w(app
|
89
|
+
completion.push %w(cloud logout grid app service vault certificate node master vpn registry container etcd external-registry whoami plugin version) if words.size < 2
|
90
90
|
if words.size > 0
|
91
91
|
case words[0]
|
92
|
+
when 'plugin'
|
93
|
+
completion.clear
|
94
|
+
sub_commands = %w(list ls search install uninstall)
|
95
|
+
if words[1]
|
96
|
+
completion.push(sub_commands) unless sub_commands.include?(words[1])
|
97
|
+
else
|
98
|
+
completion.push sub_commands
|
99
|
+
end
|
100
|
+
when 'etcd'
|
101
|
+
completion.clear
|
102
|
+
sub_commands = %w(get set mkdir mk list ls rm)
|
103
|
+
if words[1]
|
104
|
+
completion.push(sub_commands) unless sub_commands.include?(words[1])
|
105
|
+
else
|
106
|
+
completion.push sub_commands
|
107
|
+
end
|
108
|
+
when 'registry'
|
109
|
+
completion.clear
|
110
|
+
sub_commands = %w(create remove rm)
|
111
|
+
if words[1]
|
112
|
+
completion.push(sub_commands) unless sub_commands.include?(words[1])
|
113
|
+
else
|
114
|
+
completion.push sub_commands
|
115
|
+
end
|
92
116
|
when 'grid'
|
93
117
|
completion.clear
|
94
118
|
sub_commands = %w(add-user audit-log create current list user remove show use)
|
@@ -109,15 +133,31 @@ if words.size > 0
|
|
109
133
|
end
|
110
134
|
when 'master'
|
111
135
|
completion.clear
|
112
|
-
sub_commands = %w(list use)
|
136
|
+
sub_commands = %w(list use users current remove rm config cfg login logout token join audit-log init-cloud)
|
113
137
|
if words[1] && words[1] == 'use'
|
114
138
|
completion.push helper.master_names
|
115
139
|
elsif words[1] && words[1] == 'users'
|
116
140
|
users_sub_commands = %(invite list role)
|
117
141
|
completion.push users_sub_commands
|
142
|
+
elsif words[1] && ['config', 'cfg'].include?(words[1])
|
143
|
+
config_sub_commands = %(set get dump load import export unset)
|
144
|
+
completion.push config_sub_commands
|
145
|
+
elsif words[1] && words[1] == 'token'
|
146
|
+
token_sub_commands = %(list ls rm remove show current create)
|
147
|
+
completion.push token_sub_commands
|
148
|
+
elsif words[1]
|
149
|
+
completion.push(sub_commands) unless sub_commands.include?(words[1])
|
150
|
+
else
|
151
|
+
completion.push sub_commands
|
152
|
+
end
|
153
|
+
when 'cloud'
|
154
|
+
completion.clear
|
155
|
+
sub_commands = %w(login logout master)
|
156
|
+
if words[1] && words[1] == 'master'
|
157
|
+
cloud_master_sub_commands = %(list ls remove rm add show update)
|
158
|
+
completion.push cloud_master_sub_commands
|
118
159
|
elsif words[1]
|
119
160
|
completion.push(sub_commands) unless sub_commands.include?(words[1])
|
120
|
-
completion.push %w(create)
|
121
161
|
else
|
122
162
|
completion.push sub_commands
|
123
163
|
end
|
@@ -134,7 +174,7 @@ if words.size > 0
|
|
134
174
|
end
|
135
175
|
when 'container'
|
136
176
|
completion.clear
|
137
|
-
sub_commands = %w(exec)
|
177
|
+
sub_commands = %w(exec inspect logs)
|
138
178
|
if words[1]
|
139
179
|
completion.push(sub_commands) unless sub_commands.include?(words[1])
|
140
180
|
completion.push helper.containers
|
data/lib/kontena/scripts/init
CHANGED
@@ -2,10 +2,20 @@
|
|
2
2
|
|
3
3
|
_kontena_complete() {
|
4
4
|
COMPREPLY=()
|
5
|
-
|
5
|
+
if [ "$ZSH_VERSION" == "" ]; then
|
6
|
+
local src="${BASH_SOURCE[0]}"
|
7
|
+
else
|
8
|
+
local src="${(%):-%x}"
|
9
|
+
fi
|
10
|
+
DIR=$( cd "$( dirname "$src" )" && pwd )
|
6
11
|
local word="${COMP_WORDS[COMP_CWORD]}"
|
7
12
|
local completions="$(${DIR}/completer ${COMP_WORDS[*]})"
|
8
13
|
COMPREPLY=( $(compgen -W "$completions" -- "$word") )
|
9
14
|
}
|
10
15
|
|
16
|
+
if [ "$ZSH_VERSION" != "" ]; then
|
17
|
+
autoload -U +X compinit && compinit
|
18
|
+
autoload -U +X bashcompinit && bashcompinit
|
19
|
+
fi
|
20
|
+
|
11
21
|
complete -F _kontena_complete kontena
|
data/spec/kontena/client_spec.rb
CHANGED
@@ -38,7 +38,7 @@ describe Kontena::Client do
|
|
38
38
|
client = Kontena::Client.new('https://localhost/v1/', token)
|
39
39
|
expect(client.token).to eq token
|
40
40
|
end
|
41
|
-
|
41
|
+
|
42
42
|
it 'uses the access token as a bearer token' do
|
43
43
|
client = Kontena::Client.new('https://localhost/v1/', token)
|
44
44
|
expect(client.http_client).to receive(:request) do |opts|
|
@@ -210,4 +210,89 @@ describe Kontena::Client do
|
|
210
210
|
subject.delete('foo', nil, nil, {'Some-Header' => 'value'})
|
211
211
|
end
|
212
212
|
end
|
213
|
+
|
214
|
+
describe '#request' do
|
215
|
+
subject do
|
216
|
+
Kontena::Client.new('http://localhost', master.token)
|
217
|
+
end
|
218
|
+
|
219
|
+
context "for an expected response" do
|
220
|
+
before :each do
|
221
|
+
allow(subject).to receive(:http_client).and_call_original
|
222
|
+
|
223
|
+
WebMock.stub_request(:any, 'http://localhost/v1/test').to_return(
|
224
|
+
status: 200,
|
225
|
+
headers: {
|
226
|
+
'Content-Type' => 'application/json',
|
227
|
+
},
|
228
|
+
body: {'test' => [ "This was a triumph.", "I’m making a note here: HUGE SUCCESS." ]}.to_json,
|
229
|
+
)
|
230
|
+
end
|
231
|
+
|
232
|
+
it "returns the JSON object" do
|
233
|
+
expect(subject.get('test')['test'].join(" / ")).to eq "This was a triumph. / I’m making a note here: HUGE SUCCESS."
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
context "with an empty error response" do
|
238
|
+
before :each do
|
239
|
+
# workaround https://github.com/bblimke/webmock/issues/653
|
240
|
+
expect(http_client).to receive(:request).with(
|
241
|
+
hash_including(path: '/v1/coffee', method: :brew)
|
242
|
+
) {
|
243
|
+
raise Excon::Errors::HTTPStatusError.new("I'm a teapot",
|
244
|
+
{
|
245
|
+
method: 'brew',
|
246
|
+
path: '/v1/coffee',
|
247
|
+
},
|
248
|
+
double(:response,
|
249
|
+
status: 418,
|
250
|
+
reason_phrase: "I'm a teapot",
|
251
|
+
headers: {
|
252
|
+
'Content-Type' => 'short/stout',
|
253
|
+
},
|
254
|
+
body: "",
|
255
|
+
)
|
256
|
+
)
|
257
|
+
}
|
258
|
+
end
|
259
|
+
|
260
|
+
it "raises StandardError with the status phrase" do
|
261
|
+
expect{subject.request(http_method: :brew, path: 'coffee')}.to raise_error(Kontena::Errors::StandardError, "I'm a teapot")
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
context "with an 500 response with text error" do
|
266
|
+
before :each do
|
267
|
+
allow(subject).to receive(:http_client).and_call_original
|
268
|
+
|
269
|
+
WebMock.stub_request(:any, 'http://localhost/v1/print').to_return(
|
270
|
+
status: 500,
|
271
|
+
body: "lp0 (printer) on fire",
|
272
|
+
)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "raises StandardError with the server error message" do
|
276
|
+
expect{subject.post('print', { 'code' => "8A/HyA==" })}.to raise_error(Kontena::Errors::StandardError, "lp0 (printer) on fire")
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
context "with a 422 response with JSON error" do
|
281
|
+
before :each do
|
282
|
+
allow(subject).to receive(:http_client).and_call_original
|
283
|
+
|
284
|
+
WebMock.stub_request(:any, 'http://localhost/v1/test').to_return(
|
285
|
+
status: 422,
|
286
|
+
headers: {
|
287
|
+
'Content-Type' => 'application/json',
|
288
|
+
},
|
289
|
+
body: {'error' => "You are wrong"}.to_json,
|
290
|
+
)
|
291
|
+
end
|
292
|
+
|
293
|
+
it "raises StandardError with the server error message" do
|
294
|
+
expect{subject.get('test')}.to raise_error(Kontena::Errors::StandardError, "You are wrong")
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
213
298
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kontena-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.16.0.
|
4
|
+
version: 0.16.0.pre9
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kontena, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-10-
|
11
|
+
date: 2016-10-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|