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
@@ -13,6 +13,7 @@ module Kontena::Cli::Services
13
13
  token = require_token
14
14
 
15
15
  show_service(token, name)
16
+ show_service_instances(token, name)
16
17
  end
17
18
  end
18
19
  end
@@ -32,12 +32,12 @@ module Kontena::Cli::Services
32
32
  option "--deploy-interval", "TIME", "Auto-deploy with given interval (format: <number><unit>, where unit = min, h, d)"
33
33
  option "--pid", "PID", "Pid namespace to use"
34
34
  option "--secret", "SECRET", "Import secret from Vault (format: <secret>:<name>:<type>)", multivalued: true
35
- option "--health-check-uri", "HEALTH CHECK URI", "URI path for HTTP health check"
36
- option "--health-check-timeout", "HEALTH CHECK TIMEOUT", "Timeout for HTTP health check"
37
- option "--health-check-interval", "HEALTH CHECK INTERVAL", "Interval for HTTP health check"
38
- option "--health-check-initial-delay", "HEALTH CHECK INITIAL DELAY", "Initial for HTTP health check"
39
- option "--health-check-port", "HEALTH CHECK PORT", "Port for HTTP health check"
40
- option "--health-check-protocol", "HEALTH CHECK PROTOCOL", "Protocol of health check"
35
+ option "--health-check-uri", "URI", "URI path for HTTP health check"
36
+ option "--health-check-timeout", "TIMEOUT", "Timeout for HTTP health check"
37
+ option "--health-check-interval", "INTERVAL", "Interval for HTTP health check"
38
+ option "--health-check-initial-delay", "DELAY", "Initial for HTTP health check"
39
+ option "--health-check-port", "PORT", "Port for HTTP health check"
40
+ option "--health-check-protocol", "PROTOCOL", "Protocol of health check"
41
41
 
42
42
  def execute
43
43
  require_api_url
@@ -1,19 +1,25 @@
1
- require_relative 'stacks/create_command'
1
+ require_relative 'stacks/install_command'
2
2
  require_relative 'stacks/remove_command'
3
3
  require_relative 'stacks/deploy_command'
4
- require_relative 'stacks/update_command'
4
+ require_relative 'stacks/upgrade_command'
5
5
  require_relative 'stacks/list_command'
6
6
  require_relative 'stacks/show_command'
7
+ require_relative 'stacks/build_command'
8
+ require_relative 'stacks/monitor_command'
9
+ require_relative 'stacks/logs_command'
7
10
 
8
11
  class Kontena::Cli::StackCommand < Kontena::Command
9
12
 
10
- subcommand "create", "Create stack", Kontena::Cli::Stacks::CreateCommand
13
+ subcommand "install", "Install a stack", Kontena::Cli::Stacks::InstallCommand
14
+ subcommand "build", "Build stack file images", Kontena::Cli::Stacks::BuildCommand
11
15
  subcommand ["ls", "list"], "List stacks", Kontena::Cli::Stacks::ListCommand
12
16
  subcommand "show", "Show stack details", Kontena::Cli::Stacks::ShowCommand
13
- subcommand "update", "Update stack", Kontena::Cli::Stacks::UpdateCommand
14
- subcommand "deploy", "Deploy Kontena stack", Kontena::Cli::Stacks::DeployCommand
17
+ subcommand "upgrade", "Upgrade installed stack", Kontena::Cli::Stacks::UpgradeCommand
18
+ subcommand "deploy", "Deploy stack", Kontena::Cli::Stacks::DeployCommand
19
+ subcommand "logs", "Show stack logs from stack services", Kontena::Cli::Stacks::LogsCommand
20
+ subcommand "monitor", "Monitor stack", Kontena::Cli::Stacks::MonitorCommand
15
21
  subcommand ["remove","rm"], "Remove stack", Kontena::Cli::Stacks::RemoveCommand
16
-
22
+
17
23
  def execute
18
24
 
19
25
  end
@@ -0,0 +1,110 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Stacks
4
+ class BuildCommand < Clamp::Command
5
+ include Kontena::Cli::Common
6
+ include Common
7
+
8
+ option ['-f', '--file'], 'FILE', 'Specify an alternate Kontena compose file', attribute_name: :filename, default: 'kontena.yml'
9
+ option ['--no-cache'], :flag, 'Do not use cache when building the image', default: false
10
+ option ['--no-push'], :flag, 'Do not push images to registry', default: false
11
+ option ['--no-pull'], :flag, 'Do not attempt to pull a newer version of the image', default: false
12
+ parameter "[SERVICE] ...", "Services to build"
13
+
14
+ def execute
15
+ require_config_file(filename)
16
+ services = services_from_yaml(filename, service_list, service_prefix)
17
+ if services.none?{ |name, service| service['build'] }
18
+ abort 'Not found any service with a build option'.colorize(:red)
19
+ end
20
+ build_docker_images(services, no_cache?, no_pull?)
21
+ push_docker_images(services) unless no_push?
22
+ end
23
+
24
+ # @param [Hash] services
25
+ # @param [Boolean] no_cache
26
+ # @param [Boolean] no_pull
27
+ def build_docker_images(services, no_cache = false, no_pull = false)
28
+ services.each do |name, service|
29
+ if service['build']
30
+ dockerfile = service['build']['dockerfile'] || 'Dockerfile'
31
+ abort("'#{service['image']}' is not valid Docker image name") unless validate_image_name(service['image'])
32
+ abort("'#{service['build']['context']}' does not have #{dockerfile}") unless dockerfile_exist?(service['build']['context'], dockerfile)
33
+ if service['hooks'] && service['hooks']['pre_build']
34
+ puts "Running pre_build hook".colorize(:cyan)
35
+ run_pre_build_hook(service['hooks']['pre_build'])
36
+ end
37
+ puts "Building image #{service['image'].colorize(:cyan)}"
38
+ build_docker_image(service, no_cache, no_pull)
39
+ end
40
+ end
41
+ end
42
+
43
+ # @param [Hash] services
44
+ def push_docker_images(services)
45
+ services.each do |name, service|
46
+ if service['build']
47
+ puts "Pushing image #{service['image'].colorize(:cyan)}"
48
+ push_docker_image(service['image'])
49
+ end
50
+ end
51
+ end
52
+
53
+ # @param [Hash] service
54
+ # @param [Boolean] no_cache
55
+ # @param [Boolean] no_pull
56
+ # @return [Integer]
57
+ def build_docker_image(service, no_cache = false, no_pull = false)
58
+ dockerfile = dockerfile = service['build']['dockerfile'] || 'Dockerfile'
59
+ build_context = service['build']['context']
60
+ cmd = ['docker', 'build', '-t', service['image']]
61
+ cmd << ['-f', File.join(File.expand_path(build_context), dockerfile)] if dockerfile != "Dockerfile"
62
+ cmd << '--no-cache' if no_cache
63
+ cmd << '--pull' unless no_pull
64
+ args = service['build']['args'] || {}
65
+ args.each do |k, v|
66
+ cmd << "--build-arg=#{k}=#{v}"
67
+ end
68
+ cmd << build_context
69
+ ret = system(*cmd.flatten)
70
+ raise ("Failed to build image #{service['image'].colorize(:cyan)}") unless ret
71
+ ret
72
+ end
73
+
74
+ # @param [String] image
75
+ # @return [Integer]
76
+ def push_docker_image(image)
77
+ ret = system('docker', 'push', image)
78
+ raise ("Failed to push image #{image.colorize(:cyan)}") unless ret
79
+ ret
80
+ end
81
+
82
+ # @param [String] name
83
+ # @return [Boolean]
84
+ def validate_image_name(name)
85
+ !(/^[\w.\/\-:]+:?+[\w+.]+$/ =~ name).nil?
86
+ end
87
+
88
+ # @param [String] image
89
+ # @return [Boolean]
90
+ def image_exist?(image)
91
+ system("docker history '#{image}' >/dev/null 2>/dev/null")
92
+ end
93
+
94
+ # @param [String] path
95
+ # @param [String] dockerfile
96
+ # @return [Boolean]
97
+ def dockerfile_exist?(path, dockerfile)
98
+ file = File.join(File.expand_path(path), dockerfile)
99
+ File.exist?(file)
100
+ end
101
+
102
+ # @param [Hash] hook
103
+ def run_pre_build_hook(hook)
104
+ hook.each do |h|
105
+ ret = system(h['cmd'])
106
+ raise ("Failed to run pre_build hook: #{h['name']}!") unless ret
107
+ end
108
+ end
109
+ end
110
+ end
@@ -1,37 +1,102 @@
1
- require_relative '../apps/yaml/reader'
1
+ require 'yaml'
2
+ require_relative 'yaml/reader'
3
+ require_relative '../services/services_helper'
4
+ require_relative 'service_generator_v2'
2
5
 
3
6
  module Kontena::Cli::Stacks
4
7
  module Common
5
- include Kontena::Cli::Apps::Common
8
+ include Kontena::Cli::Services::ServicesHelper
6
9
 
7
- def service_prefix
8
- @service_prefix ||= project_name_from_yaml(filename)
9
- end
10
-
11
- def stack_from_yaml(filename)
12
- set_env_variables(service_prefix, current_grid)
13
- outcome = read_yaml(filename)
14
- if outcome[:version] != '2'
15
- exit_with_error "Stack supported only in v2 YAML! Aborting."
16
- end
17
- if outcome[:name].nil?
10
+ def stack_from_yaml(filename, skip_validation = false)
11
+ reader = Kontena::Cli::Stacks::YAML::Reader.new(filename, skip_validation)
12
+ if reader.stack_name.nil?
18
13
  exit_with_error "Stack MUST have name in YAML! Aborting."
19
14
  end
15
+ set_env_variables(self.name || reader.stack_name, current_grid)
16
+ reader.reload
17
+ outcome = reader.execute
18
+
20
19
  hint_on_validation_notifications(outcome[:notifications]) if outcome[:notifications].size > 0
21
20
  abort_on_validation_errors(outcome[:errors]) if outcome[:errors].size > 0
22
21
  kontena_services = generate_services(outcome[:services], outcome[:version])
23
- # services now as hash, needs to be array in stacks API
24
- services = []
25
- kontena_services.each do |name, service|
26
- service['name'] = prefixed_name(name)
27
- services << service
28
- end
29
22
  stack = {
30
23
  'name' => outcome[:name],
31
- 'services' => services
24
+ 'stack' => outcome[:stack],
25
+ 'expose' => outcome[:expose],
26
+ 'version' => outcome[:version],
27
+ 'source' => reader.raw,
28
+ 'registry' => 'file://',
29
+ 'services' => kontena_services
32
30
  }
33
31
  stack
34
32
  end
35
33
 
34
+ def require_config_file(filename)
35
+ exit_with_error("File #{filename} does not exist") unless File.exists?(filename)
36
+ end
37
+
38
+
39
+ ##
40
+ # @param [Hash] yaml
41
+ # @param [String] version
42
+ # @return [Hash]
43
+ def generate_services(yaml_services, version)
44
+ services = []
45
+ generator_klass = ServiceGeneratorV2
46
+ yaml_services.each do |service_name, config|
47
+ exit_with_error("Image is missing for #{service_name}. Aborting.") unless config['image']
48
+ service = generator_klass.new(config).generate
49
+ service['name'] = service_name
50
+ services << service
51
+ end
52
+ services
53
+ end
54
+
55
+ def read_yaml(filename, skip_validation = false)
56
+ reader = Kontena::Cli::Stacks::YAML::Reader.new(filename)
57
+ outcome = reader.execute
58
+ outcome
59
+ end
60
+
61
+ def set_env_variables(stack, grid)
62
+ ENV['STACK'] = stack
63
+ ENV['GRID'] = grid
64
+ end
65
+
66
+ # @return [String]
67
+ def current_dir
68
+ File.basename(Dir.getwd)
69
+ end
70
+
71
+ def display_notifications(messages, color = :yellow)
72
+ messages.each do |files|
73
+ files.each do |file, services|
74
+ STDERR.puts "#{file}:".colorize(color)
75
+ services.each do |service|
76
+ service.each do |name, errors|
77
+ STDERR.puts " #{name}:".colorize(color)
78
+ if errors.is_a?(String)
79
+ STDERR.puts " - #{errors}".colorize(color)
80
+ else
81
+ errors.each do |key, error|
82
+ STDERR.puts " - #{key}: #{error.to_json}".colorize(color)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def hint_on_validation_notifications(errors)
92
+ STDERR.puts "YAML contains the following unsupported options and they were rejected:".colorize(:yellow)
93
+ display_notifications(errors)
94
+ end
95
+
96
+ def abort_on_validation_errors(errors)
97
+ STDERR.puts "YAML validation failed! Aborting.".colorize(:red)
98
+ display_notifications(errors, :red)
99
+ abort
100
+ end
36
101
  end
37
102
  end
@@ -10,17 +10,40 @@ module Kontena::Cli::Stacks
10
10
 
11
11
  def execute
12
12
  require_api_url
13
- require_token
14
-
15
- deploy_stack(name)
13
+ token = require_token
14
+
15
+ deployment = nil
16
+ spinner "Deploying stack #{pastel.cyan(name)}" do
17
+ deployment = deploy_stack(token, name)
18
+ deployment['service_deploys'].each do |service_deploy|
19
+ wait_for_deploy_to_finish(token, service_deploy)
20
+ end
21
+ end
16
22
  end
17
23
 
18
- private
19
-
20
-
21
- def deploy_stack
24
+ def deploy_stack(token, name)
22
25
  client(token).post("stacks/#{current_grid}/#{name}/deploy", {})
23
26
  end
24
27
 
28
+ # @param [String] token
29
+ # @param [Hash] deployment
30
+ # @return [Boolean]
31
+ def wait_for_deploy_to_finish(token, deployment, timeout = 600)
32
+ deployed = false
33
+ Timeout::timeout(timeout) do
34
+ until deployed
35
+ deployment = client(token).get("services/#{deployment['service_id']}/deploys/#{deployment['id']}")
36
+ deployed = true if deployment['finished_at']
37
+ sleep 1
38
+ end
39
+ if deployment['state'] == 'error'
40
+ raise deployment['reason']
41
+ end
42
+ end
43
+
44
+ deployed
45
+ rescue Timeout::Error
46
+ raise 'deploy timed out'
47
+ end
25
48
  end
26
49
  end
@@ -0,0 +1,30 @@
1
+ require_relative 'common'
2
+
3
+ module Kontena::Cli::Stacks
4
+ class InstallCommand < Kontena::Command
5
+ include Kontena::Cli::Common
6
+ include Kontena::Cli::GridOptions
7
+ include Common
8
+
9
+ parameter "[FILE]", "Kontena stack file", default: "kontena.yml", attribute_name: :filename
10
+
11
+ option ['-n', '--name'], 'NAME', 'Define stack name (by default comes from stack file)'
12
+ option '--deploy', :flag, 'Deploy after installation'
13
+
14
+ def execute
15
+ require_api_url
16
+ token = require_token
17
+ require_config_file(filename)
18
+ stack = stack_from_yaml(filename)
19
+ stack['name'] = name if name
20
+ spinner "Creating stack #{pastel.cyan(stack['name'])} " do
21
+ create_stack(token, stack)
22
+ end
23
+ Kontena.run("stack deploy #{stack['name']}") if deploy?
24
+ end
25
+
26
+ def create_stack(token, stack)
27
+ client(token).post("grids/#{current_grid}/stacks", stack)
28
+ end
29
+ end
30
+ end
@@ -6,33 +6,93 @@ module Kontena::Cli::Stacks
6
6
  include Kontena::Cli::GridOptions
7
7
  include Common
8
8
 
9
- COLUMNS = "%-30s %-10s %-10s".freeze
10
-
11
9
  def execute
12
10
  require_api_url
13
- require_token
11
+ token = require_token
14
12
 
15
- list_stacks
13
+ list_stacks(token)
16
14
  end
17
15
 
18
- private
16
+ def list_stacks(token)
17
+ response = client(token).get("grids/#{current_grid}/stacks")
19
18
 
20
- def list_stacks
21
- response = client(token).get("stacks/#{current_grid}")
22
-
23
- titles = ['NAME', 'SERVICES', 'STATE']
24
- puts COLUMNS % titles
19
+ titles = ['NAME', 'VERSION', 'SERVICES', 'STATE', 'EXPOSED PORTS']
20
+ puts "%-60s %-10s %-10s %-10s %-50s" % titles
25
21
 
26
22
  response['stacks'].each do |stack|
23
+ ports = stack_ports(stack)
24
+ health = stack_health(stack)
25
+ if health == :unhealthy
26
+ icon = '⊗'.freeze
27
+ color = :red
28
+ elsif health == :partial
29
+ icon = '⊙'.freeze
30
+ color = :yellow
31
+ elsif health == :healthy
32
+ icon = '⊛'.freeze
33
+ color = :green
34
+ else
35
+ icon = '⊝'.freeze
36
+ color = :dim
37
+ end
38
+
27
39
  vars = [
28
- stack['name'],
29
- stack['grid_services'].size,
30
- stack['state']
40
+ icon.colorize(color),
41
+ "#{stack['name']}",
42
+ "#{stack['version']}",
43
+ stack['services'].size,
44
+ stack['state'],
45
+ ports.join(",")
31
46
  ]
32
47
 
33
- puts COLUMNS % vars
48
+ puts "%s %-58s %-10s %-10s %-10s %-50s" % vars
34
49
  end
35
50
  end
36
51
 
52
+ # @param [Hash] stack
53
+ # @return [Array<String>]
54
+ def stack_ports(stack)
55
+ ports = []
56
+ stack['services'].each{|s|
57
+ service_ports = s['ports'].map{|p|
58
+ p['ip'] = '*' if p['ip'] == '0.0.0.0'
59
+ "#{p['ip']}:#{p['node_port']}->#{p['container_port']}/#{p['protocol']}"
60
+ }
61
+ ports = ports + service_ports unless service_ports.empty?
62
+ }
63
+ ports
64
+ end
65
+
66
+ # @param [Hash] stack
67
+ # @return [Symbol]
68
+ def stack_health(stack)
69
+ services_count = stack['services'].size
70
+ return :unknown if services_count == 0
71
+
72
+ fully_healthy_count = 0
73
+ partial_healthy_count = 0
74
+ unhealthy_count = 0
75
+ unknown_count = 0
76
+ stack['services'].each { |s|
77
+ total = s.dig('health_status', 'total').to_i
78
+ healthy = s.dig('health_status', 'healthy').to_i
79
+ if total > 0 && healthy == total
80
+ fully_healthy_count += 1
81
+ elsif healthy < total && healthy > 0
82
+ partial_healthy_count += 1
83
+ elsif healthy == 0 && total > 0
84
+ unhealthy_count += 1
85
+ else
86
+ unknown_count += 1
87
+ end
88
+ }
89
+ return :partial if partial_healthy_count > 0
90
+ return :partial if unhealthy_count > 0 && fully_healthy_count > 0
91
+ return :unhealthy if unhealthy_count == services_count
92
+ return :healthy if fully_healthy_count == services_count
93
+ return :healthy if fully_healthy_count > 0 && unknown_count > 0
94
+
95
+ :unknown
96
+ end
37
97
  end
38
98
  end