kontena-cli 0.16.0.pre7 → 0.16.0.pre8

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 (57) hide show
  1. checksums.yaml +4 -4
  2. data/VERSION +1 -1
  3. data/kontena-cli.gemspec +2 -5
  4. data/lib/kontena/callbacks/master/deploy/05_before_deploy_configuration_wizard.rb +15 -21
  5. data/lib/kontena/callbacks/master/deploy/50_authenticate_after_deploy.rb +5 -3
  6. data/lib/kontena/callbacks/master/deploy/55_create_initial_grid_after_deploy.rb +3 -1
  7. data/lib/kontena/callbacks/master/deploy/60_configure_auth_provider_after_deploy.rb +7 -18
  8. data/lib/kontena/callbacks/master/deploy/70_invite_self_after_deploy.rb +93 -0
  9. data/lib/kontena/callbacks/master/deploy/90_proptip_after_deploy.rb +33 -0
  10. data/lib/kontena/cli/apps/build_command.rb +2 -1
  11. data/lib/kontena/cli/apps/common.rb +3 -2
  12. data/lib/kontena/cli/apps/config_command.rb +3 -3
  13. data/lib/kontena/cli/apps/deploy_command.rb +1 -0
  14. data/lib/kontena/cli/apps/docker_helper.rb +7 -7
  15. data/lib/kontena/cli/apps/list_command.rb +1 -1
  16. data/lib/kontena/cli/apps/logs_command.rb +1 -1
  17. data/lib/kontena/cli/apps/monitor_command.rb +2 -2
  18. data/lib/kontena/cli/apps/remove_command.rb +1 -1
  19. data/lib/kontena/cli/apps/restart_command.rb +1 -1
  20. data/lib/kontena/cli/apps/scale_command.rb +1 -1
  21. data/lib/kontena/cli/apps/start_command.rb +1 -1
  22. data/lib/kontena/cli/apps/stop_command.rb +1 -1
  23. data/lib/kontena/cli/apps/yaml/custom_validators/affinities_validator.rb +19 -0
  24. data/lib/kontena/cli/apps/yaml/custom_validators/build_validator.rb +22 -0
  25. data/lib/kontena/cli/apps/yaml/custom_validators/extends_validator.rb +21 -0
  26. data/lib/kontena/cli/apps/yaml/custom_validators/hooks_validator.rb +54 -0
  27. data/lib/kontena/cli/apps/yaml/custom_validators/secrets_validator.rb +22 -0
  28. data/lib/kontena/cli/apps/yaml/validations.rb +60 -90
  29. data/lib/kontena/cli/apps/yaml/validator.rb +9 -31
  30. data/lib/kontena/cli/apps/yaml/validator_v2.rb +13 -40
  31. data/lib/kontena/cli/cloud/login_command.rb +2 -2
  32. data/lib/kontena/cli/cloud/master/add_command.rb +114 -34
  33. data/lib/kontena/cli/cloud/master/list_command.rb +5 -2
  34. data/lib/kontena/cli/cloud/master/remove_command.rb +69 -0
  35. data/lib/kontena/cli/cloud/master_command.rb +2 -2
  36. data/lib/kontena/cli/common.rb +10 -17
  37. data/lib/kontena/cli/config.rb +4 -3
  38. data/lib/kontena/cli/grids/env_command.rb +5 -5
  39. data/lib/kontena/cli/localhost_web_server.rb +1 -1
  40. data/lib/kontena/cli/master/init_cloud_command.rb +21 -0
  41. data/lib/kontena/cli/master/login_command.rb +28 -29
  42. data/lib/kontena/cli/master/remove_command.rb +58 -0
  43. data/lib/kontena/cli/master/token/create_command.rb +6 -0
  44. data/lib/kontena/cli/master/users/invite_command.rb +4 -1
  45. data/lib/kontena/cli/master/users/roles/add_command.rb +4 -3
  46. data/lib/kontena/cli/master_command.rb +8 -9
  47. data/lib/kontena/cli/services/list_command.rb +3 -2
  48. data/lib/kontena/cli/services/services_helper.rb +1 -1
  49. data/lib/kontena/client.rb +31 -47
  50. data/lib/kontena/presets/kontena_auth_provider.yml +2 -2
  51. data/lib/kontena_cli.rb +12 -0
  52. data/spec/kontena/cli/app/docker_helper_spec.rb +9 -4
  53. data/spec/kontena/cli/app/yaml/validator_spec.rb +66 -71
  54. data/spec/kontena/cli/app/yaml/validator_v2_spec.rb +51 -58
  55. metadata +25 -59
  56. data/lib/kontena/callbacks/master/deploy/90_suggest_inviting_yourself_after_deploy.rb +0 -24
  57. data/lib/kontena/cli/cloud/master/delete_command.rb +0 -20
@@ -15,7 +15,7 @@ module Kontena::Cli::Apps
15
15
  def execute
16
16
  require_config_file(filename)
17
17
 
18
- @services = services_from_yaml(filename, service_list, service_prefix)
18
+ @services = services_from_yaml(filename, service_list, service_prefix, true)
19
19
  if services.size > 0
20
20
  restart_services(services)
21
21
  elsif !service_list.empty?
@@ -16,7 +16,7 @@ module Kontena::Cli::Apps
16
16
 
17
17
  def execute
18
18
  require_config_file(filename)
19
- yml_service = services_from_yaml(filename, [service], service_prefix)
19
+ yml_service = services_from_yaml(filename, [service], service_prefix, true)
20
20
  if yml_service[service]
21
21
  options = yml_service[service]
22
22
  exit_with_error("Service has already instances defined in #{filename}. Please update #{filename} and deploy service instead") if options['container_count']
@@ -16,7 +16,7 @@ module Kontena::Cli::Apps
16
16
  def execute
17
17
  require_config_file(filename)
18
18
 
19
- @services = services_from_yaml(filename, service_list, service_prefix)
19
+ @services = services_from_yaml(filename, service_list, service_prefix, true)
20
20
  if services.size > 0
21
21
  start_services(services)
22
22
  elsif !service_list.empty?
@@ -16,7 +16,7 @@ module Kontena::Cli::Apps
16
16
  def execute
17
17
  require_config_file(filename)
18
18
 
19
- @services = services_from_yaml(filename, service_list, service_prefix)
19
+ @services = services_from_yaml(filename, service_list, service_prefix, true)
20
20
  if services.size > 0
21
21
  stop_services(services)
22
22
  elsif !service_list.empty?
@@ -0,0 +1,19 @@
1
+ module Kontena::Cli::Apps::YAML::Validations::CustomValidators
2
+ class AffinitiesValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('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
@@ -0,0 +1,22 @@
1
+ module Kontena::Cli::Apps::YAML::Validations::CustomValidators
2
+ class BuildValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('valid_build')
5
+ end
6
+
7
+ def validate(key, value, validations, errors)
8
+ unless value.is_a?(String) || value.is_a?(Hash)
9
+ errors[key] = 'build must be string or hash'
10
+ return
11
+ end
12
+ if value.is_a?(Hash)
13
+ build_validation = {
14
+ 'context' => 'string',
15
+ 'dockerfile' => HashValidator.optional('string'),
16
+ 'args' => HashValidator.optional(-> (value) { value.is_a?(Array) || value.is_a?(Hash) })
17
+ }
18
+ HashValidator.validator_for(build_validation).validate(key, value, build_validation, errors)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module Kontena::Cli::Apps::YAML::Validations::CustomValidators
2
+ class ExtendsValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('valid_extends')
5
+ end
6
+
7
+ def validate(key, value, validations, errors)
8
+ unless value.is_a?(String) || value.is_a?(Hash)
9
+ errors[key] = 'extends must be string or hash'
10
+ return
11
+ end
12
+ if value.is_a?(Hash)
13
+ extends_validation = {
14
+ 'service' => 'string',
15
+ 'file' => HashValidator.optional('string')
16
+ }
17
+ HashValidator.validator_for(extends_validation).validate(key, value, extends_validation, errors)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ module Kontena::Cli::Apps::YAML::Validations::CustomValidators
2
+ class HooksValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('valid_hooks')
5
+ end
6
+
7
+ def validate(key, value, validations, errors)
8
+ unless value.is_a?(Hash)
9
+ errors[key] = 'hooks must be array'
10
+ return
11
+ end
12
+
13
+ if value['pre_build']
14
+ validate_pre_build_hooks(key, value['pre_build'], errors)
15
+ end
16
+
17
+ if value['post_start']
18
+ validate_post_start_hooks(key, value['post_start'], errors)
19
+ end
20
+ end
21
+
22
+ def validate_pre_build_hooks(key, pre_build_hooks, errors)
23
+ unless pre_build_hooks.is_a?(Array)
24
+ errors[key] = 'pre_build must be array'
25
+ return
26
+ end
27
+ pre_build_validation = {
28
+ 'name' => 'string',
29
+ 'cmd' => 'string'
30
+ }
31
+ validator = HashValidator.validator_for(pre_build_validation)
32
+ pre_build_hooks.each do |pre_build|
33
+ validator.validate('hooks.pre_build', pre_build, pre_build_validation, errors)
34
+ end
35
+ end
36
+
37
+ def validate_post_start_hooks(key, post_start_hooks, errors)
38
+ unless post_start_hooks.is_a?(Array)
39
+ errors[key] = 'post_start must be array'
40
+ return
41
+ end
42
+ post_start_validation = {
43
+ 'name' => 'string',
44
+ 'instances' => (-> (value) { value.is_a?(Integer) || value == '*' }),
45
+ 'cmd' => 'string',
46
+ 'oneshot' => HashValidator.optional('boolean')
47
+ }
48
+ validator = HashValidator.validator_for(post_start_validation)
49
+ post_start_hooks.each do |post_start|
50
+ validator.validate('hooks.post_start', post_start, post_start_validation, errors)
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,22 @@
1
+ module Kontena::Cli::Apps::YAML::Validations::CustomValidators
2
+ class SecretsValidator < HashValidator::Validator::Base
3
+ def initialize
4
+ super('valid_secrets')
5
+ end
6
+
7
+ def validate(key, value, validations, errors)
8
+ unless value.is_a?(Array)
9
+ errors[key] = 'secrets must be array'
10
+ return
11
+ end
12
+ secret_item_validation = {
13
+ 'secret' => 'string',
14
+ 'name' => 'string',
15
+ 'type' => 'string'
16
+ }
17
+ value.each do |secret|
18
+ HashValidator.validator_for(secret_item_validation).validate(key, secret, secret_item_validation, errors)
19
+ end
20
+ end
21
+ end
22
+ end
@@ -1,97 +1,67 @@
1
1
  module Kontena::Cli::Apps::YAML
2
2
  module Validations
3
-
4
- def append_common_validations(base)
5
- base.optional('image').maybe(:str?)
6
-
7
- base.optional('extends') { str? | type?(Hash) }
8
- base.rule(when_extends_is_hash: ['extends']) do |extends|
9
- extends.type?(Hash) > extends.schema do
10
- required('service').filled(:str?)
11
- optional('file').value(:str?)
12
- end
13
- end
14
-
15
- base.optional('stateful') { bool? }
16
- base.optional('affinity') { array? { each { format?(/(?<=\!|\=)=/) } } }
17
- base.optional('cap_add').maybe(:array?)
18
- base.optional('cap_drop').maybe(:array?)
19
- base.optional('command').maybe(:str?)
20
- base.optional('cpu_shares').maybe(:int?)
21
- base.optional('external_links') { array? }
22
- base.optional('mem_limit') { int? | str? }
23
- base.optional('memswap_limit') { int? | str? }
24
- base.optional('environment') { array? | type?(Hash) }
25
- base.optional('env_file') { str? | array? }
26
- base.optional('instances') { int? }
27
- base.optional('links') { array? | empty? }
28
- base.optional('ports').value(:array?)
29
- base.optional('volumes').value(:array?)
30
- base.optional('volumes_from').value(:array?)
31
-
32
- base.optional('deploy').schema do
33
- optional('strategy').value(included_in?: %w(ha daemon random))
34
- optional('wait_for_port').value(:int?)
35
- optional('min_health').value(:float?)
36
- optional('interval').value(format?: /^\d+(min|h|d|)$/)
37
- end
38
-
39
- base.optional('hooks').schema do
40
- optional('post_start').each do
41
- required('name').filled
42
- required('cmd').filled
43
- required('instances') { int? | eql?('*') }
44
- optional('oneshot').value(:bool?)
45
- end
46
-
47
- optional('pre_build').each do
48
- required('cmd').filled
49
- end
50
- end
51
-
52
- base.optional('secrets').each do
53
- required('secret').filled
54
- required('name').filled
55
- required('type').filled
56
- end
57
- base.optional('health_check').schema do
58
- required('protocol').filled(format?: /^(http|tcp)$/)
59
- required('port').filled(:int?)
60
- optional('uri').value(format?: /\/[\S]*/)
61
- optional('timeout').value(:int?)
62
- optional('interval').value(:int?)
63
- optional('initial_delay').value(:int?)
64
- end
3
+ module CustomValidators
4
+ require_relative 'custom_validators/affinities_validator'
5
+ require_relative 'custom_validators/build_validator'
6
+ require_relative 'custom_validators/extends_validator'
7
+ require_relative 'custom_validators/hooks_validator'
8
+ require_relative 'custom_validators/secrets_validator'
9
+
10
+ HashValidator.append_validator(AffinitiesValidator.new)
11
+ HashValidator.append_validator(BuildValidator.new)
12
+ HashValidator.append_validator(ExtendsValidator.new)
13
+ HashValidator.append_validator(SecretsValidator.new)
14
+ HashValidator.append_validator(HooksValidator.new)
65
15
  end
66
16
 
67
- ##
68
- # @param [Hash] service_config
69
- def validate_options(service_config)
70
- @yaml_schema.call(service_config)
71
- end
17
+ def common_validations
18
+ {
19
+ 'image' => optional('string'), # it's optional because some base yml file might contain image option
20
+ 'extends' => optional('valid_extends'),
21
+ 'stateful' => optional('boolean'),
22
+ 'affinity' => optional('valid_affinities'),
23
+ 'cap_add' => optional('array'),
24
+ 'cap_drop' => optional('array'),
25
+ 'command' => optional('string'),
26
+ 'cpu_shares' => optional('integer'),
27
+ 'external_links' => optional('array'),
28
+ 'mem_limit' => optional('string'),
29
+ 'mem_swaplimit' => optional('string'),
30
+ 'environment' => optional(-> (value) { value.is_a?(Array) || value.is_a?(Hash) }),
31
+ 'env_file' => optional(-> (value) { value.is_a?(String) || value.is_a?(Array) }),
32
+ 'instances' => optional('integer'),
33
+ 'links' => optional(-> (value) { value.is_a?(Array) || value.nil? }),
34
+ 'ports' => optional('array'),
35
+ 'pid' => optional('string'),
36
+ 'privileged' => optional('boolean'),
37
+ 'user' => optional('string'),
38
+ 'volumes' => optional('array'),
39
+ 'volumes_from' => optional('array'),
40
+ 'secrets' => optional('valid_secrets'),
41
+ 'hooks' => optional('valid_hooks'),
42
+ 'deploy' => optional({
43
+ 'strategy' => optional(%w(ha daemon random)),
44
+ 'wait_for_port' => optional('integer'),
45
+ 'min_health' => optional('float'),
46
+ 'interval' => optional(/^\d+(min|h|d|)$/)
47
+ }),
48
+ 'health_check' => optional({
49
+ 'protocol' => /^(http|tcp)$/,
50
+ 'port' => 'integer',
51
+ 'uri' => optional(/\/[\S]*/),
52
+ 'timeout' => optional('integer'),
53
+ 'interval' => optional('integer'),
54
+ 'initial_delay' => optional('integer')
55
+ })
56
+ }
57
+ end
72
58
 
73
- ##
74
- # @param [Hash] service_config
75
- # @return [Array<String>] errors
76
- def validate_keys(service_config)
77
- errors = {}
78
- service_config.keys.each do |key|
79
- error = validate_required(key)
80
- errors[key] = error if error
81
- end
82
- errors
83
- end
59
+ def optional(type)
60
+ HashValidator.optional(type)
61
+ end
84
62
 
85
- ##
86
- # @param [String] key
87
- def validate_required(key)
88
- if self.class::UNSUPPORTED_KEYS.include?(key)
89
- ['unsupported option']
90
- elsif !self.class::VALID_KEYS.include?(key)
91
- ['invalid option']
92
- else
93
- nil
94
- end
95
- end
96
- end
63
+ def validate_options(service_config)
64
+ HashValidator.validate(service_config, @schema, true)
65
+ end
66
+ end
97
67
  end
@@ -1,36 +1,17 @@
1
- require 'dry-validation'
1
+ require 'hash_validator'
2
2
  module Kontena::Cli::Apps
3
3
  module YAML
4
4
  class Validator
5
5
  require_relative 'validations'
6
6
  include Validations
7
7
 
8
- VALID_KEYS = %w(
9
- affinity build dockerfile cap_add cap_drop command deploy env_file environment extends external_links
10
- image links log_driver log_opt net pid ports volumes volumes_from cpu_shares
11
- mem_limit memswap_limit privileged stateful user instances hooks secrets health_check
12
- ).freeze
13
-
14
- UNSUPPORTED_KEYS = %w(
15
- cgroup_parent container_name devices depends_on dns dns_search tmpfs entrypoint
16
- expose extra_hosts labels logging network_mode networks security_opt stop_signal ulimits volume_driver
17
- cpu_quota cpuset domainname hostname ipc mac_address
18
- read_only restart shm_size stdin_open tty working_dir
19
- ).freeze
20
-
21
- ##
22
- # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
23
8
  def initialize(need_image=false)
24
- base = self
25
- @yaml_schema = Dry::Validation.Schema do
26
- base.append_common_validations(self)
27
-
28
- optional('build').value(:str?)
29
- optional('dockerfile').value(:str?)
30
- optional('net').value(included_in?: (%w(host bridge)))
31
- optional('log_driver').value(:str?)
32
- optional('log_opts').value(type?: Hash)
33
- end
9
+ @schema = common_validations
10
+ @schema['build'] = optional('string')
11
+ @schema['dockerfile'] = optional('string')
12
+ @schema['net'] = optional(%w(host bridge))
13
+ @schema['log_driver'] = optional('string')
14
+ @schema['log_opts'] = optional({})
34
15
  end
35
16
 
36
17
  ##
@@ -41,16 +22,13 @@ module Kontena::Cli::Apps
41
22
  errors: [],
42
23
  notifications: []
43
24
  }
44
-
45
25
  yaml.each do |service, options|
46
26
  unless options.is_a?(Hash)
47
- result[:errors] << { service => { 'options' => 'must be a mapping not a string'} }
27
+ result[:errors] << { service => { 'options' => 'must be a mapping not a string'} }
48
28
  next
49
29
  end
50
- key_errors = validate_keys(options)
51
30
  option_errors = validate_options(options)
52
- result[:errors] << { service => option_errors.messages } if option_errors.failure?
53
- result[:notifications] << { service => key_errors } if key_errors.size > 0
31
+ result[:errors] << { service => option_errors.errors } unless option_errors.valid?
54
32
  end
55
33
  result
56
34
  end
@@ -1,4 +1,4 @@
1
- require 'dry-validation'
1
+ require 'hash_validator'
2
2
  require_relative 'validator'
3
3
 
4
4
  module Kontena::Cli::Apps
@@ -7,45 +7,20 @@ module Kontena::Cli::Apps
7
7
  require_relative 'validations'
8
8
  include Validations
9
9
 
10
- VALID_KEYS = %w(
11
- affinity build cap_add cap_drop command deploy depends_on env_file environment extends external_links
12
- image links logging network_mode pid ports volumes volumes_from cpu_shares
13
- mem_limit memswap_limit privileged stateful user instances hooks secrets health_check
14
- ).freeze
15
-
16
- UNSUPPORTED_KEYS = %w(
17
- cgroup_parent container_name devices dns dns_search tmpfs entrypoint
18
- expose extra_hosts labels log_driver log_opt net networks security_opt stop_signal ulimits volume_driver
19
- cpu_quota cpuset domainname hostname ipc mac_address
20
- read_only restart shm_size stdin_open tty working_dir
21
- ).freeze
22
-
23
- ##
24
- # rubocop:disable Metrics/MethodLength,Metrics/AbcSize
25
10
  def initialize
26
- base = self
27
- @yaml_schema = Dry::Validation.Schema do
28
- base.append_common_validations(self)
29
- optional('build') { str? | type?(Hash) }
30
-
31
- rule(build_hash: ['build']) do |build|
32
- build.type?(Hash) > build.schema do
33
- required('context').filled
34
- optional('dockerfile').value(:str?)
35
- optional('args') { array? | type?(Hash) }
36
- end
37
- end
38
- optional('depends_on').value(:array?)
39
- optional('network_mode').value(included_in?: (%w(host bridge)))
40
- optional('logging').schema do
41
- optional('driver').value(:str?)
42
- optional('options') { type?(Hash) }
43
- end
44
- end
11
+ @schema = common_validations
12
+ @schema['build'] = optional('valid_build')
13
+ @schema['depends_on'] = optional('array')
14
+ @schema['network_mode'] = optional(%w(host bridge))
15
+ @schema['logging'] = optional({
16
+ 'driver' => optional('string'),
17
+ 'options' => optional(-> (value) { value.is_a?(Hash) })
18
+ })
45
19
  end
46
20
 
47
21
  ##
48
22
  # @param [Hash] yaml
23
+ # @param [TrueClass|FalseClass] strict
49
24
  # @return [Array] validation_errors
50
25
  def validate(yaml)
51
26
  result = {
@@ -53,15 +28,13 @@ module Kontena::Cli::Apps
53
28
  notifications: []
54
29
  }
55
30
  if yaml.key?('services')
56
- yaml['services'].each do |service, options|
31
+ yaml['services'].each do |service, options|
57
32
  unless options.is_a?(Hash)
58
- result[:errors] << { service => { 'options' => 'must be a mapping not a string'} }
33
+ result[:errors] << { service => { 'options' => 'must be a mapping not a string'} }
59
34
  next
60
35
  end
61
- key_errors = validate_keys(options)
62
36
  option_errors = validate_options(options)
63
- result[:errors] << { service => option_errors.messages } if option_errors.failure?
64
- result[:notifications] << { service => key_errors } if key_errors.size > 0
37
+ result[:errors] << { service => option_errors.errors } unless option_errors.valid?
65
38
  end
66
39
  else
67
40
  result[:errors] << { 'file' => 'services missing' }