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 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