kontena-cli 1.4.3 → 1.5.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/Dockerfile +7 -3
  3. data/Gemfile +7 -3
  4. data/README.md +1 -1
  5. data/VERSION +1 -1
  6. data/bin/kontena +1 -0
  7. data/kontena-cli.gemspec +5 -6
  8. data/lib/kontena/cli/browser_launcher.rb +61 -0
  9. data/lib/kontena/cli/certificate/authorize_command.rb +40 -16
  10. data/lib/kontena/cli/certificate/get_command.rb +1 -1
  11. data/lib/kontena/cli/cloud/login_command.rb +3 -4
  12. data/lib/kontena/cli/cloud/master/add_command.rb +1 -1
  13. data/lib/kontena/cli/cloud/master/list_command.rb +1 -1
  14. data/lib/kontena/cli/cloud/master/remove_command.rb +1 -1
  15. data/lib/kontena/cli/cloud/master/update_command.rb +1 -1
  16. data/lib/kontena/cli/common.rb +2 -2
  17. data/lib/kontena/cli/etcd_command.rb +1 -1
  18. data/lib/kontena/cli/external_registries/add_command.rb +2 -2
  19. data/lib/kontena/cli/external_registries/remove_command.rb +1 -1
  20. data/lib/kontena/cli/grids/common.rb +14 -4
  21. data/lib/kontena/cli/grids/events_command.rb +2 -2
  22. data/lib/kontena/cli/grids/list_command.rb +1 -1
  23. data/lib/kontena/cli/grids/logs_command.rb +1 -1
  24. data/lib/kontena/cli/grids/remove_command.rb +12 -10
  25. data/lib/kontena/cli/grids/trusted_subnets/add_command.rb +1 -1
  26. data/lib/kontena/cli/grids/trusted_subnets/remove_command.rb +12 -10
  27. data/lib/kontena/cli/grids/use_command.rb +1 -1
  28. data/lib/kontena/cli/helpers/log_helper.rb +1 -1
  29. data/lib/kontena/cli/logout_command.rb +1 -1
  30. data/lib/kontena/cli/master/login_command.rb +2 -3
  31. data/lib/kontena/cli/master/logout_command.rb +2 -2
  32. data/lib/kontena/cli/master/token/common.rb +2 -1
  33. data/lib/kontena/cli/master/token/create_command.rb +5 -2
  34. data/lib/kontena/cli/master/token/current_command.rb +9 -4
  35. data/lib/kontena/cli/master/token/list_command.rb +1 -1
  36. data/lib/kontena/cli/master/token/show_command.rb +11 -1
  37. data/lib/kontena/cli/master/user/invite_command.rb +1 -1
  38. data/lib/kontena/cli/master_command.rb +0 -1
  39. data/lib/kontena/cli/nodes/create_command.rb +1 -1
  40. data/lib/kontena/cli/nodes/labels/remove_command.rb +17 -3
  41. data/lib/kontena/cli/nodes/remove_command.rb +12 -10
  42. data/lib/kontena/cli/nodes/reset_token_command.rb +1 -1
  43. data/lib/kontena/cli/nodes/update_command.rb +1 -1
  44. data/lib/kontena/cli/plugin_command.rb +2 -1
  45. data/lib/kontena/cli/plugins/install_command.rb +2 -2
  46. data/lib/kontena/cli/plugins/uninstall_command.rb +19 -10
  47. data/lib/kontena/cli/plugins/upgrade_command.rb +60 -0
  48. data/lib/kontena/cli/registry/create_command.rb +1 -1
  49. data/lib/kontena/cli/registry/remove_command.rb +2 -2
  50. data/lib/kontena/cli/services/containers_command.rb +1 -1
  51. data/lib/kontena/cli/services/create_command.rb +1 -1
  52. data/lib/kontena/cli/services/deploy_command.rb +1 -1
  53. data/lib/kontena/cli/services/envs/add_command.rb +1 -1
  54. data/lib/kontena/cli/services/envs/remove_command.rb +5 -3
  55. data/lib/kontena/cli/services/link_command.rb +1 -1
  56. data/lib/kontena/cli/services/logs_command.rb +1 -1
  57. data/lib/kontena/cli/services/monitor_command.rb +1 -1
  58. data/lib/kontena/cli/services/remove_command.rb +11 -9
  59. data/lib/kontena/cli/services/restart_command.rb +1 -1
  60. data/lib/kontena/cli/services/secrets/link_command.rb +1 -1
  61. data/lib/kontena/cli/services/services_helper.rb +6 -12
  62. data/lib/kontena/cli/services/start_command.rb +5 -3
  63. data/lib/kontena/cli/services/stop_command.rb +5 -3
  64. data/lib/kontena/cli/services/unlink_command.rb +1 -1
  65. data/lib/kontena/cli/services/update_command.rb +1 -1
  66. data/lib/kontena/cli/spinner.rb +10 -10
  67. data/lib/kontena/cli/stack_command.rb +1 -0
  68. data/lib/kontena/cli/stacks/build_command.rb +6 -6
  69. data/lib/kontena/cli/stacks/deploy_command.rb +12 -10
  70. data/lib/kontena/cli/stacks/inspect_command.rb +17 -0
  71. data/lib/kontena/cli/stacks/install_command.rb +15 -4
  72. data/lib/kontena/cli/stacks/list_command.rb +2 -3
  73. data/lib/kontena/cli/stacks/logs_command.rb +1 -1
  74. data/lib/kontena/cli/stacks/monitor_command.rb +2 -2
  75. data/lib/kontena/cli/stacks/remove_command.rb +28 -19
  76. data/lib/kontena/cli/stacks/restart_command.rb +5 -4
  77. data/lib/kontena/cli/stacks/stop_command.rb +6 -5
  78. data/lib/kontena/cli/stacks/upgrade_command.rb +84 -64
  79. data/lib/kontena/cli/stacks/yaml/reader.rb +9 -4
  80. data/lib/kontena/cli/vault/remove_command.rb +7 -5
  81. data/lib/kontena/cli/vault/update_command.rb +1 -1
  82. data/lib/kontena/cli/vault/write_command.rb +1 -1
  83. data/lib/kontena/cli/volumes/remove_command.rb +6 -4
  84. data/lib/kontena/cli/vpn/create_command.rb +1 -1
  85. data/lib/kontena/cli/vpn/remove_command.rb +1 -1
  86. data/lib/kontena/client.rb +23 -14
  87. data/lib/kontena/command.rb +2 -2
  88. data/lib/kontena/debug_instrumentor.rb +11 -2
  89. data/lib/kontena/plugin_manager/common.rb +5 -2
  90. data/lib/kontena/plugin_manager/installer.rb +34 -10
  91. data/lib/kontena/scripts/completer.rb +91 -43
  92. data/lib/kontena/{cli/stacks → stacks}/change_resolver.rb +38 -16
  93. data/lib/kontena/stacks/stack_data.rb +58 -0
  94. data/lib/kontena/stacks/stack_data_set.rb +51 -0
  95. data/lib/kontena_cli.rb +1 -0
  96. data/omnibus/Gemfile.lock +32 -22
  97. data/omnibus/config/projects/kontena.rb +2 -0
  98. data/omnibus/config/software/kontena-cli.rb +6 -4
  99. data/omnibus/package-scripts/kontena/postinstall +1 -1
  100. data/omnibus/wrappers/sh/kontena +1 -1
  101. data/spec/fixtures/kontena_v3_with_registry_extends.yml +20 -0
  102. data/spec/kontena/cli/certificates/authorize_command_spec.rb +81 -0
  103. data/spec/kontena/cli/cloud/login_command_spec.rb +4 -4
  104. data/spec/kontena/cli/common_spec.rb +8 -1
  105. data/spec/kontena/cli/grids/update_command_spec.rb +13 -0
  106. data/spec/kontena/cli/master/join_command_spec.rb +1 -4
  107. data/spec/kontena/cli/master/login_command_spec.rb +4 -4
  108. data/spec/kontena/cli/master/token/create_command_spec.rb +132 -0
  109. data/spec/kontena/cli/master/token/show_command_spec.rb +90 -0
  110. data/spec/kontena/cli/nodes/labels/remove_command_spec.rb +35 -5
  111. data/spec/kontena/cli/stacks/install_command_spec.rb +16 -6
  112. data/spec/kontena/cli/stacks/remove_command_spec.rb +23 -2
  113. data/spec/kontena/cli/stacks/validate_command_spec.rb +1 -1
  114. data/spec/kontena/cli/stacks/yaml/reader_spec.rb +33 -1
  115. data/spec/kontena/client_spec.rb +38 -1
  116. data/spec/kontena/stacks/change_resolver_spec.rb +44 -0
  117. data/spec/kontena/stacks/stack_data_set_spec.rb +59 -0
  118. metadata +36 -34
  119. data/lib/kontena/cli/master/users_command.rb +0 -13
@@ -213,7 +213,11 @@ module Kontena::Cli::Stacks
213
213
  result['dependencies'] = dependencies
214
214
  result['source'] = raw_content
215
215
  result['variables'] = variable_values(without_defaults: true, without_vault: true)
216
- result['parent_name'] = parent_name
216
+ end
217
+ if parent_name
218
+ result['parent'] = { 'name' => parent_name }
219
+ else
220
+ result['parent'] = nil
217
221
  end
218
222
  if service_name.nil?
219
223
  result['services'].each do |service|
@@ -349,8 +353,8 @@ module Kontena::Cli::Stacks
349
353
  @services ||= fully_interpolated_yaml.fetch('services', {})
350
354
  end
351
355
 
352
- def from_external_file(filename, service_name)
353
- external_reader = FileLoader.new(filename, loader).reader
356
+ def from_external_stack(name, service_name)
357
+ external_reader = StackFileLoader.for(name, loader).reader
354
358
  variables.to_a(with_value: true).each do |var|
355
359
  external_reader.variables.build_option(var)
356
360
  end
@@ -445,7 +449,8 @@ module Kontena::Cli::Stacks
445
449
  parent_config = process_config(services[extends])
446
450
  when Hash
447
451
  target = extends['file'] || extends['stack']
448
- parent_config = from_external_file(target, extends['service'])
452
+ raise ("Service '#{extends}' does not define file: or stack: source") if target.nil?
453
+ parent_config = from_external_stack(target, extends['service'])
449
454
  else
450
455
  raise TypeError, "Extends must be a hash or string"
451
456
  end
@@ -3,18 +3,20 @@ module Kontena::Cli::Vault
3
3
  include Kontena::Cli::Common
4
4
  include Kontena::Cli::GridOptions
5
5
 
6
- parameter "NAME", "Secret name"
6
+ parameter "NAME ...", "Secret name", attribute_name: :names
7
7
  option "--force", :flag, "Force remove", default: false, attribute_name: :forced
8
8
  option "--silent", :flag, "Reduce output verbosity"
9
9
 
10
10
  def execute
11
11
  require_api_url
12
12
  require_current_grid
13
- confirm_command(name) unless forced?
13
+ names.each do |name|
14
+ confirm_command(name) unless forced?
14
15
 
15
- token = require_token
16
- vspinner "Removing #{name.colorize(:cyan)} from the vault " do
17
- client(token).delete("secrets/#{current_grid}/#{name}")
16
+ token = require_token
17
+ vspinner "Removing #{pastel.cyan(name)} from the vault " do
18
+ client(token).delete("secrets/#{current_grid}/#{name}")
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -16,7 +16,7 @@ module Kontena::Cli::Vault
16
16
  end
17
17
 
18
18
  def execute
19
- vspinner "Updating #{name.colorize(:cyan)} value in the vault " do
19
+ vspinner "Updating #{pastel.cyan(name)} value in the vault " do
20
20
  client.put("secrets/#{current_grid}/#{name}", {name: name, value: value, upsert: upsert? })
21
21
  end
22
22
  end
@@ -15,7 +15,7 @@ module Kontena::Cli::Vault
15
15
  end
16
16
 
17
17
  def execute
18
- vspinner "Writing #{name.colorize(:cyan)} to the vault " do
18
+ vspinner "Writing #{pastel.cyan(name)} to the vault " do
19
19
  client.post("grids/#{current_grid}/secrets", { name: name, value: value })
20
20
  end
21
21
  end
@@ -6,17 +6,19 @@ module Kontena::Cli::Volumes
6
6
 
7
7
 
8
8
  banner "Removes a volume"
9
- parameter 'VOLUME', 'Volume'
9
+ parameter 'VOLUME ...', 'Volume name', attribute_name: :volumes
10
10
  option "--force", :flag, "Force remove", default: false, attribute_name: :forced
11
11
 
12
12
  requires_current_master
13
13
  requires_current_master_token
14
14
 
15
15
  def execute
16
- confirm_command(volume) unless forced?
16
+ volumes.each do |volume|
17
+ confirm_command(volume) unless forced?
17
18
 
18
- spinner "Removing volume #{pastel.cyan(volume)} " do
19
- remove_volume(volume)
19
+ spinner "Removing volume #{pastel.cyan(volume)} " do
20
+ remove_volume(volume)
21
+ end
20
22
  end
21
23
  end
22
24
 
@@ -53,7 +53,7 @@ module Kontena::Cli::Vpn
53
53
  spinner "Generating #{pastel.cyan(name)} keys (this will take a while) " do
54
54
  wait_for_configuration_to_finish(token)
55
55
  end
56
- puts "#{name.colorize(:cyan)} service is now started (udp://#{vpn_ip}:1194)."
56
+ puts "#{pastel.cyan(name)} service is now started (udp://#{vpn_ip}:1194)."
57
57
  puts "use 'kontena vpn config' to fetch OpenVPN client config to your machine."
58
58
  end
59
59
 
@@ -14,7 +14,7 @@ module Kontena::Cli::Vpn
14
14
  vpn = client(token).get("stacks/#{current_grid}/#{name}") rescue nil
15
15
  exit_with_error("VPN stack does not exist") if vpn.nil?
16
16
 
17
- spinner "Removing #{name.colorize(:cyan)} service " do
17
+ spinner "Removing #{pastel.cyan(name)} service " do
18
18
  client(token).delete("stacks/#{current_grid}/#{name}")
19
19
  end
20
20
  end
@@ -11,6 +11,8 @@ module Kontena
11
11
  X_KONTENA_VERSION = 'X-Kontena-Version'.freeze
12
12
  ACCEPT = 'Accept'.freeze
13
13
  AUTHORIZATION = 'Authorization'.freeze
14
+ ACCEPT_ENCODING = 'Accept-Encoding'.freeze
15
+ GZIP = 'gzip'.freeze
14
16
 
15
17
  attr_accessor :default_headers
16
18
  attr_accessor :path_prefix
@@ -53,7 +55,8 @@ module Kontena
53
55
  connect_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_i : 10,
54
56
  read_timeout: ENV["EXCON_READ_TIMEOUT"] ? ENV["EXCON_READ_TIMEOUT"].to_i : 30,
55
57
  write_timeout: ENV["EXCON_WRITE_TIMEOUT"] ? ENV["EXCON_WRITE_TIMEOUT"].to_i : 10,
56
- ssl_verify_peer: ignore_ssl_errors? ? false : true
58
+ ssl_verify_peer: ignore_ssl_errors? ? false : true,
59
+ middlewares: Excon.defaults[:middlewares] + [Excon::Middleware::Decompress]
57
60
  }
58
61
  if Kontena.debug?
59
62
  require 'kontena/debug_instrumentor'
@@ -248,7 +251,7 @@ module Kontena
248
251
  # @param [Hash,NilClass] params
249
252
  # @param [Hash] headers
250
253
  def get_stream(path, response_block, params = nil, headers = {}, auth = true)
251
- request(path: path, query: params, headers: headers, response_block: response_block, auth: auth)
254
+ request(path: path, query: params, headers: headers, response_block: response_block, auth: auth, gzip: false)
252
255
  end
253
256
 
254
257
  def token_expired?
@@ -279,15 +282,15 @@ module Kontena
279
282
  # @param expects [Array] raises unless response status code matches this list.
280
283
  # @param auth [Boolean] use token authentication default = true
281
284
  # @return [Hash, String] response parsed response object
282
- def request(http_method: :get, path:'/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true)
285
+ def request(http_method: :get, path:'/', body: nil, query: {}, headers: {}, response_block: nil, expects: [200, 201, 204], host: nil, port: nil, auth: true, gzip: true)
283
286
 
284
287
  retried ||= false
285
288
 
286
289
  if auth && token_expired?
287
- raise Excon::Errors::Unauthorized, "Token expired or not valid, you need to login again, use: kontena #{token_is_for_master? ? "master" : "cloud"} login"
290
+ raise Excon::Error::Unauthorized, "Token expired or not valid, you need to login again, use: kontena #{token_is_for_master? ? "master" : "cloud"} login"
288
291
  end
289
292
 
290
- request_headers = request_headers(headers, auth)
293
+ request_headers = request_headers(headers, auth: auth, gzip: gzip)
291
294
 
292
295
  if body.nil?
293
296
  body_content = ''
@@ -325,7 +328,7 @@ module Kontena
325
328
  @last_response = http_client.request(request_options)
326
329
 
327
330
  parse_response(@last_response)
328
- rescue Excon::Errors::Unauthorized
331
+ rescue Excon::Error::Unauthorized
329
332
  if token
330
333
  debug { 'Server reports access token expired' }
331
334
 
@@ -337,7 +340,11 @@ module Kontena
337
340
  retry if refresh_token
338
341
  end
339
342
  raise Kontena::Errors::StandardError.new(401, 'Unauthorized')
340
- rescue Excon::Errors::HTTPStatusError => error
343
+ rescue Excon::Error::HTTPStatus => error
344
+ if error.response.headers['Content-Encoding'] == 'gzip'
345
+ error.response.body = Zlib::GzipReader.new(StringIO.new(error.response.body)).read
346
+ end
347
+
341
348
  debug { "Request #{error.request[:method].upcase} #{error.request[:path]}: #{error.response.status} #{error.response.reason_phrase}: #{error.response.body}" }
342
349
 
343
350
  handle_error_response(error.response)
@@ -447,9 +454,10 @@ module Kontena
447
454
  #
448
455
  # @param [Hash] headers
449
456
  # @return [Hash]
450
- def request_headers(headers = {}, auth = true)
457
+ def request_headers(headers = {}, auth: true, gzip: true)
451
458
  headers = default_headers.merge(headers)
452
459
  headers.merge!(bearer_authorization_header) if auth
460
+ headers[ACCEPT_ENCODING] = GZIP if gzip
453
461
  headers.reject{|_,v| v.nil? || (v.respond_to?(:empty?) && v.empty?)}
454
462
  end
455
463
 
@@ -479,7 +487,7 @@ module Kontena
479
487
  check_version_and_warn(response.headers[X_KONTENA_VERSION])
480
488
 
481
489
  if response.headers[CONTENT_TYPE] =~ JSON_REGEX
482
- parse_json(response.body)
490
+ parse_json(response)
483
491
  else
484
492
  response.body
485
493
  end
@@ -503,13 +511,14 @@ module Kontena
503
511
 
504
512
  # Parse json
505
513
  #
506
- # @param [String] json
514
+ # @param response [Excon::Response]
507
515
  # @return [Hash,Object,NilClass]
508
- def parse_json(json)
509
- JSON.parse(json)
516
+ def parse_json(response)
517
+ return nil if response.body.empty?
518
+
519
+ JSON.parse(response.body)
510
520
  rescue => ex
511
- debug { "JSON parse exception: #{ex.class.name} : #{ex.message}" }
512
- nil
521
+ raise Kontena::Errors::StandardError.new(520, "Invalid response JSON from server for #{response.path}: #{ex.class.name}: #{ex.message}")
513
522
  end
514
523
 
515
524
  # Dump json
@@ -3,7 +3,7 @@ require 'kontena/cli/subcommand_loader'
3
3
  require 'kontena/util'
4
4
  require 'kontena/cli/bytes_helper'
5
5
  require 'kontena/cli/grid_options'
6
- require 'excon/errors'
6
+ require 'excon/error'
7
7
 
8
8
  class Kontena::Command < Clamp::Command
9
9
 
@@ -221,7 +221,7 @@ class Kontena::Command < Clamp::Command
221
221
  run_callbacks :after unless help_requested?
222
222
  exit(@exit_code) if @exit_code.to_i > 0
223
223
  @result
224
- rescue Excon::Errors::SocketError => ex
224
+ rescue Excon::Error::Socket => ex
225
225
  if ex.message.include?('Unable to verify certificate')
226
226
  $stderr.puts " [#{Kontena.pastel.red('error')}] The server uses a certificate signed by an unknown authority."
227
227
  $stderr.puts " You can trust this server by copying server CA pem file to: #{Kontena.pastel.yellow("~/.kontena/certs/<hostname>.pem")}"
@@ -20,7 +20,9 @@ module Kontena
20
20
  str = "Headers: {"
21
21
  heads = []
22
22
  heads << "Accept: #{params[:headers]['Accept']}" if params[:headers]['Accept']
23
+ heads << "Accept-Encoding: #{params[:headers]['Accept-Encoding']}" if params[:headers]['Accept-Encoding']
23
24
  heads << "Content-Type: #{params[:headers]['Content-Type']}" if params[:headers]['Content-Type']
25
+ heads << "Content-Encoding: #{params[:headers]['Content-Encoding']}" if params[:headers]['Content-Encoding']
24
26
  heads << "Authorization: #{params[:headers]['Authorization'].split(' ', 2).first}" if params[:headers]['Authorization']
25
27
  heads << "X-Kontena-Version: #{params[:headers]['X-Kontena-Version']}" if params[:headers]['X-Kontena-Version']
26
28
  str << heads.join(', ')
@@ -39,12 +41,19 @@ module Kontena
39
41
  end
40
42
 
41
43
  if params[:body] && !params[:body].empty?
44
+ if params[:headers]['Content-Encoding'].to_s =~ /gzip/
45
+ body_content = Zlib::GzipReader.new(StringIO.new(params[:body])).read
46
+ body = "(GZIPPED 1:%d) %s" % [body_content.bytesize / params[:body].bytesize, body_content]
47
+ else
48
+ body = params[:body]
49
+ end
50
+
42
51
  str = "Body: "
43
52
  if ENV["DEBUG"] == "api"
44
53
  str << "\n"
45
- str << params[:body]
54
+ str << body
46
55
  else
47
- body = params[:body].inspect.strip
56
+ body = body.inspect.strip
48
57
  str << body[0,80]
49
58
  if body.length > 80
50
59
  str << "...\""
@@ -6,6 +6,8 @@ module Kontena
6
6
  Gem.autoload :DefaultUserInteraction, 'rubygems/user_interaction'
7
7
  Gem.autoload :StreamUI, 'rubygems/user_interaction'
8
8
 
9
+ KONTENA_PLUGIN = 'kontena-plugin-%s'
10
+
9
11
  # @return [Boolean] is the CLI in plugin debugging mode?
10
12
  def plugin_debug?
11
13
  @plugin_debug ||= ENV['DEBUG'] == 'plugin'
@@ -26,8 +28,9 @@ module Kontena
26
28
 
27
29
  # Prefix a plugin name into a gem name (hello to kontena-plugin-hello)
28
30
  def prefix(plugin_name)
29
- return plugin_name if plugin_name.to_s.start_with?('kontena-plugin-')
30
- "kontena-plugin-#{plugin_name}"
31
+ return KONTENA_PLUGIN % nil if plugin_name.nil? || plugin_name.empty?
32
+ return plugin_name if plugin_name.start_with?('kontena-plugin-') || plugin_name.include?('.')
33
+ KONTENA_PLUGIN % plugin_name
31
34
  end
32
35
  module_function :prefix
33
36
 
@@ -7,7 +7,8 @@ module Kontena
7
7
  class Installer
8
8
  include Common
9
9
 
10
- attr_reader :plugin_name, :pre, :version
10
+ attr_reader :plugin_name, :pre
11
+ attr_accessor :version
11
12
 
12
13
  # Create a new instance of plugin installer
13
14
  # @param plugin_name [String]
@@ -19,6 +20,10 @@ module Kontena
19
20
  @version = version
20
21
  end
21
22
 
23
+ def pre?
24
+ !!@pre
25
+ end
26
+
22
27
  def command
23
28
  @command ||= Gem::DependencyInstaller.new(
24
29
  document: false,
@@ -30,23 +35,42 @@ module Kontena
30
35
 
31
36
  # Install a plugin
32
37
  def install
38
+ return install_uri if plugin_name.include?('://')
33
39
  plugin_version = version.nil? ? Gem::Requirement.default : Gem::Requirement.new(version)
34
40
  command.install(prefix(plugin_name), plugin_version)
35
41
  command.installed_gems
36
42
  end
37
43
 
38
- # Upgrade an installed plugin
39
- # @param plugin_name [String]
40
- # @param pre [Boolean] upgrade to a prerelease version if available. Will happen always when the installed version is a prerelease version.
41
- def upgrade
42
- return install if version
44
+ def install_uri
45
+ require 'tempfile'
46
+ require 'open-uri'
47
+ file = Tempfile.new(['kontena_plugin', '.gem'])
48
+ open(plugin_name) do |input|
49
+ file.write input.read
50
+ file.close
51
+ end
52
+ self.class.new(file.path).install
53
+ ensure
54
+ file.unlink
55
+ end
56
+
57
+ def available_upgrade
43
58
  installed = installed(plugin_name)
44
- pre = installed.version.prerelease?
59
+ return false unless installed
45
60
 
46
- raise "Plugin #{plugin_name} not installed" unless installed
47
- latest = rubygems_client.latest_version(prefix(plugin_name), pre: pre)
61
+ pre = installed.version.prerelease?
62
+ latest = rubygems_client.latest_version(prefix(plugin_name), pre: pre? || pre)
48
63
  if latest > installed.version
49
- Installer.new(plugin_name, version: latest.to_s).install
64
+ latest.to_s
65
+ end
66
+ end
67
+
68
+ # Upgrade an installed plugin
69
+ def upgrade
70
+ return install if version
71
+ upgrade_to = available_upgrade
72
+ if upgrade_to
73
+ Installer.new(plugin_name, version: upgrade_to).install
50
74
  end
51
75
  end
52
76
  end
@@ -1,4 +1,5 @@
1
1
  require 'kontena/cli/common'
2
+ require 'kontena/stacks_client'
2
3
 
3
4
  class Helper
4
5
  include Kontena::Cli::Common
@@ -27,12 +28,6 @@ class Helper
27
28
  client_config['current_server']
28
29
  end
29
30
 
30
- def client
31
- $VERSION_WARNING_ADDED=true
32
- token = require_token
33
- super(token)
34
- end
35
-
36
31
  def grids
37
32
  client.get("grids")['grids'].map{|grid| grid['id']}
38
33
  rescue => ex
@@ -57,6 +52,37 @@ class Helper
57
52
  []
58
53
  end
59
54
 
55
+ def stack_registry_usable?
56
+ return false if current_account.nil? || current_account.stacks_url.nil?
57
+ return false if current_account.stacks_read_authentication && current_account.token.nil? || current_account.token.access_token.nil?
58
+ true
59
+ end
60
+
61
+ def stacks_client
62
+ Kontena::StacksClient.new(current_account.stacks_url, current_account.token, read_requires_token: current_account.stacks_read_authentication)
63
+ end
64
+
65
+ def registry_stacks(query = '')
66
+ return [] unless stack_registry_usable?
67
+ results = stacks_client.search(query).map { |s| s['stack'] }
68
+ if results.empty? && !query.empty? # this is here because old stack registry does not return anything for "org/"
69
+ results = stacks_client.search('').map { |s| s['stack'] }.select { |s| s.start_with?(query) }
70
+ end
71
+ results
72
+ rescue => ex
73
+ logger.debug ex
74
+ []
75
+ end
76
+
77
+ def registry_stack_versions(stackname)
78
+ return [] unless stack_registry_usable?
79
+ logger.debug stackname.inspect
80
+ stacks_client.versions(stackname).map { |v| [stackname, v['version']].join(':') }
81
+ rescue => ex
82
+ logger.debug ex
83
+ []
84
+ end
85
+
60
86
  def services
61
87
  services = client.get("grids/#{current_grid}/services")['services']
62
88
  results = []
@@ -99,8 +125,21 @@ class Helper
99
125
  []
100
126
  end
101
127
 
102
- def yml_files
103
- Dir["./*.yml"].map{|file| file.sub('./', '')}
128
+ def directories(word)
129
+ if word && File.directory?(word) && !word.end_with?('/')
130
+ ['%s/' % word]
131
+ else
132
+ Dir[File.join('.', '%s*' % word)].select { |file| File.directory?(file) }.map { |file| '%s/' % file.sub('./', '') }
133
+ end
134
+ end
135
+
136
+ def yml_files(word)
137
+ if word && File.directory?(word) && word.end_with?('/')
138
+ glob = File.join(word, '*.{yml,yaml}')
139
+ else
140
+ glob = File.join('.', '%s*.{yml,yaml}' % word)
141
+ end
142
+ Dir[glob].map { |file| file.sub('./', '') } + directories(word)
104
143
  rescue => ex
105
144
  logger.debug ex
106
145
  []
@@ -139,30 +178,30 @@ helper.logger.debug { "Completing #{words.inspect}" }
139
178
 
140
179
  begin
141
180
  completion = []
142
- completion.push %w(cloud grid app service stack vault certificate node master vpn registry container etcd external-registry whoami plugin version) if words.size < 2
181
+ completion.push %w(cloud grid service stack vault certificate node master vpn registry container etcd external-registry whoami plugin version) if words.size < 2
143
182
  if words.size > 0
144
183
  case words[0]
145
184
  when 'plugin'
146
185
  completion.clear
147
- sub_commands = %w(list ls search install uninstall)
186
+ sub_commands = %w(list search install uninstall)
148
187
  if words[1]
149
- completion.push(sub_commands) unless sub_commands.include?(words[1])
188
+ completion.push(sub_commands) unless (sub_commands + %w(ls)).include?(words[1])
150
189
  else
151
190
  completion.push sub_commands
152
191
  end
153
192
  when 'etcd'
154
193
  completion.clear
155
- sub_commands = %w(get set mkdir mk list ls rm)
194
+ sub_commands = %w(get set mkdir list rm)
156
195
  if words[1]
157
- completion.push(sub_commands) unless sub_commands.include?(words[1])
196
+ completion.push(sub_commands) unless (sub_commands + %w(ls)).include?(words[1])
158
197
  else
159
198
  completion.push sub_commands
160
199
  end
161
200
  when 'registry'
162
201
  completion.clear
163
- sub_commands = %w(create remove rm)
202
+ sub_commands = %w(create remove)
164
203
  if words[1]
165
- completion.push(sub_commands) unless sub_commands.include?(words[1])
204
+ completion.push(sub_commands) unless (sub_commands + %w(rm)).include?(words[1])
166
205
  else
167
206
  completion.push sub_commands
168
207
  end
@@ -171,7 +210,7 @@ begin
171
210
  sub_commands = %w(add-user audit-log create current list user remove show use)
172
211
  if words[1] && words[1] == 'use'
173
212
  completion.push helper.grids.reject { |g| g == helper.current_grid }
174
- elsif words[1] && %w(update show rm remove env cloud-config health).include?(words[1])
213
+ elsif words[1] && %w(update show remove env cloud-config health).include?(words[1])
175
214
  completion.push helper.grids
176
215
  else
177
216
  completion.push sub_commands
@@ -186,7 +225,7 @@ begin
186
225
  end
187
226
  when 'master'
188
227
  completion.clear
189
- sub_commands = %w(list use user current remove rm config cfg login logout token join audit-log init-cloud)
228
+ sub_commands = %w(list use user current remove config login logout token join audit-log init-cloud)
190
229
  if words[1] && words[1] == 'use'
191
230
  completion.push helper.master_names.reject { |n| n == helper.current_master_name }
192
231
  elsif words[1] && %w(remove rm).include?(words[1])
@@ -194,8 +233,8 @@ begin
194
233
  elsif words[1] && words[1] == 'user'
195
234
  users_sub_commands = %w(invite list role)
196
235
  if words[2] == 'role'
197
- role_subcommands = %w(add remove rm)
198
- if !words[3] || !role_subcommands.include?(words[3])
236
+ role_subcommands = %w(add remove)
237
+ if !words[3] || !(role_subcommands + %w(rm)).include?(words[3])
199
238
  completion.push role_subcommands
200
239
  end
201
240
  else
@@ -205,10 +244,10 @@ begin
205
244
  config_sub_commands = %(set get dump load import export unset)
206
245
  completion.push config_sub_commands
207
246
  elsif words[1] && words[1] == 'token'
208
- token_sub_commands = %(list ls rm remove show current create)
247
+ token_sub_commands = %(list remove show current create)
209
248
  completion.push token_sub_commands
210
249
  elsif words[1]
211
- completion.push(sub_commands) unless sub_commands.include?(words[1])
250
+ completion.push(sub_commands) unless (sub_commands + %w(ls rm)).include?(words[1])
212
251
  else
213
252
  completion.push sub_commands
214
253
  end
@@ -216,10 +255,10 @@ begin
216
255
  completion.clear
217
256
  sub_commands = %w(login logout master)
218
257
  if words[1] && words[1] == 'master'
219
- cloud_master_sub_commands = %(list ls remove rm add show update)
258
+ cloud_master_sub_commands = %(list remove add show update)
220
259
  completion.push cloud_master_sub_commands
221
260
  elsif words[1]
222
- completion.push(sub_commands) unless sub_commands.include?(words[1])
261
+ completion.push(sub_commands) unless (sub_commands + %w(ls rm)).include?(words[1])
223
262
  else
224
263
  completion.push sub_commands
225
264
  end
@@ -247,31 +286,40 @@ begin
247
286
  when 'external-registry'
248
287
  completion.clear
249
288
  completion.push %w(add list delete)
250
- when 'app'
251
- completion.clear
252
- sub_commands = %w(init build config deploy start stop remove rm ps list
253
- logs monitor show)
254
- if words[1] && sub_commands.include?(words[1])
255
- completion.push helper.yml_services
256
- else
257
- completion.push sub_commands
258
- end
259
289
  when 'stack'
260
290
  completion.clear
261
- sub_commands = %w(build install upgrade deploy start stop remove rm ls list
262
- logs monitor show registry)
291
+ sub_commands = %w(build install upgrade deploy start stop remove restart list
292
+ logs monitor show registry inspect)
263
293
  if words[1]
264
- if words[1] == 'registry'
265
- registry_sub_commands = %(push pull search show rm)
266
- completion.push registry_sub_commands
267
- elsif %w(install).include?(words[1])
268
- completion.push helper.yml_files
269
- elsif words[1] == 'upgrade' && words[3]
270
- completion.push helper.yml_files
271
- elsif words[1] && sub_commands.include?(words[1])
294
+ if words[1] == 'registry' || words[1] == 'reg'
295
+ registry_sub_commands = %(push pull search show remove)
296
+ if words[2]
297
+ if words[2] == 'push'
298
+ completion.push helper.yml_files(words[3])
299
+ elsif %w(pull search show remove rm).include?(words[2]) && words[4].nil?
300
+ completion.push helper.registry_stacks(words[3].to_s)
301
+ else
302
+ completion.push registry_sub_commands
303
+ end
304
+ else
305
+ completion.push registry_sub_commands
306
+ end
307
+ elsif %w(install validate build).include?(words[1])
308
+ completion.push helper.yml_files(words[2])
309
+ if words[1] == 'install'
310
+ completion.push helper.registry_stacks(words[2].to_s)
311
+ end
312
+ elsif words[1] == 'upgrade'
313
+ if words[3]
314
+ completion.push helper.yml_files(words[4])
315
+ completion.push helper.registry_stacks(words[4].to_s)
316
+ else
317
+ completion.push helper.stacks
318
+ end
319
+ elsif %w(deploy start stop remove rm restart logs monitor show inspect).include?(words[1])
272
320
  completion.push helper.stacks
273
321
  else
274
- completion.push(sub_commands)
322
+ completion.push(sub_commands) unless (sub_commands + %w(rm ls)).include?(words[1])
275
323
  end
276
324
  else
277
325
  completion.push sub_commands