kontena-cli 1.4.0.pre6 → 1.4.0.pre7

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 (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