kontena-cli 0.16.0.pre8 → 0.16.0.pre9

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 549a20ff35d02d49d04d5589b5f5186b72de7ed6
4
- data.tar.gz: e18b7450099a72993f8569ce271aaa72c5a3bf84
3
+ metadata.gz: fa76bec3b6a5ed5df0158f25414e20867de2a4a9
4
+ data.tar.gz: 37751c168d7c698026f87cc6399bc8a2d7a81f65
5
5
  SHA512:
6
- metadata.gz: ffe9ff495c0eab3b8e7076b429a24916a6c2f8fd459f2bb6142b50f97e4f52b54fa52342c1b80d117975653c6d39e3ec5f4e7a60c0f9479019fd6cd613a06410
7
- data.tar.gz: aeb28936bece1fe439c6d57d1293cf94a922f8d9bf741e982872f5db44b14a3bcc1e532fc463c8ed9c200100cf32fdfa7fb4fcf737e81c6cf93f177bdb43318c
6
+ metadata.gz: e58a8e4e643e9ca4abad3630d3f0bb68d6006504f27338e7cca4374c85dc8227a9c66a2606da26fc04092e5a076812ab02121f209126f4d3af71fe4e3e166dfe
7
+ data.tar.gz: 834031757d11996be1724deffdd172fbc1dd2ef431b17792276a66000bdfcc35e434dbfe5002ca40aa66d38b239c03d5fc074b5d448f3df7b5bdee6da92a1cbc
data/Gemfile CHANGED
@@ -8,4 +8,5 @@ group :development, :test do
8
8
  gem "kontena-plugin-hello", path: "./examples/kontena-plugin-hello"
9
9
  gem 'pry', require: false
10
10
  gem 'pry-byebug', require: false
11
+ gem 'webmock', require: false
11
12
  end
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.pre8
1
+ 0.16.0.pre9
@@ -66,4 +66,3 @@ module Kontena::Cli::Cloud::Master
66
66
  end
67
67
  end
68
68
  end
69
-
@@ -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
@@ -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
- body_content = body.nil? ? '' : encode_body(body, request_headers[CONTENT_TYPE])
290
- if http_method == :get
291
- request_headers.delete('Content-Type')
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::NotFound
336
- raise Kontena::Errors::StandardError.new(404, 'Not found')
337
- rescue Excon::Errors::Forbidden
338
- raise Kontena::Errors::StandardError.new(403, 'Access denied')
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 [HTTP::Message]
472
+ # @param [Excon::Response]
473
473
  # @return [Hash,String]
474
- def parse_response
475
- if last_response.headers[CONTENT_TYPE] =~ JSON_REGEX
476
- parse_json(last_response.body)
474
+ def parse_response(response)
475
+ if response.headers[CONTENT_TYPE] =~ JSON_REGEX
476
+ parse_json(response.body)
477
477
  else
478
- last_response.body
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
- raise $!, $!.message unless last_response
509
- raise Kontena::Errors::StandardError.new(last_response.status, last_response.body)
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 deploy forgot-password master node grid service container vpn external-registry registry login logout whoami) if words.size < 2
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
@@ -2,10 +2,20 @@
2
2
 
3
3
  _kontena_complete() {
4
4
  COMPREPLY=()
5
- DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )
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
@@ -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
@@ -15,6 +15,7 @@ require 'stringio'
15
15
  require 'clamp'
16
16
  require 'ruby_dig'
17
17
  require 'kontena_cli'
18
+ require 'webmock/rspec'
18
19
 
19
20
  RSpec.configure do |config|
20
21
  config.run_all_when_everything_filtered = true
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.pre8
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-18 00:00:00.000000000 Z
11
+ date: 2016-10-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler