kontena-cli 0.16.3 → 0.17.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (99) hide show
  1. checksums.yaml +4 -4
  2. data/.dockerignore +1 -0
  3. data/.gitignore +3 -1
  4. data/VERSION +1 -1
  5. data/lib/kontena/callbacks/master/deploy/40_install_ssl_certificate_after_deploy.rb +32 -0
  6. data/lib/kontena/cli/apps/deploy_command.rb +2 -2
  7. data/lib/kontena/cli/apps/scale_command.rb +2 -2
  8. data/lib/kontena/cli/apps/show_command.rb +3 -2
  9. data/lib/kontena/cli/apps/yaml/validations.rb +10 -6
  10. data/lib/kontena/cli/apps/yaml/validator.rb +1 -0
  11. data/lib/kontena/cli/apps/yaml/validator_v2.rb +1 -0
  12. data/lib/kontena/cli/cloud/login_command.rb +66 -64
  13. data/lib/kontena/cli/common.rb +0 -10
  14. data/lib/kontena/cli/grids/logs_command.rb +0 -1
  15. data/lib/kontena/cli/localhost_web_server.rb +11 -3
  16. data/lib/kontena/cli/master/login_command.rb +213 -163
  17. data/lib/kontena/cli/nodes/label_command.rb +2 -0
  18. data/lib/kontena/cli/nodes/labels/add_command.rb +7 -8
  19. data/lib/kontena/cli/nodes/labels/list_command.rb +17 -0
  20. data/lib/kontena/cli/nodes/labels/remove_command.rb +7 -12
  21. data/lib/kontena/cli/nodes/show_command.rb +1 -0
  22. data/lib/kontena/cli/plugins/common.rb +8 -0
  23. data/lib/kontena/cli/plugins/install_command.rb +21 -2
  24. data/lib/kontena/cli/plugins/list_command.rb +4 -2
  25. data/lib/kontena/cli/plugins/search_command.rb +4 -2
  26. data/lib/kontena/cli/registry/create_command.rb +19 -12
  27. data/lib/kontena/cli/registry/remove_command.rb +4 -4
  28. data/lib/kontena/cli/registry_command.rb +0 -1
  29. data/lib/kontena/cli/services/create_command.rb +6 -6
  30. data/lib/kontena/cli/services/deploy_command.rb +8 -4
  31. data/lib/kontena/cli/services/list_command.rb +34 -21
  32. data/lib/kontena/cli/services/logs_command.rb +1 -1
  33. data/lib/kontena/cli/services/scale_command.rb +3 -3
  34. data/lib/kontena/cli/services/services_helper.rb +18 -14
  35. data/lib/kontena/cli/services/show_command.rb +1 -0
  36. data/lib/kontena/cli/services/update_command.rb +6 -6
  37. data/lib/kontena/cli/stack_command.rb +12 -6
  38. data/lib/kontena/cli/stacks/build_command.rb +110 -0
  39. data/lib/kontena/cli/stacks/common.rb +85 -20
  40. data/lib/kontena/cli/stacks/deploy_command.rb +30 -7
  41. data/lib/kontena/cli/stacks/install_command.rb +30 -0
  42. data/lib/kontena/cli/stacks/list_command.rb +74 -14
  43. data/lib/kontena/cli/stacks/logs_command.rb +31 -0
  44. data/lib/kontena/cli/stacks/monitor_command.rb +91 -0
  45. data/lib/kontena/cli/stacks/remove_command.rb +24 -7
  46. data/lib/kontena/cli/stacks/service_generator.rb +115 -0
  47. data/lib/kontena/cli/stacks/service_generator_v2.rb +27 -0
  48. data/lib/kontena/cli/stacks/show_command.rb +65 -13
  49. data/lib/kontena/cli/stacks/upgrade_command.rb +28 -0
  50. data/lib/kontena/cli/stacks/yaml/custom_validators/affinities_validator.rb +19 -0
  51. data/lib/kontena/cli/stacks/yaml/custom_validators/build_validator.rb +22 -0
  52. data/lib/kontena/cli/stacks/yaml/custom_validators/extends_validator.rb +21 -0
  53. data/lib/kontena/cli/stacks/yaml/custom_validators/hooks_validator.rb +54 -0
  54. data/lib/kontena/cli/stacks/yaml/custom_validators/secrets_validator.rb +22 -0
  55. data/lib/kontena/cli/stacks/yaml/reader.rb +219 -0
  56. data/lib/kontena/cli/stacks/yaml/service_extender.rb +78 -0
  57. data/lib/kontena/cli/stacks/yaml/validations.rb +71 -0
  58. data/lib/kontena/cli/stacks/yaml/validator_v3.rb +52 -0
  59. data/lib/kontena/cli/version_command.rb +5 -1
  60. data/lib/kontena/cli/vpn/create_command.rb +20 -17
  61. data/lib/kontena/cli/vpn/remove_command.rb +4 -3
  62. data/lib/kontena/client.rb +21 -20
  63. data/lib/kontena/machine/cert_helper.rb +4 -0
  64. data/lib/kontena/machine/cloud_config/cloudinit.yml +1 -1
  65. data/lib/kontena/main_command.rb +1 -1
  66. data/spec/fixtures/kontena-build.yml +2 -2
  67. data/spec/fixtures/kontena-invalid.yml +1 -1
  68. data/spec/fixtures/kontena-not-hash-service-config.yml +1 -1
  69. data/spec/fixtures/kontena-with-env-file.yml +2 -2
  70. data/spec/fixtures/kontena_build_v3.yml +23 -0
  71. data/spec/fixtures/kontena_v3.yml +20 -0
  72. data/spec/fixtures/stack-internal-extend.yml +11 -0
  73. data/spec/fixtures/stack-with-env-file.yml +21 -0
  74. data/spec/fixtures/stack-with-variables.yml +22 -0
  75. data/spec/kontena/cli/app/scale_spec.rb +3 -1
  76. data/spec/kontena/cli/cloud/login_command_spec.rb +283 -0
  77. data/spec/kontena/cli/master/login_command_spec.rb +324 -145
  78. data/spec/kontena/cli/services/link_command_spec.rb +1 -1
  79. data/spec/kontena/cli/services/secrets/link_command_spec.rb +4 -4
  80. data/spec/kontena/cli/services/secrets/unlink_command_spec.rb +2 -2
  81. data/spec/kontena/cli/services/services_helper_spec.rb +15 -11
  82. data/spec/kontena/cli/services/unlink_command_spec.rb +1 -1
  83. data/spec/kontena/cli/stacks/deploy_command_spec.rb +26 -0
  84. data/spec/kontena/cli/stacks/install_command_spec.rb +54 -0
  85. data/spec/kontena/cli/stacks/list_command_spec.rb +27 -0
  86. data/spec/kontena/cli/stacks/remove_command_spec.rb +45 -0
  87. data/spec/kontena/cli/stacks/service_generator_spec.rb +385 -0
  88. data/spec/kontena/cli/stacks/service_generator_v2_spec.rb +74 -0
  89. data/spec/kontena/cli/stacks/show_command_spec.rb +26 -0
  90. data/spec/kontena/cli/stacks/upgrade_command_spec.rb +50 -0
  91. data/spec/kontena/cli/stacks/yaml/reader_spec.rb +370 -0
  92. data/spec/kontena/cli/stacks/yaml/service_extender_spec.rb +128 -0
  93. data/spec/kontena/cli/stacks/yaml/validator_v3_spec.rb +302 -0
  94. data/spec/spec_helper.rb +6 -4
  95. data/spec/support/client_helpers.rb +1 -0
  96. metadata +57 -7
  97. data/lib/kontena/cli/registry/delete_command.rb +0 -18
  98. data/lib/kontena/cli/stacks/create_command.rb +0 -27
  99. data/lib/kontena/cli/stacks/update_command.rb +0 -27
@@ -0,0 +1,31 @@
1
+ module Kontena::Cli::Stacks
2
+ class LogsCommand < Clamp::Command
3
+ include Kontena::Cli::Common
4
+ include Kontena::Cli::GridOptions
5
+ include Kontena::Cli::Helpers::LogHelper
6
+
7
+ parameter "NAME", "Stack name"
8
+ option ["-t", "--tail"], :flag, "Tail (follow) logs", default: false
9
+ option ["-l", "--lines"], "LINES", "How many lines to show", default: '100'
10
+ option "--since", "SINCE", "Show logs since given timestamp"
11
+
12
+ def execute
13
+ require_api_url
14
+ token = require_token
15
+
16
+ query_params = {}
17
+ query_params[:limit] = lines if lines
18
+ query_params[:since] = since if since
19
+
20
+ show_logs("stacks/#{current_grid}/#{name}/container_logs", query_params) do |log|
21
+ show_log(log)
22
+ end
23
+ end
24
+
25
+ def show_log(log)
26
+ color = color_for_container(log['name'])
27
+ prefix = "#{log['created_at']} [#{log['name']}]:".colorize(color)
28
+ puts "#{prefix} #{log['data']}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,91 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Stacks
4
+ class MonitorCommand < Clamp::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ parameter "NAME", "Stack name"
10
+
11
+ attr_reader :services
12
+
13
+ def execute
14
+ require_api_url
15
+ token = require_token
16
+
17
+ response = client(token).get("grids/#{current_grid}/services?stack=#{name}")
18
+ show_monitor(response['services'])
19
+
20
+ @services = services_from_yaml(filename, service_list, service_prefix)
21
+ if services.size > 0
22
+ show_monitor(services)
23
+ elsif !service_list.empty?
24
+ puts "No such service: #{service_list.join(', ')}".colorize(:red)
25
+ end
26
+ end
27
+
28
+ def show_monitor(services)
29
+ loop do
30
+ nodes = {}
31
+ services.each do |service|
32
+ result = client(token).get("services/#{service['id']}/containers") rescue nil
33
+ if result
34
+ service['instances'] = result['containers'].size
35
+ result['containers'].each do |container|
36
+ container['service'] = service['name']
37
+ nodes[container['node']['name']] ||= []
38
+ nodes[container['node']['name']] << container
39
+ end
40
+ end
41
+ end
42
+ clear_terminal
43
+ puts "grid: #{current_grid}"
44
+ puts "stack: #{name}"
45
+ puts "services:"
46
+ services.each do |service|
47
+ color = color_for_service(service['name'])
48
+ puts " #{"■".colorize(color)} #{service['name']} (#{service['instances']} instances)"
49
+ end
50
+ puts "nodes:"
51
+ node_names = nodes.keys.sort
52
+ node_names.each do |name|
53
+ containers = nodes[name]
54
+ puts " #{name} (#{containers.size} instances)"
55
+ print " "
56
+ containers.each do |container|
57
+ icon = "■"
58
+ if container['status'] != 'running'
59
+ icon = "□"
60
+ end
61
+ color = color_for_service(container['service'])
62
+ print icon.colorize(color)
63
+ end
64
+ puts ''
65
+ end
66
+ sleep 1
67
+ end
68
+ end
69
+
70
+ def color_for_service(service)
71
+ color_maps[service] = colors.shift unless color_maps[service]
72
+ color_maps[service].to_sym
73
+ end
74
+
75
+ def color_maps
76
+ @color_maps ||= {}
77
+ end
78
+
79
+ def colors
80
+ if(@colors.nil? || @colors.size == 0)
81
+ @colors = [:green, :magenta, :yellow, :cyan, :red,
82
+ :light_green, :light_yellow, :ligh_magenta, :light_cyan, :light_red]
83
+ end
84
+ @colors
85
+ end
86
+
87
+ def clear_terminal
88
+ print "\e[H\e[2J"
89
+ end
90
+ end
91
+ end
@@ -6,21 +6,38 @@ module Kontena::Cli::Stacks
6
6
  include Kontena::Cli::GridOptions
7
7
  include Common
8
8
 
9
- parameter "NAME", "Service name"
9
+ parameter "NAME", "Stack name"
10
+ option "--force", :flag, "Force remove", default: false, attribute_name: :forced
10
11
 
11
12
  def execute
12
13
  require_api_url
13
- require_token
14
+ token = require_token
14
15
 
15
- remove_stack(name)
16
+ confirm_command(name) unless forced?
17
+ spinner "Removing stack #{pastel.cyan(name)} " do
18
+ remove_stack(token, name)
19
+ wait_stack_removal(token, name)
20
+ end
16
21
  end
17
22
 
18
- private
19
-
20
-
21
- def remove_stack(name)
23
+ def remove_stack(token, name)
22
24
  client(token).delete("stacks/#{current_grid}/#{name}")
23
25
  end
24
26
 
27
+ def wait_stack_removal(token, name)
28
+ removed = false
29
+ until removed == true
30
+ begin
31
+ client(token).get("stacks/#{current_grid}/#{name}")
32
+ sleep 1
33
+ rescue Kontena::Errors::StandardError => exc
34
+ if exc.status == 404
35
+ removed = true
36
+ else
37
+ raise exc
38
+ end
39
+ end
40
+ end
41
+ end
25
42
  end
26
43
  end
@@ -0,0 +1,115 @@
1
+ require 'yaml'
2
+ require 'shellwords'
3
+ require_relative '../services/services_helper'
4
+
5
+ module Kontena::Cli::Stacks
6
+ class ServiceGenerator
7
+ include Kontena::Cli::Services::ServicesHelper
8
+
9
+ attr_reader :service_config
10
+
11
+ def initialize(service_config)
12
+ @service_config = service_config
13
+ end
14
+
15
+ ##
16
+ # @return [Hash]
17
+ def generate
18
+ parse_data(service_config)
19
+ end
20
+
21
+ private
22
+
23
+ ##
24
+ # @param [Hash] options
25
+ # @return [Hash]
26
+ def parse_data(options)
27
+ data = {}
28
+ data['container_count'] = options['instances']
29
+ data['image'] = parse_image(options['image'])
30
+ data['env'] = options['environment'] if options['environment']
31
+ data['container_count'] = options['instances']
32
+ data['links'] = parse_links(options['links'] || [])
33
+ data['external_links'] = parse_links(options['external_links'] || [])
34
+ data['ports'] = parse_stringified_ports(options['ports'] || [])
35
+ data['memory'] = parse_memory(options['mem_limit'].to_s) if options['mem_limit']
36
+ data['memory_swap'] = parse_memory(options['memswap_limit'].to_s) if options['memswap_limit']
37
+ data['cpu_shares'] = options['cpu_shares'] if options['cpu_shares']
38
+ data['volumes'] = options['volumes'] || []
39
+ data['volumes_from'] = options['volumes_from'] || []
40
+ data['cmd'] = Shellwords.split(options['command']) if options['command']
41
+ data['affinity'] = options['affinity'] || []
42
+ data['user'] = options['user'] if options['user']
43
+ data['stateful'] = options['stateful'] == true
44
+ data['privileged'] = options['privileged'] unless options['privileged'].nil?
45
+ data['cap_add'] = options['cap_add'] if options['cap_add']
46
+ data['cap_drop'] = options['cap_drop'] if options['cap_drop']
47
+ data['net'] = options['net'] if options['net']
48
+ data['pid'] = options['pid'] if options['pid']
49
+ data['log_driver'] = options['log_driver'] if options['log_driver']
50
+ data['log_opts'] = options['log_opt'] if options['log_opt'] && !options['log_opt'].empty?
51
+ deploy_opts = options['deploy'] || {}
52
+ data['strategy'] = deploy_opts['strategy'] if deploy_opts['strategy']
53
+ deploy = {}
54
+ deploy['wait_for_port'] = deploy_opts['wait_for_port'] if deploy_opts.has_key?('wait_for_port')
55
+ deploy['min_health'] = deploy_opts['min_health'] if deploy_opts.has_key?('min_health')
56
+ deploy['interval'] = parse_relative_time(deploy_opts['interval']) if deploy_opts.has_key?('interval')
57
+ unless deploy.empty?
58
+ data['deploy_opts'] = deploy
59
+ end
60
+ data['hooks'] = options['hooks'] || {}
61
+ data['secrets'] = options['secrets'] if options['secrets']
62
+ data['build'] = parse_build_options(options) if options['build']
63
+ health_check = {}
64
+ health_opts = options['health_check'] || {}
65
+ health_check['protocol'] = health_opts['protocol'] if health_opts.has_key?('protocol')
66
+ health_check['uri'] = health_opts['uri'] if health_opts.has_key?('uri')
67
+ health_check['port'] = health_opts['port'] if health_opts.has_key?('port')
68
+ health_check['timeout'] = health_opts['timeout'] if health_opts.has_key?('timeout')
69
+ health_check['interval'] = health_opts['interval'] if health_opts.has_key?('interval')
70
+ health_check['initial_delay'] = health_opts['initial_delay'] if health_opts.has_key?('initial_delay')
71
+ unless health_check.empty?
72
+ data['health_check'] = health_check
73
+ end
74
+ data
75
+ end
76
+
77
+ # @param [Array<String>] port_options
78
+ # @return [Array<Hash>]
79
+ def parse_stringified_ports(port_options)
80
+ parse_ports(port_options).map {|p|
81
+ {
82
+ 'ip' => p[:ip],
83
+ 'container_port' => p[:container_port],
84
+ 'node_port' => p[:node_port],
85
+ 'protocol' => p[:protocol]
86
+ }
87
+ }
88
+ end
89
+
90
+ # @param [Array<String>] link_options
91
+ # @return [Array<Hash>]
92
+ def parse_links(link_options)
93
+ link_options.map{|l|
94
+ service_name, alias_name = l.split(':')
95
+ if service_name.nil?
96
+ raise ArgumentError.new("Invalid link value #{l}")
97
+ end
98
+ alias_name = service_name if alias_name.nil?
99
+ {
100
+ 'name' => service_name,
101
+ 'alias' => alias_name
102
+ }
103
+ }
104
+ end
105
+
106
+ # @param [Hash] options
107
+ # @return [Hash]
108
+ def parse_build_options(options)
109
+ build = {}
110
+ build['context'] = options['build'] if options['build']
111
+ build['dockerfile'] = options['dockerfile'] if options['dockerfile']
112
+ build
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,27 @@
1
+ require 'yaml'
2
+ require_relative 'service_generator'
3
+
4
+ module Kontena::Cli::Stacks
5
+ class ServiceGeneratorV2 < ServiceGenerator
6
+
7
+ def parse_data(options)
8
+ data = super(options)
9
+ data['net'] = options['network_mode'] if options['network_mode']
10
+ data['log_driver'] = options.dig('logging', 'driver')
11
+ data['log_opts'] = options.dig('logging', 'options')
12
+ if options['depends_on']
13
+ data['links'] ||= []
14
+ data['links'] = (data['links'] + parse_links(options['depends_on'])).uniq
15
+ end
16
+ data
17
+ end
18
+
19
+ def parse_build_options(options)
20
+ unless options['build'].is_a?(Hash)
21
+ options['build'] = { 'context' => options['build']}
22
+ end
23
+ options['build']['args'] = parse_build_args(options['build']['args']) if options['build']['args']
24
+ options['build']
25
+ end
26
+ end
27
+ end
@@ -6,33 +6,85 @@ module Kontena::Cli::Stacks
6
6
  include Kontena::Cli::GridOptions
7
7
  include Common
8
8
 
9
- parameter "NAME", "Service name"
9
+ parameter "NAME", "Stack name"
10
10
 
11
11
  def execute
12
12
  require_api_url
13
- require_token
14
-
15
- show_stack(name)
16
-
17
- end
13
+ token = require_token
18
14
 
19
- private
15
+ show_stack(token, name)
16
+ end
20
17
 
21
- def show_stack(name)
18
+ def show_stack(token, name)
22
19
  stack = client(token).get("stacks/#{current_grid}/#{name}")
23
20
 
24
- puts stack
25
-
26
- puts "#{stack['id']}:"
21
+ puts "#{stack['name']}:"
27
22
  puts " state: #{stack['state']}"
28
23
  puts " created_at: #{stack['created_at']}"
29
24
  puts " updated_at: #{stack['updated_at']}"
30
25
  puts " version: #{stack['version']}"
26
+ puts " expose: #{stack['expose'] || '-'}"
31
27
  puts " services:"
32
- stack['grid_services'].each do |service|
33
- puts " id: #{service['id']}"
28
+ stack['services'].each do |service|
29
+ show_service(token, service['id'])
34
30
  end
35
31
  end
36
32
 
33
+ # @param [String] token
34
+ # @param [String] service_id
35
+ def show_service(token, service_id)
36
+ service = get_service(token, service_id)
37
+ pad = ' '.freeze
38
+ puts "#{pad}#{service['name']}:"
39
+ puts "#{pad} image: #{service['image']}"
40
+ puts "#{pad} status: #{service['state'] }"
41
+ if service['health_status']
42
+ puts "#{pad} health_status:"
43
+ puts "#{pad} healthy: #{service['health_status']['healthy']}"
44
+ puts "#{pad} total: #{service['health_status']['total']}"
45
+ end
46
+ puts "#{pad} revision: #{service['revision']}"
47
+ puts "#{pad} stateful: #{service['stateful'] == true ? 'yes' : 'no' }"
48
+ puts "#{pad} scaling: #{service['container_count'] }"
49
+ puts "#{pad} strategy: #{service['strategy']}"
50
+ puts "#{pad} deploy_opts:"
51
+ puts "#{pad} min_health: #{service['deploy_opts']['min_health']}"
52
+ if service['deploy_opts']['wait_for_port']
53
+ puts "#{pad} wait_for_port: #{service['deploy_opts']['wait_for_port']}"
54
+ end
55
+ if service['deploy_opts']['interval']
56
+ puts "#{pad} interval: #{service['deploy_opts']['interval']}"
57
+ end
58
+ puts "#{pad} dns: #{service['dns']}"
59
+
60
+ if service['affinity'].to_a.size > 0
61
+ puts "#{pad} affinity: "
62
+ service['affinity'].to_a.each do |a|
63
+ puts "#{pad} - #{a}"
64
+ end
65
+ end
66
+
67
+ unless service['cmd'].to_s.empty?
68
+ if service['cmd']
69
+ puts "#{pad} cmd: #{service['cmd'].join(' ')}"
70
+ else
71
+ puts "#{pad} cmd: "
72
+ end
73
+ end
74
+
75
+ if service['ports'].to_a.size > 0
76
+ puts "#{pad} ports:"
77
+ service['ports'].to_a.each do |p|
78
+ puts "#{pad} - #{p['node_port']}:#{p['container_port']}/#{p['protocol']}"
79
+ end
80
+ end
81
+
82
+ if service['links'].to_a.size > 0
83
+ puts "#{pad} links: "
84
+ service['links'].to_a.each do |l|
85
+ puts "#{pad} - #{l['alias']} => #{l['id']}"
86
+ end
87
+ end
88
+ end
37
89
  end
38
90
  end
@@ -0,0 +1,28 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Stacks
4
+ class UpgradeCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ parameter "NAME", "Stack name"
10
+ parameter "FILE", "Kontena stack file"
11
+ option '--deploy', :flag, 'Deploy after upgrade'
12
+
13
+ def execute
14
+ require_api_url
15
+ token = require_token
16
+ require_config_file(file)
17
+ stack = stack_from_yaml(file)
18
+ spinner "Upgrading stack #{pastel.cyan(name)} " do
19
+ update_stack(token, stack)
20
+ end
21
+ Kontena.run("stack deploy #{name}")
22
+ end
23
+
24
+ def update_stack(token, stack)
25
+ client(token).put("stacks/#{current_grid}/#{name}", stack)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ module Kontena::Cli::Stacks::YAML::Validations::CustomValidators
2
+ class AffinitiesValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('stacks_valid_affinities')
5
+ end
6
+
7
+ def validate(key, value, validations, errors)
8
+ unless value.is_a?(Array)
9
+ errors[key] = 'affinity must be array'
10
+ return
11
+ end
12
+
13
+ invalid_formats = value.find_all { |a| !a.match(/(?<=\!|\=)=/) }
14
+ if invalid_formats.count > 0
15
+ errors[key] = "affinity contains invalid formats: #{invalid_formats.join(', ')}"
16
+ end
17
+ end
18
+ end
19
+ end