kontena-cli 1.4.0.pre6 → 1.4.0.pre7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (181) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/VERSION +1 -1
  4. data/bin/kontena +1 -1
  5. data/kontena-cli.gemspec +3 -3
  6. data/lib/kontena/cli/certificate/authorize_command.rb +67 -6
  7. data/lib/kontena/cli/certificate/get_command.rb +7 -0
  8. data/lib/kontena/cli/certificate/list_command.rb +75 -0
  9. data/lib/kontena/cli/certificate/register_command.rb +13 -2
  10. data/lib/kontena/cli/certificate/request_command.rb +20 -0
  11. data/lib/kontena/cli/certificate/show_command.rb +19 -0
  12. data/lib/kontena/cli/certificate_command.rb +4 -1
  13. data/lib/kontena/cli/cloud/master/add_command.rb +1 -1
  14. data/lib/kontena/cli/common.rb +21 -33
  15. data/lib/kontena/cli/etcd/health_command.rb +21 -27
  16. data/lib/kontena/cli/helpers/exec_helper.rb +15 -6
  17. data/lib/kontena/cli/helpers/health_helper.rb +12 -0
  18. data/lib/kontena/cli/helpers/log_helper.rb +2 -2
  19. data/lib/kontena/cli/helpers/time_helper.rb +29 -0
  20. data/lib/kontena/cli/master/init_cloud_command.rb +19 -0
  21. data/lib/kontena/cli/master/list_command.rb +1 -1
  22. data/lib/kontena/cli/master/ssh_command.rb +3 -1
  23. data/lib/kontena/cli/master/use_command.rb +1 -2
  24. data/lib/kontena/cli/node_command.rb +1 -0
  25. data/lib/kontena/cli/nodes/health_command.rb +28 -13
  26. data/lib/kontena/cli/nodes/list_command.rb +19 -3
  27. data/lib/kontena/cli/nodes/show_command.rb +4 -2
  28. data/lib/kontena/cli/nodes/ssh_command.rb +5 -2
  29. data/lib/kontena/cli/nodes/update_command.rb +2 -0
  30. data/lib/kontena/cli/plugins/install_command.rb +11 -8
  31. data/lib/kontena/cli/plugins/list_command.rb +5 -3
  32. data/lib/kontena/cli/plugins/search_command.rb +4 -2
  33. data/lib/kontena/cli/plugins/show_command.rb +17 -0
  34. data/lib/kontena/cli/plugins/uninstall_command.rb +9 -13
  35. data/lib/kontena/cli/registry/create_command.rb +1 -1
  36. data/lib/kontena/cli/services/create_command.rb +6 -0
  37. data/lib/kontena/cli/services/services_helper.rb +33 -6
  38. data/lib/kontena/cli/services/update_command.rb +6 -0
  39. data/lib/kontena/cli/stacks/build_command.rb +3 -3
  40. data/lib/kontena/cli/stacks/common.rb +105 -90
  41. data/lib/kontena/cli/stacks/deploy_command.rb +7 -3
  42. data/lib/kontena/cli/stacks/install_command.rb +39 -6
  43. data/lib/kontena/cli/stacks/list_command.rb +36 -4
  44. data/lib/kontena/cli/stacks/logs_command.rb +9 -2
  45. data/lib/kontena/cli/stacks/registry/pull_command.rb +2 -2
  46. data/lib/kontena/cli/stacks/registry/push_command.rb +20 -9
  47. data/lib/kontena/cli/stacks/registry/remove_command.rb +4 -4
  48. data/lib/kontena/cli/stacks/registry/show_command.rb +4 -4
  49. data/lib/kontena/cli/stacks/remove_command.rb +27 -1
  50. data/lib/kontena/cli/stacks/service_generator.rb +12 -2
  51. data/lib/kontena/cli/stacks/show_command.rb +35 -5
  52. data/lib/kontena/cli/stacks/stack_name.rb +71 -0
  53. data/lib/kontena/cli/stacks/upgrade_command.rb +127 -14
  54. data/lib/kontena/cli/stacks/validate_command.rb +38 -10
  55. data/lib/kontena/cli/stacks/yaml/custom_validators/certificates_validator.rb +22 -0
  56. data/lib/kontena/cli/stacks/yaml/opto/prompt_resolver.rb +1 -2
  57. data/lib/kontena/cli/stacks/yaml/reader.rb +211 -185
  58. data/lib/kontena/cli/stacks/yaml/service_extender.rb +6 -12
  59. data/lib/kontena/cli/stacks/yaml/stack_file_loader.rb +97 -0
  60. data/lib/kontena/cli/stacks/yaml/stack_file_loader/file_loader.rb +41 -0
  61. data/lib/kontena/cli/stacks/yaml/stack_file_loader/registry_loader.rb +24 -0
  62. data/lib/kontena/cli/stacks/yaml/stack_file_loader/uri_loader.rb +23 -0
  63. data/lib/kontena/cli/stacks/yaml/validations.rb +16 -0
  64. data/lib/kontena/cli/stacks/yaml/validator_v3.rb +25 -8
  65. data/lib/kontena/client.rb +2 -2
  66. data/lib/kontena/command.rb +11 -0
  67. data/lib/kontena/main_command.rb +3 -1
  68. data/lib/kontena/plugin_manager.rb +11 -198
  69. data/lib/kontena/plugin_manager/cleaner.rb +33 -0
  70. data/lib/kontena/plugin_manager/common.rb +86 -0
  71. data/lib/kontena/plugin_manager/installer.rb +54 -0
  72. data/lib/kontena/plugin_manager/loader.rb +93 -0
  73. data/lib/kontena/plugin_manager/rubygems_client.rb +42 -23
  74. data/lib/kontena/plugin_manager/uninstaller.rb +34 -0
  75. data/lib/kontena/util.rb +24 -0
  76. data/lib/kontena_cli.rb +1 -0
  77. data/omnibus/config/projects/kontena.rb +7 -1
  78. data/omnibus/config/software/{kontena.rb → kontena-cli.rb} +2 -0
  79. data/spec/fixtures/api/node.json +2 -1
  80. data/spec/fixtures/stack-internal-extend.yml +6 -1
  81. data/spec/fixtures/stack-with-dependencies-dep-1-1.yml +8 -0
  82. data/spec/fixtures/stack-with-dependencies-dep-1.yml +17 -0
  83. data/spec/fixtures/stack-with-dependencies-dep-2.yml +8 -0
  84. data/spec/fixtures/stack-with-dependencies-dep-3.yml +5 -0
  85. data/spec/fixtures/stack-with-dependencies-dep_2-removed.yml +17 -0
  86. data/spec/fixtures/stack-with-dependencies-dep_3-added.yml +25 -0
  87. data/spec/fixtures/stack-with-dependencies.yml +22 -0
  88. data/spec/fixtures/stack-with-variables.yml +3 -0
  89. data/spec/kontena/cli/etcd/health_command_spec.rb +45 -33
  90. data/spec/kontena/cli/helpers/exec_helper_spec.rb +2 -1
  91. data/spec/kontena/cli/master/init_cloud_command_spec.rb +14 -0
  92. data/spec/kontena/cli/nodes/health_command_spec.rb +74 -10
  93. data/spec/kontena/cli/nodes/list_command_spec.rb +381 -232
  94. data/spec/kontena/cli/nodes/show_command_spec.rb +31 -0
  95. data/spec/kontena/cli/nodes/ssh_command_spec.rb +18 -3
  96. data/spec/kontena/cli/plugins/install_command_spec.rb +1 -1
  97. data/spec/kontena/cli/stacks/build_command_spec.rb +6 -12
  98. data/spec/kontena/cli/stacks/common_spec.rb +42 -69
  99. data/spec/kontena/cli/stacks/install_command_spec.rb +57 -31
  100. data/spec/kontena/cli/stacks/list_command_spec.rb +44 -0
  101. data/spec/kontena/cli/stacks/logs_command_spec.rb +12 -1
  102. data/spec/kontena/cli/stacks/remove_command_spec.rb +39 -0
  103. data/spec/kontena/cli/stacks/show_command_spec.rb +16 -0
  104. data/spec/kontena/cli/stacks/stack_name_spec.rb +21 -0
  105. data/spec/kontena/cli/stacks/upgrade_command_spec.rb +73 -56
  106. data/spec/kontena/cli/stacks/validate_command_spec.rb +81 -0
  107. data/spec/kontena/cli/stacks/yaml/custom_validators/affinities_validator_spec.rb +22 -0
  108. data/spec/kontena/cli/stacks/yaml/reader_spec.rb +173 -169
  109. data/spec/kontena/cli/stacks/yaml/service_extender_spec.rb +12 -3
  110. data/spec/kontena/cli/stacks/yaml/stack_file_loader/file_loader_spec.rb +47 -0
  111. data/spec/kontena/cli/stacks/yaml/stack_file_loader/registry_loader_spec.rb +53 -0
  112. data/spec/kontena/cli/stacks/yaml/stack_file_loader/uri_loader_spec.rb +53 -0
  113. data/spec/kontena/cli/stacks/yaml/stack_file_loader_spec.rb +104 -0
  114. data/spec/kontena/cli/stacks/yaml/validator_v3_spec.rb +19 -0
  115. data/spec/kontena/plugin_manager/cleaner_spec.rb +20 -0
  116. data/spec/kontena/plugin_manager/common_spec.rb +39 -0
  117. data/spec/kontena/plugin_manager/installer_spec.rb +50 -0
  118. data/spec/kontena/plugin_manager/loader_spec.rb +5 -0
  119. data/spec/kontena/plugin_manager/rubygems_client_spec.rb +11 -25
  120. data/spec/kontena/plugin_manager/uninstaller_spec.rb +19 -0
  121. data/spec/kontena/plugin_manager_spec.rb +7 -7
  122. metadata +64 -97
  123. data/lib/kontena/cli/app_command.rb +0 -22
  124. data/lib/kontena/cli/apps/build_command.rb +0 -28
  125. data/lib/kontena/cli/apps/common.rb +0 -172
  126. data/lib/kontena/cli/apps/config_command.rb +0 -25
  127. data/lib/kontena/cli/apps/deploy_command.rb +0 -137
  128. data/lib/kontena/cli/apps/docker_compose_generator.rb +0 -61
  129. data/lib/kontena/cli/apps/docker_helper.rb +0 -80
  130. data/lib/kontena/cli/apps/dockerfile_generator.rb +0 -16
  131. data/lib/kontena/cli/apps/init_command.rb +0 -89
  132. data/lib/kontena/cli/apps/kontena_yml_generator.rb +0 -105
  133. data/lib/kontena/cli/apps/list_command.rb +0 -59
  134. data/lib/kontena/cli/apps/logs_command.rb +0 -37
  135. data/lib/kontena/cli/apps/monitor_command.rb +0 -93
  136. data/lib/kontena/cli/apps/remove_command.rb +0 -74
  137. data/lib/kontena/cli/apps/restart_command.rb +0 -39
  138. data/lib/kontena/cli/apps/scale_command.rb +0 -33
  139. data/lib/kontena/cli/apps/service_generator.rb +0 -114
  140. data/lib/kontena/cli/apps/service_generator_v2.rb +0 -27
  141. data/lib/kontena/cli/apps/show_command.rb +0 -23
  142. data/lib/kontena/cli/apps/start_command.rb +0 -40
  143. data/lib/kontena/cli/apps/stop_command.rb +0 -40
  144. data/lib/kontena/cli/apps/yaml/custom_validators/affinities_validator.rb +0 -19
  145. data/lib/kontena/cli/apps/yaml/custom_validators/build_validator.rb +0 -22
  146. data/lib/kontena/cli/apps/yaml/custom_validators/extends_validator.rb +0 -20
  147. data/lib/kontena/cli/apps/yaml/custom_validators/hooks_validator.rb +0 -54
  148. data/lib/kontena/cli/apps/yaml/custom_validators/secrets_validator.rb +0 -22
  149. data/lib/kontena/cli/apps/yaml/reader.rb +0 -213
  150. data/lib/kontena/cli/apps/yaml/service_extender.rb +0 -77
  151. data/lib/kontena/cli/apps/yaml/validations.rb +0 -71
  152. data/lib/kontena/cli/apps/yaml/validator.rb +0 -38
  153. data/lib/kontena/cli/apps/yaml/validator_v2.rb +0 -53
  154. data/spec/fixtures/app.json +0 -42
  155. data/spec/fixtures/health.yml +0 -26
  156. data/spec/fixtures/kontena-build.yml +0 -16
  157. data/spec/fixtures/kontena-internal-extend.yml +0 -8
  158. data/spec/fixtures/kontena-invalid.yml +0 -4
  159. data/spec/fixtures/kontena-with-env-file.yml +0 -18
  160. data/spec/fixtures/kontena-with-variables.yml +0 -19
  161. data/spec/fixtures/kontena.yml +0 -17
  162. data/spec/fixtures/kontena_build_v2.yml +0 -26
  163. data/spec/fixtures/kontena_numeric_version.yml +0 -9
  164. data/spec/fixtures/kontena_v2.yml +0 -35
  165. data/spec/fixtures/mysql.yml +0 -3
  166. data/spec/fixtures/wordpress-scaled.yml +0 -3
  167. data/spec/fixtures/wordpress.yml +0 -2
  168. data/spec/kontena/cli/app/build_command_spec.rb +0 -55
  169. data/spec/kontena/cli/app/common_spec.rb +0 -110
  170. data/spec/kontena/cli/app/config_command_spec.rb +0 -78
  171. data/spec/kontena/cli/app/deploy_command_spec.rb +0 -217
  172. data/spec/kontena/cli/app/docker_helper_spec.rb +0 -155
  173. data/spec/kontena/cli/app/init_command_spec.rb +0 -109
  174. data/spec/kontena/cli/app/logs_command_spec.rb +0 -131
  175. data/spec/kontena/cli/app/scale_spec.rb +0 -51
  176. data/spec/kontena/cli/app/service_generator_spec.rb +0 -384
  177. data/spec/kontena/cli/app/service_generator_v2_spec.rb +0 -73
  178. data/spec/kontena/cli/app/yaml/reader_spec.rb +0 -457
  179. data/spec/kontena/cli/app/yaml/service_extender_spec.rb +0 -127
  180. data/spec/kontena/cli/app/yaml/validator_spec.rb +0 -380
  181. data/spec/kontena/cli/app/yaml/validator_v2_spec.rb +0 -301
@@ -1,5 +1,3 @@
1
- require_relative '../../../util'
2
-
3
1
  module Kontena::Cli::Stacks
4
2
  module YAML
5
3
  class ServiceExtender
@@ -14,7 +12,7 @@ module Kontena::Cli::Stacks
14
12
  # @param [Hash] from
15
13
  # @return [Hash]
16
14
  def extend_from(from)
17
- service_config['environment'] = extend_env_vars(from['environment'], service_config['environment'])
15
+ service_config['environment'] = extend_env_vars(from['env'], service_config['environment'])
18
16
  service_config['secrets'] = extend_secrets( from['secrets'], service_config['secrets'])
19
17
  build_args = extend_build_args(safe_dig(from, 'build', 'args'), safe_dig(service_config, 'build', 'args'))
20
18
  unless build_args.empty?
@@ -26,6 +24,10 @@ module Kontena::Cli::Stacks
26
24
 
27
25
  private
28
26
 
27
+ def env_to_hash(env_array)
28
+ env_array.map { |env| env.split('=', 2) }.to_h
29
+ end
30
+
29
31
  # Takes two arrays of "key=value" pairs and merges them. Keys in "from"-array
30
32
  # will not overwrite keys that already exist in "to"-array.
31
33
  #
@@ -33,15 +35,7 @@ module Kontena::Cli::Stacks
33
35
  # @param [Array] to
34
36
  # @return [Array]
35
37
  def extend_env_vars(from, to)
36
- env_vars = to || []
37
- if from
38
- from.each do |env|
39
- env_vars << env unless to && to.find do |key|
40
- key.split('=').first == env.split('=').first
41
- end
42
- end
43
- end
44
- env_vars
38
+ env_to_hash(from || []).merge(env_to_hash(to || [])).map { |k,v| [k.to_s, v.to_s].join('=') }
45
39
  end
46
40
 
47
41
  # Takes two arrays of hashes containing { 'secret' => 'str', 'type' => 'str', 'name' => 'str' }
@@ -0,0 +1,97 @@
1
+ require_relative '../stack_name'
2
+ require 'yaml'
3
+ require_relative 'reader'
4
+
5
+ module Kontena::Cli::Stacks
6
+ module YAML
7
+ class StackFileLoader
8
+ # A base class for loading stack files. You can define more loaders by
9
+ # inheriting from this class.
10
+ #
11
+ # The purpose of StackFileLoader is to provide a generic interface for
12
+ # loading stack YAML's from different sources, such as local files,
13
+ # stack registry or URLs
14
+
15
+ def self.inherited(where)
16
+ loaders << where
17
+ end
18
+
19
+ def self.loaders
20
+ @loaders ||= []
21
+ end
22
+
23
+ # The main interface for getting a new loader
24
+ #
25
+ # @param source [String] stack file source string (filename, url, ..)
26
+ # @param parent [StackFileLoader] define a parent for recursion
27
+ # @return [StackFileLoader]
28
+ def self.for(source, parent = nil)
29
+ loader = loaders.find { |l| l.match?(source, parent) }
30
+ if loader.nil?
31
+ raise RuntimeError, "Not found: no such file #{source} or invalid uri scheme"
32
+ end
33
+ loader.new(source, parent)
34
+ end
35
+
36
+ attr_reader :source, :parent
37
+
38
+ # @param source [String] stack file source string (filename, url, ..)
39
+ # @param parent [StackFileLoader] define a parent for recursion
40
+ # @return [StackFileLoader]
41
+ def initialize(source, parent = nil)
42
+ @source = source
43
+ @parent = parent
44
+ end
45
+
46
+ # @return [Hash] a hash parsed from the YAML content
47
+ def yaml
48
+ @yaml ||= ::YAML.safe_load(content, [], [], true, source)
49
+ end
50
+
51
+ # @return [String] raw file content
52
+ def content
53
+ @content ||= read_content
54
+ end
55
+
56
+ def read_content
57
+ raise "Implement in inheriting class"
58
+ end
59
+
60
+ # @return [StackName] an accessor to StackName for the target file
61
+ def stack_name
62
+ @stack_name ||= Kontena::Cli::Stacks::StackName.new(yaml['stack'], yaml['version'])
63
+ end
64
+
65
+ # @return [Reader] an accessor to YAML::Reader for the target file
66
+ def reader(*args)
67
+ @reader ||= Reader.new(self, *args)
68
+ end
69
+
70
+ # Builds an array of hashes that represent the dependency tree starting
71
+ # from the target file. Unless recurse is set to false, the tree will
72
+ # contain also nested dependencies from any child stacks.
73
+ #
74
+ # @param recurse [TrueClass,FalseClass] recurse child dependencies?
75
+ # @return [Array<Hash>] an array of hashes ('name', 'stack', 'variables', and 'depends')
76
+ def dependencies(recurse: true)
77
+ return @dependencies if @dependencies
78
+ depends = yaml['depends']
79
+ if depends.nil? || depends.empty?
80
+ @dependencies = nil
81
+ else
82
+ @dependencies = depends.map do |name, dependency|
83
+ reader = StackFileLoader.for(dependency['stack'], self)
84
+ deps = { 'name' => name, 'stack' => reader.source, 'variables' => dependency.fetch('variables', Hash.new) }
85
+ if recurse
86
+ child_deps = reader.dependencies
87
+ deps['depends'] = child_deps unless child_deps.nil?
88
+ end
89
+ deps
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+
97
+ Dir[File.expand_path('../stack_file_loader/*.rb', __FILE__)].each { |f| require f }
@@ -0,0 +1,41 @@
1
+ require 'pathname'
2
+
3
+ module Kontena::Cli::Stacks
4
+ module YAML
5
+ class FileLoader < StackFileLoader
6
+ def self.match?(source, parent = nil)
7
+ ::File.exist?(with_context(source, parent))
8
+ end
9
+
10
+ def self.is_file?(parent)
11
+ parent.is_a?(FileLoader)
12
+ end
13
+
14
+ def self.with_context(source, parent = nil)
15
+ if is_file?(parent)
16
+ File.join(File.dirname(parent.source), source)
17
+ else
18
+ File.absolute_path(source)
19
+ end
20
+ end
21
+
22
+ def initialize(*args)
23
+ super
24
+ @source = self.class.with_context(@source, parent)
25
+ end
26
+
27
+ def read_content
28
+ ::File.read(source)
29
+ end
30
+
31
+ def origin
32
+ "file"
33
+ end
34
+
35
+ def registry
36
+ "file://"
37
+ end
38
+
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,24 @@
1
+ module Kontena::Cli::Stacks
2
+ module YAML
3
+ class RegistryLoader < StackFileLoader
4
+ def self.match?(source, parent = nil)
5
+ source =~ /\A[a-zA-Z0-9\_\.\-]+\/[a-zA-Z0-9\_\.\-]+(?::.*)?\z/ && !FileLoader.match?(source, parent)
6
+ end
7
+
8
+ def read_content
9
+ Kontena::StacksCache.pull(source)
10
+ end
11
+
12
+ def origin
13
+ "registry"
14
+ end
15
+
16
+ def registry
17
+ account = Kontena::Cli::Config.current_account
18
+ raise "Current account not set" if account.nil?
19
+ account.stacks_url
20
+ end
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,23 @@
1
+ module Kontena::Cli::Stacks
2
+ module YAML
3
+ class UriLoader < StackFileLoader
4
+ def self.match?(source, parent = nil)
5
+ source.include?('://') && !FileLoader.match?(source, parent)
6
+ end
7
+
8
+ def read_content
9
+ require 'open-uri'
10
+ stream = open(source)
11
+ stream.read
12
+ end
13
+
14
+ def origin
15
+ "uri"
16
+ end
17
+
18
+ def registry
19
+ "file://"
20
+ end
21
+ end
22
+ end
23
+ end
@@ -6,6 +6,7 @@ module Kontena::Cli::Stacks::YAML
6
6
  require_relative 'custom_validators/extends_validator'
7
7
  require_relative 'custom_validators/hooks_validator'
8
8
  require_relative 'custom_validators/secrets_validator'
9
+ require_relative 'custom_validators/certificates_validator'
9
10
 
10
11
  def self.load
11
12
  return if @loaded
@@ -13,6 +14,7 @@ module Kontena::Cli::Stacks::YAML
13
14
  HashValidator.append_validator(BuildValidator.new)
14
15
  HashValidator.append_validator(ExtendsValidator.new)
15
16
  HashValidator.append_validator(SecretsValidator.new)
17
+ HashValidator.append_validator(CertificatesValidator.new)
16
18
  HashValidator.append_validator(HooksValidator.new)
17
19
  @loaded = true
18
20
  end
@@ -27,10 +29,12 @@ module Kontena::Cli::Stacks::YAML
27
29
  'cap_add' => optional('array'),
28
30
  'cap_drop' => optional('array'),
29
31
  'command' => optional('string'),
32
+ 'cpus' => optional('float'),
30
33
  'cpu_shares' => optional('integer'),
31
34
  'external_links' => optional('array'),
32
35
  'mem_limit' => optional('string'),
33
36
  'mem_swaplimit' => optional('string'),
37
+ 'shm_size' => optional('string'),
34
38
  'environment' => optional(-> (value) {
35
39
  if value.is_a?(Hash)
36
40
  value.all? do |k,v|
@@ -58,6 +62,7 @@ module Kontena::Cli::Stacks::YAML
58
62
  'volumes' => optional('array'),
59
63
  'volumes_from' => optional('array'),
60
64
  'secrets' => optional('stacks_valid_secrets'),
65
+ 'certificates' => optional('stacks_valid_certificates'),
61
66
  'hooks' => optional('stacks_valid_hooks'),
62
67
  'only_if' => optional(-> (value) { value.is_a?(String) || value.is_a?(Hash) || value.is_a?(Array) }),
63
68
  'skip_if' => optional(-> (value) { value.is_a?(String) || value.is_a?(Hash) || value.is_a?(Array) }),
@@ -92,10 +97,21 @@ module Kontena::Cli::Stacks::YAML
92
97
  HashValidator.validate(volume_config, volume_schema, true)
93
98
  end
94
99
 
100
+ def validate_dependencies(dependency_config)
101
+ HashValidator.validate(dependency_config, dependency_schema, true)
102
+ end
103
+
95
104
  def volume_schema
96
105
  {
97
106
  'external' => optional(-> (value) { value.is_a?(TrueClass) || (value.is_a?(Hash) && value['name'].is_a?(String)) })
98
107
  }
99
108
  end
109
+
110
+ def dependency_schema
111
+ {
112
+ 'stack' => optional('string'),
113
+ 'variables' => optional(-> (value) { value.is_a?(Hash) })
114
+ }
115
+ end
100
116
  end
101
117
  end
@@ -6,7 +6,7 @@ module Kontena::Cli::Stacks
6
6
  require_relative 'validations'
7
7
  include Validations
8
8
 
9
- KNOWN_TOP_LEVEL_KEYS = %w(
9
+ KNOWN_TOP_LEVEL_KEYS = %i(
10
10
  services
11
11
  errors
12
12
  volumes
@@ -17,6 +17,7 @@ module Kontena::Cli::Stacks
17
17
  data
18
18
  description
19
19
  expose
20
+ depends
20
21
  )
21
22
 
22
23
  def initialize
@@ -26,7 +27,7 @@ module Kontena::Cli::Stacks
26
27
  @schema['network_mode'] = optional(%w(host bridge))
27
28
  @schema['logging'] = optional({
28
29
  'driver' => optional('string'),
29
- 'options' => optional(-> (value) { value.is_a?(Hash) })
30
+ 'options' => optional(-> (value) { value.kind_of?(Hash) })
30
31
  })
31
32
  Validations::CustomValidators.load
32
33
  end
@@ -59,14 +60,16 @@ module Kontena::Cli::Stacks
59
60
  notifications: []
60
61
  }
61
62
 
62
- result[:notifications] += (yaml.keys - KNOWN_TOP_LEVEL_KEYS).map do |key|
63
- { key => "unknown top level key" }
63
+ yaml.keys.each do |key|
64
+ unless KNOWN_TOP_LEVEL_KEYS.include?(key) || KNOWN_TOP_LEVEL_KEYS.include?(key.to_sym)
65
+ result[:notifications] << { key.to_s => "unknown top level key" }
66
+ end
64
67
  end
65
68
 
66
69
  if yaml.key?('services')
67
- if yaml['services'].is_a?(Hash)
70
+ if yaml['services'].kind_of?(Hash)
68
71
  yaml['services'].each do |service, options|
69
- unless options.is_a?(Hash)
72
+ unless options.kind_of?(Hash)
70
73
  result[:errors] << { 'services' => { service => { 'options' => "must be a mapping not a #{options.class}"} } }
71
74
  next
72
75
  end
@@ -107,9 +110,9 @@ module Kontena::Cli::Stacks
107
110
  end
108
111
 
109
112
  if yaml.key?('volumes')
110
- if yaml['volumes'].is_a?(Hash)
113
+ if yaml['volumes'].kind_of?(Hash)
111
114
  yaml['volumes'].each do |volume, options|
112
- if options.is_a?(Hash)
115
+ if options.kind_of?(Hash)
113
116
  option_errors = validate_volume_options(options)
114
117
  unless option_errors.valid?
115
118
  result[:errors] << { 'volumes' => { volume => option_errors.errors } }
@@ -130,6 +133,20 @@ module Kontena::Cli::Stacks
130
133
  if (yaml['volumes'].nil? || yaml['volumes'].empty?) && (yaml['services'].nil? || yaml['services'].empty?)
131
134
  result[:errors] << { 'file' => 'does not list any services or volumes' }
132
135
  end
136
+
137
+ if yaml.key?('depends')
138
+ unless yaml['depends'].kind_of?(Hash)
139
+ result[:errors] << { 'depends' => "Must be a mapping, not #{yaml['depends'].class}" }
140
+ end
141
+
142
+ yaml['depends'].each do |name, dependency_options|
143
+ validator = validate_dependencies(dependency_options)
144
+ result[:errors] << { 'depends' => { name => validator.errors } } unless validator.valid?
145
+ if yaml.key?('services') && yaml['services'][name]
146
+ result[:errors] << { 'depends' => { name => 'is defined both as service and dependency name' } }
147
+ end
148
+ end
149
+ end
133
150
  result
134
151
  end
135
152
  end
@@ -50,9 +50,9 @@ module Kontena
50
50
 
51
51
  excon_opts = {
52
52
  omit_default_port: true,
53
- connect_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_i : 5,
53
+ connect_timeout: ENV["EXCON_CONNECT_TIMEOUT"] ? ENV["EXCON_CONNECT_TIMEOUT"].to_i : 10,
54
54
  read_timeout: ENV["EXCON_READ_TIMEOUT"] ? ENV["EXCON_READ_TIMEOUT"].to_i : 30,
55
- write_timeout: ENV["EXCON_WRITE_TIMEOUT"] ? ENV["EXCON_WRITE_TIMEOUT"].to_i : 5,
55
+ write_timeout: ENV["EXCON_WRITE_TIMEOUT"] ? ENV["EXCON_WRITE_TIMEOUT"].to_i : 10,
56
56
  ssl_verify_peer: ignore_ssl_errors? ? false : true
57
57
  }
58
58
  if ENV["DEBUG"]
@@ -183,6 +183,17 @@ class Kontena::Command < Clamp::Command
183
183
  false
184
184
  end
185
185
 
186
+ # Returns an instance of the command, just like with Kontena.run! but before calling "execute"
187
+ # You can use it for specs or reuse of instancemethods.
188
+ # Example:
189
+ # cmd = Kontena::FooCommand.instance(['-n', 'foo'])
190
+ # cmd.fetch_stuff
191
+ def instance(arguments)
192
+ @arguments = arguments
193
+ parse @arguments
194
+ self
195
+ end
196
+
186
197
  def run(arguments)
187
198
  Kontena.logger.debug { "Running #{self.class.name} with #{arguments.inspect} -- callback matcher = '#{self.class.callback_matcher.nil? ? "nil" : self.class.callback_matcher.map(&:to_s).join(' ')}'" }
188
199
  @arguments = arguments
@@ -29,7 +29,6 @@ class Kontena::MainCommand < Kontena::Command
29
29
  subcommand "registry", "Registry specific commands", load_subcommand('registry_command')
30
30
  subcommand "external-registry", "External registry specific commands", load_subcommand('external_registry_command')
31
31
  subcommand "volume", "Volume specific commands [EXPERIMENTAL]", load_subcommand('volume_command')
32
- subcommand "app", "App specific commands", load_subcommand('app_command')
33
32
  subcommand "plugin", "Plugin specific commands", load_subcommand('plugin_command')
34
33
  subcommand "whoami", "Shows current logged in user", load_subcommand('whoami_command')
35
34
  subcommand "version", "Show CLI and current master version", load_subcommand('version_command')
@@ -54,6 +53,9 @@ class Kontena::MainCommand < Kontena::Command
54
53
  elsif name == 'logout'
55
54
  exit_with_error "Use 'kontena master logout' to log out from a Kontena Master\n"+
56
55
  " or 'kontena cloud logout' for logging out from your Kontena Cloud account"
56
+ elsif name == 'app'
57
+ exit_with_error "The deprecated app subcommand has been moved into a plugin. You can install\n" +
58
+ " it by using 'kontena plugin install app-command'"
57
59
  end
58
60
  super
59
61
  end
@@ -1,213 +1,26 @@
1
- require 'singleton'
2
-
3
1
  module Kontena
4
- class PluginManager
2
+ module PluginManager
5
3
  autoload :RubygemsClient, 'kontena/plugin_manager/rubygems_client'
6
- Gem.autoload :DependencyInstaller, 'rubygems/dependency_installer'
7
- Gem.autoload :Requirement, 'rubygems/requirement'
8
- Gem.autoload :Uninstaller, 'rubygems/uninstaller'
9
- Gem.autoload :Commands, 'rubygems/command'
10
- Gem.autoload :DefaultUserInteraction, 'rubygems/user_interaction'
11
- Gem.autoload :StreamUI, 'rubygems/user_interaction'
12
- Gem::Commands.autoload :CleanupCommand, 'rubygems/commands/cleanup_command'
13
-
14
- include Singleton
15
-
16
- CLI_GEM = 'kontena-cli'.freeze
17
- MIN_CLI_VERSION = '0.15.99'.freeze
4
+ autoload :Loader, 'kontena/plugin_manager/loader'
5
+ autoload :Installer, 'kontena/plugin_manager/installer'
6
+ autoload :Uninstaller, 'kontena/plugin_manager/uninstaller'
7
+ autoload :Cleaner, 'kontena/plugin_manager/cleaner'
8
+ autoload :Common, 'kontena/plugin_manager/common'
18
9
 
19
10
  # Initialize plugin manager
20
11
  def init
21
- ENV["GEM_HOME"] = install_dir
12
+ ENV["GEM_HOME"] = Common.install_dir
22
13
  Gem.paths = ENV
14
+ Common.use_dummy_ui unless ENV["DEBUG"]
23
15
  plugins
24
- use_dummy_ui unless ENV["DEBUG"]
25
16
  true
26
17
  end
27
-
28
- # Install a plugin
29
- # @param plugin_name [String]
30
- # @param pre [Boolean] install a prerelease version if available
31
- # @param version [String] install a specific version
32
- def install_plugin(plugin_name, pre: false, version: nil)
33
- cmd = Gem::DependencyInstaller.new(
34
- document: false,
35
- force: true,
36
- prerelease: pre,
37
- minimal_deps: true
38
- )
39
- plugin_version = version.nil? ? Gem::Requirement.default : Gem::Requirement.new(version)
40
- cmd.install(prefix(plugin_name), plugin_version)
41
- cmd.installed_gems
42
- end
43
-
44
- # Uninstall a plugin
45
- # @param plugin_name [String]
46
- def uninstall_plugin(plugin_name)
47
- installed = installed(plugin_name)
48
- raise "Plugin #{plugin_name} not installed" unless installed
49
-
50
- cmd = Gem::Uninstaller.new(
51
- installed.name,
52
- all: true,
53
- executables: true,
54
- force: true,
55
- install_dir: installed.base_dir
56
- )
57
- cmd.uninstall
58
- end
59
-
60
- # Search rubygems for kontena plugins
61
- # @param pattern [String] optional search pattern
62
- def search_plugins(pattern = nil)
63
- RubygemsClient.new.search(prefix(pattern))
64
- end
65
-
66
- # Retrieve plugin versions from rubygems
67
- # @param plugin_name [String]
68
- def gem_versions(plugin_name)
69
- RubygemsClient.new.versions(prefix(plugin_name))
70
- end
71
-
72
- # Get the latest version number from rubygems
73
- # @param plugin_name [String]
74
- # @param pre [Boolean] include prerelease versions
75
- def latest_version(plugin_name, pre: false)
76
- return gem_versions(plugin_name).first if pre
77
- gem_versions(plugin_name).find { |version| !version.prerelease? }
78
- end
79
-
80
- # Find a plugin by name from installed plugins
81
- # @param plugin_name [String]
82
- def installed(plugin_name)
83
- search = prefix(plugin_name)
84
- plugins.find {|plugin| plugin.name == search }
85
- end
86
-
87
- # Upgrade an installed plugin
88
- # @param plugin_name [String]
89
- # @param pre [Boolean] upgrade to a prerelease version if available. Will happen always when the installed version is a prerelease version.
90
- def upgrade_plugin(plugin_name, pre: false)
91
- installed = installed(plugin_name)
92
- if installed.version.prerelease?
93
- pre = true
94
- end
95
-
96
- if installed
97
- latest = latest_version(plugin_name, pre: pre)
98
- if latest > installed.version
99
- install_plugin(plugin_name, version: latest.to_s)
100
- end
101
- else
102
- raise "Plugin #{plugin_name} not installed"
103
- end
104
- end
105
-
106
- # Runs gem cleanup, removes remains from previous versions
107
- # @param plugin_name [String]
108
- def cleanup_plugin(plugin_name)
109
- cmd = Gem::Commands::CleanupCommand.new
110
- options = []
111
- options += ['-q', '--no-verbose'] unless ENV["DEBUG"]
112
- cmd.handle_options options
113
- cmd.execute
114
- rescue Gem::SystemExitException => e
115
- return true if e.exit_code == 0
116
- raise
117
- end
118
-
119
- # Gem installation directory
120
- # @return [String]
121
- def install_dir
122
- return @install_dir if @install_dir
123
- install_dir = File.join(Dir.home, '.kontena', 'gems', RUBY_VERSION)
124
- unless File.directory?(install_dir)
125
- require 'fileutils'
126
- FileUtils.mkdir_p(install_dir, mode: 0700)
127
- end
128
- @install_dir = install_dir
129
- end
130
-
18
+ module_function :init
131
19
 
132
20
  # @return [Array<Gem::Specification>]
133
21
  def plugins
134
- @plugins ||= load_plugins
135
- end
136
-
137
- private
138
-
139
- def plugin_debug?
140
- @plugin_debug ||= ENV['DEBUG'] == 'plugin'
141
- end
142
-
143
- def load_plugins
144
- plugins = []
145
- Gem::Specification.to_a.each do |spec|
146
- spec.require_paths.to_a.each do |require_path|
147
- plugin = File.join(spec.gem_dir, require_path, 'kontena_cli_plugin.rb')
148
- if File.exist?(plugin) && !plugins.find{ |p| p.name == spec.name }
149
- begin
150
- if spec_has_valid_dependency?(spec)
151
-
152
- loaded_features_before = $LOADED_FEATURES.dup
153
- load_path_before = $LOAD_PATH.dup
154
-
155
- Kontena.logger.debug { "Activating plugin #{spec.name}" } if plugin_debug?
156
- spec.activate
157
- spec.activate_dependencies
158
-
159
- Kontena.logger.debug { "Loading plugin #{spec.name}" }
160
- require(plugin)
161
- Kontena.logger.debug { "Loaded plugin #{spec.name}" } if plugin_debug?
162
-
163
- if plugin_debug?
164
- added_features = ($LOADED_FEATURES - loaded_features_before).map {|feat| "- #{feat}"}
165
- added_paths = ($LOAD_PATH - load_path_before).map {|feat| "- #{feat}"}
166
- Kontena.logger.debug { "Plugin manager loaded features for #{spec.name}:" } unless added_features.empty?
167
- added_features.each { |feat| Kontena.logger.debug { feat } }
168
- Kontena.logger.debug { "Plugin manager load paths added for #{spec.name}:" } unless added_paths.empty?
169
- added_paths.each { |path| Kontena.logger.debug { path } }
170
- end
171
-
172
- plugins << spec
173
- else
174
- plugin_name = spec.name.sub('kontena-plugin-', '')
175
- $stderr.puts " [#{Kontena.pastel.red('error')}] Plugin #{Kontena.pastel.cyan(plugin_name)} (#{spec.version}) is not compatible with the current cli version."
176
- $stderr.puts " To update the plugin, run 'kontena plugin install #{plugin_name}'"
177
- end
178
- rescue ScriptError, StandardError => ex
179
- warn " [#{Kontena.pastel.red('error')}] Failed to load plugin: #{spec.name} from #{spec.gem_dir}\n\tRerun the command with environment DEBUG=true set to get the full exception."
180
- Kontena.logger.error(ex)
181
- end
182
- end
183
- end
184
- end
185
- plugins
186
- rescue => ex
187
- $stderr.puts Kontena.pastel.red(ex.message)
188
- Kontena.logger.error(ex)
189
- end
190
-
191
- def prefix(plugin_name)
192
- return plugin_name if plugin_name.to_s.start_with?('kontena-plugin-')
193
- "kontena-plugin-#{plugin_name}"
194
- end
195
-
196
- def dummy_ui
197
- Gem::StreamUI.new(StringIO.new, StringIO.new, StringIO.new, false)
198
- end
199
-
200
- def use_dummy_ui
201
- Gem::DefaultUserInteraction.ui = dummy_ui
202
- end
203
-
204
- # @param [Gem::Specification] spec
205
- # @return [Boolean]
206
- def spec_has_valid_dependency?(spec)
207
- kontena_cli = spec.runtime_dependencies.find{ |d| d.name == CLI_GEM }
208
- !kontena_cli.match?(CLI_GEM, MIN_CLI_VERSION)
209
- rescue
210
- false
22
+ @plugins ||= Loader.new.load_plugins
211
23
  end
24
+ module_function :plugins
212
25
  end
213
26
  end