kontena-cli 1.4.3 → 1.5.0.pre1

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