kontena-cli 0.16.0.pre7 → 0.16.0.pre8

Sign up to get free protection for your applications and to get access to all the features.
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' }