kontena-cli 1.5.0.pre5 → 1.5.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/kontena/callbacks/master/deploy/05_before_deploy_configuration_wizard.rb +2 -2
- data/lib/kontena/cli/services/deploy_command.rb +1 -1
- data/lib/kontena/cli/stacks/deploy_command.rb +1 -1
- data/lib/kontena/cli/stacks/install_command.rb +11 -0
- data/lib/kontena/cli/stacks/registry/create_command.rb +24 -0
- data/lib/kontena/cli/stacks/registry/make_private_command.rb +24 -0
- data/lib/kontena/cli/stacks/registry/make_public_command.rb +24 -0
- data/lib/kontena/cli/stacks/registry/pull_command.rb +1 -1
- data/lib/kontena/cli/stacks/registry/push_command.rb +1 -2
- data/lib/kontena/cli/stacks/registry/remove_command.rb +1 -1
- data/lib/kontena/cli/stacks/registry/search_command.rb +23 -10
- data/lib/kontena/cli/stacks/registry/show_command.rb +44 -11
- data/lib/kontena/cli/stacks/registry_command.rb +3 -0
- data/lib/kontena/cli/stacks/show_command.rb +15 -1
- data/lib/kontena/cli/stacks/upgrade_command.rb +9 -0
- data/lib/kontena/cli/stacks/yaml/reader.rb +2 -0
- data/lib/kontena/cli/stacks/yaml/stack_file_loader/registry_loader.rb +1 -1
- data/lib/kontena/client.rb +8 -0
- data/lib/kontena/scripts/completer.rb +2 -2
- data/lib/kontena/stacks_cache.rb +22 -25
- data/lib/kontena/stacks_client.rb +133 -20
- data/omnibus/wrappers/sh/kontena +1 -1
- data/spec/fixtures/kontena_v3_with_metadata.yml +28 -0
- data/spec/kontena/cli/stacks/install_command_spec.rb +26 -1
- data/spec/kontena/cli/stacks/upgrade_command_spec.rb +8 -1
- data/spec/kontena/cli/stacks/yaml/reader_spec.rb +8 -8
- data/spec/kontena/cli/stacks/yaml/stack_file_loader/registry_loader_spec.rb +1 -1
- metadata +7 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b512497470a658b7be3dae937f44e65d8ebc673d867a20e7d5a04bf9dab2b89f
|
4
|
+
data.tar.gz: 8486842c3eba0ee7af57283f108e02fb610a3e83ceb30090e9309fb1eaa3b204
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 41400fae5d6590041c57594981ab97aea90e0e5103c0a0b8ea6215c8d00187259c529786f7230742f347666a525fb1fadfbcf0e6951c6971975e12823a2ba057
|
7
|
+
data.tar.gz: f60a87d8531854e09bea64fd2fa895dff9c6de9c7805ad7497a007910b80ec449d55dd7bed0af81cfacb051ad86dcf65a61ac882ca6a60b1b26151aa566d8d0e
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.5.0.
|
1
|
+
1.5.0.rc1
|
@@ -83,13 +83,13 @@ module Kontena
|
|
83
83
|
command.skip_auth_provider = false
|
84
84
|
when :custom
|
85
85
|
puts
|
86
|
-
puts 'Learn how to configure custom user authentication provider after installation at: www.kontena.io/docs/
|
86
|
+
puts 'Learn how to configure custom user authentication provider after installation at: www.kontena.io/docs/advanced/authentication'
|
87
87
|
puts
|
88
88
|
command.cloud_master_id = nil
|
89
89
|
command.skip_auth_provider = true
|
90
90
|
when :none
|
91
91
|
puts
|
92
|
-
puts "You have selected to use Kontena Master in single user mode. You can configure an authentication provider later. For more information, see here: www.kontena.io/docs/
|
92
|
+
puts "You have selected to use Kontena Master in single user mode. You can configure an authentication provider later. For more information, see here: www.kontena.io/docs/advanced/authentication"
|
93
93
|
puts
|
94
94
|
command.cloud_master_id = nil
|
95
95
|
command.skip_auth_provider = true
|
@@ -7,7 +7,7 @@ module Kontena::Cli::Services
|
|
7
7
|
include ServicesHelper
|
8
8
|
|
9
9
|
parameter "NAME", "Service name"
|
10
|
-
option '--[no-]wait', :flag, '
|
10
|
+
option '--[no-]wait', :flag, 'Wait for service deployment to finish', default: true
|
11
11
|
option '--force', :flag, 'Force deploy even if service does not have any changes'
|
12
12
|
|
13
13
|
def execute
|
@@ -10,7 +10,7 @@ module Kontena::Cli::Stacks
|
|
10
10
|
|
11
11
|
parameter "NAME ...", "Stack name", attribute_name: :names
|
12
12
|
|
13
|
-
option '--[no-]wait', :flag, '
|
13
|
+
option '--[no-]wait', :flag, 'Wait for deployment to finish', default: true
|
14
14
|
|
15
15
|
requires_current_master
|
16
16
|
requires_current_master_token
|
@@ -20,6 +20,8 @@ module Kontena::Cli::Stacks
|
|
20
20
|
option '--parent-name', '[PARENT_NAME]', "Set parent stack name", hidden: true
|
21
21
|
option '--skip-dependencies', :flag, "Do not install any stack dependencies"
|
22
22
|
|
23
|
+
option "--force", :flag, "Force install", default: false, attribute_name: :forced
|
24
|
+
|
23
25
|
requires_current_master
|
24
26
|
requires_current_master_token
|
25
27
|
|
@@ -30,6 +32,15 @@ module Kontena::Cli::Stacks
|
|
30
32
|
|
31
33
|
stack # runs validations
|
32
34
|
|
35
|
+
kontena_requirement = stack.dig('metadata', 'required_kontena_version')
|
36
|
+
unless kontena_requirement.nil?
|
37
|
+
master_version = Gem::Version.new(client.server_version)
|
38
|
+
unless Gem::Requirement.new(kontena_requirement).satisfied_by?(master_version)
|
39
|
+
puts "#{pastel.red("Warning: ")} Stack requires kontena version #{kontena_requirement} but Master version is #{master_version}"
|
40
|
+
confirm("Are you sure? You can skip this prompt by running this command with --force option") unless forced?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
33
44
|
hint_on_validation_notifications(reader.notifications)
|
34
45
|
abort_on_validation_errors(reader.errors)
|
35
46
|
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../common'
|
2
|
+
|
3
|
+
module Kontena::Cli::Stacks::Registry
|
4
|
+
class CreateCommand < Kontena::Command
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
include Kontena::Cli::Stacks::Common
|
7
|
+
include Kontena::Cli::Stacks::Common::RegistryNameParam
|
8
|
+
|
9
|
+
banner "Creates a new Stack to Kontena Stack Registry"
|
10
|
+
|
11
|
+
option '--private', :flag, "Create as private", attribute_name: :is_private
|
12
|
+
|
13
|
+
requires_current_account_token
|
14
|
+
|
15
|
+
def execute
|
16
|
+
exit_with_error "Can't create a stack with a version number" unless stack_name.version.nil?
|
17
|
+
spinner "Creating #{is_private? ? pastel.yellow('private') : 'public'} stack #{pastel.cyan(stack_name)} in Kontena Stack Registry" do
|
18
|
+
stacks_client.create(stack_name, is_private: is_private?)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../common'
|
2
|
+
|
3
|
+
module Kontena::Cli::Stacks::Registry
|
4
|
+
class MakePrivateCommand < Kontena::Command
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
include Kontena::Cli::Stacks::Common
|
7
|
+
include Kontena::Cli::Stacks::Common::RegistryNameParam
|
8
|
+
|
9
|
+
banner "Changes Stack visibility private in the Kontena Cloud Stack Registry"
|
10
|
+
|
11
|
+
option '--force', :flag, "Don't ask for confirmation"
|
12
|
+
|
13
|
+
requires_current_account_token
|
14
|
+
|
15
|
+
def execute
|
16
|
+
unless force?
|
17
|
+
confirm("Change stack #{pastel.cyan(stack_name)} visibility to private?")
|
18
|
+
end
|
19
|
+
spinner "Updating Stack #{pastel.cyan(stack_name)} visibility to private" do
|
20
|
+
stacks_client.make_private(stack_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../common'
|
2
|
+
|
3
|
+
module Kontena::Cli::Stacks::Registry
|
4
|
+
class MakePublicCommand < Kontena::Command
|
5
|
+
include Kontena::Cli::Common
|
6
|
+
include Kontena::Cli::Stacks::Common
|
7
|
+
include Kontena::Cli::Stacks::Common::RegistryNameParam
|
8
|
+
|
9
|
+
banner "Changes Stack visibility private in the Kontena Cloud Stack Registry"
|
10
|
+
|
11
|
+
option '--force', :flag, "Don't ask for confirmation"
|
12
|
+
|
13
|
+
requires_current_account_token
|
14
|
+
|
15
|
+
def execute
|
16
|
+
unless force?
|
17
|
+
confirm("Change stack #{pastel.cyan(stack_name)} visibility to public?")
|
18
|
+
end
|
19
|
+
spinner "Updating Stack #{pastel.cyan(stack_name)} visibility to public" do
|
20
|
+
stacks_client.make_public(stack_name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -14,7 +14,7 @@ module Kontena::Cli::Stacks::Registry
|
|
14
14
|
|
15
15
|
def execute
|
16
16
|
target = no_cache? ? stacks_client : Kontena::StacksCache
|
17
|
-
content = target.pull(stack_name
|
17
|
+
content = target.pull(stack_name)
|
18
18
|
if return?
|
19
19
|
return content
|
20
20
|
elsif file
|
@@ -30,8 +30,7 @@ module Kontena::Cli::Stacks::Registry
|
|
30
30
|
spinner("Pushing #{pastel.cyan(source)} to stack registry as #{loader.stack_name}") do
|
31
31
|
unless dry_run?
|
32
32
|
stacks_client.push(
|
33
|
-
loader.stack_name
|
34
|
-
loader.stack_name.version,
|
33
|
+
loader.stack_name,
|
35
34
|
loader.content
|
36
35
|
)
|
37
36
|
end
|
@@ -4,25 +4,38 @@ module Kontena::Cli::Stacks::Registry
|
|
4
4
|
class SearchCommand < Kontena::Command
|
5
5
|
include Kontena::Cli::Common
|
6
6
|
include Kontena::Cli::Stacks::Common
|
7
|
+
include Kontena::Cli::TableGenerator::Helper
|
7
8
|
|
8
9
|
banner "Search for stacks on the stack registry"
|
9
10
|
|
10
11
|
parameter "[QUERY]", "Query string"
|
11
12
|
|
13
|
+
option ['--[no-]pre'], :flag, "Include pre-release versions", default: true
|
14
|
+
option ['--[no-]private'], :flag, "Include private stacks", default: true, attribute_name: :priv
|
15
|
+
|
16
|
+
option ['--tag', '-t'], '[TAG]', "Search by tags", multivalued: true
|
17
|
+
|
12
18
|
option ['-q', '--quiet'], :flag, "Output the identifying column only"
|
13
19
|
|
20
|
+
def fields
|
21
|
+
quiet? ? ['name'] : %w(name version pulls description)
|
22
|
+
end
|
23
|
+
|
14
24
|
def execute
|
15
|
-
results = stacks_client.search(query.to_s)
|
16
|
-
if quiet?
|
17
|
-
puts results.map { |s| s['stack'] }.join("\n")
|
18
|
-
exit 0
|
19
|
-
end
|
25
|
+
results = stacks_client.search(query.to_s, tags: tag_list, include_prerelease: pre?, include_private: priv?)
|
20
26
|
exit_with_error 'Nothing found' if results.empty?
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
print_table(results.map { |r| r['attributes'] }) do |row|
|
28
|
+
next if quiet?
|
29
|
+
row['name'] = '%s/%s' % [row['organization-id'], row['name']]
|
30
|
+
row['name'] = pastel.yellow(row['name']) if row['is-private']
|
31
|
+
if row['latest-version'] && row['latest-version']['version']
|
32
|
+
row['version'] = row['latest-version']['version']
|
33
|
+
row['description'] = row['latest-version']['description']
|
34
|
+
else
|
35
|
+
row['version'] = '?'
|
36
|
+
end
|
37
|
+
|
38
|
+
row['description'] = '-' if row['description'].to_s.empty?
|
26
39
|
end
|
27
40
|
end
|
28
41
|
end
|
@@ -13,19 +13,52 @@ module Kontena::Cli::Stacks::Registry
|
|
13
13
|
requires_current_account_token
|
14
14
|
|
15
15
|
def execute
|
16
|
-
|
17
|
-
unless versions?
|
18
|
-
stack = ::YAML.safe_load(stacks_client.show(stack_name.stack_name, stack_name.version), [], [], true)
|
19
|
-
puts "#{stack['stack']}:"
|
20
|
-
puts " #{"latest_" unless stack_name.version}version: #{stack['version']}"
|
21
|
-
puts " expose: #{stack['expose'] || '-'}"
|
22
|
-
puts " description: #{stack['description'] || '-'}"
|
16
|
+
versions = stacks_client.versions(stack_name)
|
23
17
|
|
24
|
-
|
25
|
-
|
18
|
+
if versions?
|
19
|
+
stacks_client.versions(stack_name).each do |version|
|
20
|
+
puts version['attributes']['version']
|
21
|
+
end
|
22
|
+
else
|
23
|
+
data = stacks_client.show(stack_name).dig('data', 'attributes')
|
24
|
+
puts "#{data['organization-id']}/#{data['name']}:"
|
25
|
+
puts " description: #{data.dig('latest-version', 'description') || '-'}"
|
26
|
+
puts " latest_version: #{data.dig('latest-version', 'version') || '-'}"
|
27
|
+
puts " created_at: #{data.dig('created-at')}"
|
28
|
+
puts " pulls: #{data.dig('pulls')}"
|
29
|
+
puts " private: #{data.dig('is-private')}"
|
30
|
+
meta = data.dig('latest-version', 'meta')
|
31
|
+
if meta
|
32
|
+
puts " meta:"
|
33
|
+
readme = meta.delete('readme')
|
34
|
+
meta_lines = YAML.dump(meta).split(/[\r\n]/)
|
35
|
+
meta_lines.shift
|
36
|
+
meta_lines.each do |meta_line|
|
37
|
+
puts " %s" % meta_line
|
38
|
+
end
|
39
|
+
if readme
|
40
|
+
if readme =~ /^http\S+$/
|
41
|
+
puts " readme: readme"
|
42
|
+
else
|
43
|
+
puts " readme: |"
|
44
|
+
readme.gsub!(/(\S{#{70}})(?=\S)/, '\1 ')
|
45
|
+
readme.gsub!(/(.{1,#{70}})(?:\s+|$)/, "\\1\n")
|
46
|
+
readme.gsub!(/^/, ' ')
|
47
|
+
puts readme
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
puts " meta: -"
|
52
|
+
end
|
26
53
|
|
27
|
-
|
28
|
-
|
54
|
+
if versions.empty?
|
55
|
+
puts " versions: -"
|
56
|
+
else
|
57
|
+
puts " versions:"
|
58
|
+
versions.each do |version|
|
59
|
+
puts " - #{version['attributes']['version']} (#{version['attributes']['created-at']})"
|
60
|
+
end
|
61
|
+
end
|
29
62
|
end
|
30
63
|
end
|
31
64
|
end
|
@@ -5,5 +5,8 @@ module Kontena::Cli::Stacks
|
|
5
5
|
subcommand ["search"], "Search for stacks in the stacks registry", load_subcommand('stacks/registry/search_command')
|
6
6
|
subcommand "show", "Show info about a stack in the stacks registry", load_subcommand('stacks/registry/show_command')
|
7
7
|
subcommand ["remove", "rm"], "Remove a stack (or version) from the stacks registry", load_subcommand('stacks/registry/remove_command')
|
8
|
+
subcommand "create", "Create a stack in the registry", load_subcommand('stacks/registry/create_command')
|
9
|
+
subcommand "make-private", "Change Stack visibility to private", load_subcommand('stacks/registry/make_private_command')
|
10
|
+
subcommand "make-public", "Change Stack visibility to public", load_subcommand('stacks/registry/make_public_command')
|
8
11
|
end
|
9
12
|
end
|
@@ -26,6 +26,10 @@ module Kontena::Cli::Stacks
|
|
26
26
|
@variables ||= stack['variables'] || {}
|
27
27
|
end
|
28
28
|
|
29
|
+
def metadata
|
30
|
+
@metadata ||= stack['metadata'] || {}
|
31
|
+
end
|
32
|
+
|
29
33
|
def stack
|
30
34
|
@stack ||= client.get("stacks/#{current_grid}/#{name}")
|
31
35
|
end
|
@@ -41,7 +45,7 @@ module Kontena::Cli::Stacks
|
|
41
45
|
def write_variables
|
42
46
|
File.write(values_to, variable_yaml)
|
43
47
|
end
|
44
|
-
|
48
|
+
|
45
49
|
def show_stack
|
46
50
|
puts "#{stack['name']}:"
|
47
51
|
puts " created: #{stack['created_at']}"
|
@@ -51,10 +55,19 @@ module Kontena::Cli::Stacks
|
|
51
55
|
puts " version: #{stack['version']}"
|
52
56
|
puts " revision: #{stack['revision']}"
|
53
57
|
puts " expose: #{stack['expose'] || '-'}"
|
58
|
+
|
54
59
|
puts " variables:#{' -' if variables.empty?}"
|
55
60
|
variables.each do |var, val|
|
56
61
|
puts " #{var}: #{val}"
|
57
62
|
end
|
63
|
+
|
64
|
+
puts " metadata:#{' -' if metadata.empty?}"
|
65
|
+
unless metadata.empty?
|
66
|
+
output_lines = ::YAML.dump(metadata).split(/[\r\n]/)
|
67
|
+
output_lines.shift # get rid of "---"
|
68
|
+
puts output_lines.map { |line| ' %s' % line }.join("\n")
|
69
|
+
end
|
70
|
+
|
58
71
|
puts " parent: #{stack['parent'] ? stack['parent']['name'] : '-'}"
|
59
72
|
if stack['children'] && !stack['children'].empty?
|
60
73
|
puts " children:"
|
@@ -62,6 +75,7 @@ module Kontena::Cli::Stacks
|
|
62
75
|
puts " - #{child['name']}"
|
63
76
|
end
|
64
77
|
end
|
78
|
+
|
65
79
|
puts " services:"
|
66
80
|
stack['services'].each do |service|
|
67
81
|
show_service(service['id'])
|
@@ -33,6 +33,15 @@ module Kontena::Cli::Stacks
|
|
33
33
|
gather_master_data(stack_name)
|
34
34
|
end
|
35
35
|
|
36
|
+
kontena_requirement = loader.yaml.dig('meta', 'required_kontena_version')
|
37
|
+
unless kontena_requirement.nil?
|
38
|
+
master_version = Gem::Version.new(client.server_version)
|
39
|
+
unless Gem::Requirement.new(kontena_requirement).satisfied_by?(master_version)
|
40
|
+
puts "#{pastel.red("Warning: ")} Stack requires kontena version #{kontena_requirement} but Master version is #{master_version}"
|
41
|
+
confirm("Are you sure? You can skip this prompt by running this command with --force option") unless force?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
36
45
|
new_data = spinner "Parsing #{pastel.cyan(source)}" do
|
37
46
|
loader.flat_dependencies(
|
38
47
|
stack_name,
|
@@ -213,7 +213,9 @@ module Kontena::Cli::Stacks
|
|
213
213
|
result['dependencies'] = dependencies
|
214
214
|
result['source'] = raw_content
|
215
215
|
result['variables'] = variable_values(without_defaults: true, without_vault: true)
|
216
|
+
result['metadata'] = raw_yaml['meta'] || {}
|
216
217
|
end
|
218
|
+
|
217
219
|
if parent_name
|
218
220
|
result['parent'] = { 'name' => parent_name }
|
219
221
|
else
|
data/lib/kontena/client.rb
CHANGED
@@ -542,6 +542,14 @@ module Kontena
|
|
542
542
|
|
543
543
|
if data.is_a?(Hash) && data.has_key?('error') && data['error'].is_a?(Hash)
|
544
544
|
raise Kontena::Errors::StandardErrorHash.new(response.status, response.reason_phrase, data['error'])
|
545
|
+
elsif data.is_a?(Hash) && data.has_key?('errors') && data['errors'].is_a?(Array) && data['errors'].all? { |e| e.is_a?(Hash) }
|
546
|
+
error_with_status = data['errors'].find { |error| error.key?('status') }
|
547
|
+
if error_with_status
|
548
|
+
status = error_with_status['status']
|
549
|
+
else
|
550
|
+
status = response.status
|
551
|
+
end
|
552
|
+
raise Kontena::Errors::StandardErrorHash.new(status, response.reason_phrase, data)
|
545
553
|
elsif data.is_a?(Hash) && data.has_key?('error')
|
546
554
|
raise Kontena::Errors::StandardError.new(response.status, data['error'] + request_path)
|
547
555
|
elsif data.is_a?(String) && !data.empty?
|
@@ -292,11 +292,11 @@ begin
|
|
292
292
|
logs monitor show registry inspect)
|
293
293
|
if words[1]
|
294
294
|
if words[1] == 'registry' || words[1] == 'reg'
|
295
|
-
registry_sub_commands = %(push pull search show remove)
|
295
|
+
registry_sub_commands = %(push pull search show remove make-public make-private create)
|
296
296
|
if words[2]
|
297
297
|
if words[2] == 'push'
|
298
298
|
completion.push helper.yml_files(words[3])
|
299
|
-
elsif %w(pull search show remove rm).include?(words[2]) && words[4].nil?
|
299
|
+
elsif %w(pull search show remove rm make-public make-private).include?(words[2]) && words[4].nil?
|
300
300
|
completion.push helper.registry_stacks(words[3].to_s)
|
301
301
|
else
|
302
302
|
completion.push registry_sub_commands
|
data/lib/kontena/stacks_cache.rb
CHANGED
@@ -4,15 +4,10 @@ module Kontena
|
|
4
4
|
class StacksCache
|
5
5
|
class CachedStack
|
6
6
|
|
7
|
-
|
8
|
-
attr_accessor :version
|
7
|
+
attr_reader :stack_name
|
9
8
|
|
10
|
-
def initialize(
|
11
|
-
|
12
|
-
stack, version = stack.split(':', 2)
|
13
|
-
end
|
14
|
-
@stack = stack
|
15
|
-
@version = version
|
9
|
+
def initialize(stack_name)
|
10
|
+
@stack_name = stack_name
|
16
11
|
end
|
17
12
|
|
18
13
|
def read
|
@@ -24,7 +19,8 @@ module Kontena
|
|
24
19
|
end
|
25
20
|
|
26
21
|
def write(content)
|
27
|
-
|
22
|
+
puts "WHATHAT??? #{stack_name.inspect} #{stack_name.version} #{stack_name.stack_name}"
|
23
|
+
raise ArgumentError, "Stack name and version required" unless stack_name.stack_name && stack_name.version
|
28
24
|
unless File.directory?(File.dirname(path))
|
29
25
|
require 'fileutils'
|
30
26
|
FileUtils.mkdir_p(File.dirname(path))
|
@@ -37,12 +33,12 @@ module Kontena
|
|
37
33
|
end
|
38
34
|
|
39
35
|
def cached?
|
40
|
-
return false unless version
|
36
|
+
return false unless stack_name.version
|
41
37
|
File.exist?(path)
|
42
38
|
end
|
43
39
|
|
44
40
|
def path
|
45
|
-
path = File.expand_path(File.join(base_path, "#{
|
41
|
+
path = File.expand_path(File.join(base_path, "#{stack_name.stack_name}-#{stack_name.version}.yml"))
|
46
42
|
raise "Path traversal attempted" unless path.start_with?(base_path)
|
47
43
|
path
|
48
44
|
end
|
@@ -62,39 +58,40 @@ module Kontena
|
|
62
58
|
end
|
63
59
|
|
64
60
|
class << self
|
65
|
-
def pull(
|
66
|
-
cache(
|
61
|
+
def pull(stack_name)
|
62
|
+
cache(stack_name).read
|
67
63
|
end
|
68
64
|
|
69
65
|
def dputs(msg)
|
70
66
|
Kontena.logger.debug { msg }
|
71
67
|
end
|
72
68
|
|
73
|
-
def cache(
|
74
|
-
stack = CachedStack.new(
|
69
|
+
def cache(stack_name)
|
70
|
+
stack = CachedStack.new(stack_name)
|
75
71
|
if stack.cached?
|
76
72
|
dputs "Reading from cache: #{stack.path}"
|
77
73
|
else
|
78
|
-
dputs "Retrieving #{stack.
|
79
|
-
content = client.pull(
|
80
|
-
yaml = ::YAML.safe_load(content, [], [], true, stack.
|
81
|
-
|
74
|
+
dputs "Retrieving #{stack.stack_name} from registry"
|
75
|
+
content = client.pull(stack_name)
|
76
|
+
yaml = ::YAML.safe_load(content, [], [], true, stack.stack_name.to_s)
|
77
|
+
new_stack_name = Kontena::Cli::Stacks::StackName.new(yaml['stack'], yaml['version'])
|
78
|
+
puts new_stack_name.inspect
|
79
|
+
new_stack = CachedStack.new(new_stack_name)
|
82
80
|
if new_stack.cached?
|
83
81
|
dputs "Already cached"
|
84
82
|
stack = new_stack
|
85
83
|
else
|
86
|
-
stack.stack = yaml['stack']
|
87
|
-
stack.version = yaml['version']
|
88
84
|
dputs "Writing #{stack.path}"
|
89
|
-
|
90
|
-
dputs "#{
|
85
|
+
new_stack.write(content)
|
86
|
+
dputs "#{new_stack.stack_name} cached to #{new_stack.path}"
|
87
|
+
stack = new_stack
|
91
88
|
end
|
92
89
|
end
|
93
90
|
stack
|
94
91
|
end
|
95
92
|
|
96
|
-
def registry_url(
|
97
|
-
client.full_uri(
|
93
|
+
def registry_url(stack_name)
|
94
|
+
client.full_uri(stack_name)
|
98
95
|
end
|
99
96
|
|
100
97
|
def client
|
@@ -3,9 +3,11 @@ require 'kontena/client'
|
|
3
3
|
module Kontena
|
4
4
|
class StacksClient < Client
|
5
5
|
|
6
|
-
ACCEPT_JSON
|
7
|
-
ACCEPT_YAML
|
8
|
-
|
6
|
+
ACCEPT_JSON = { 'Accept' => 'application/json' }
|
7
|
+
ACCEPT_YAML = { 'Accept' => 'application/yaml' }
|
8
|
+
ACCEPT_JSONAPI = { 'Accept' => 'application/vnd.api+json' }
|
9
|
+
CT_YAML = { 'Content-Type' => 'application/yaml' }
|
10
|
+
CT_JSONAPI = { 'Content-Type' => 'application/vnd.api+json' }
|
9
11
|
|
10
12
|
def raise_unless_token
|
11
13
|
unless token && token['access_token']
|
@@ -20,45 +22,156 @@ module Kontena
|
|
20
22
|
end
|
21
23
|
end
|
22
24
|
|
23
|
-
def full_uri(stack_name
|
24
|
-
URI.join(api_url,
|
25
|
+
def full_uri(stack_name)
|
26
|
+
URI.join(api_url, path_to_version(stack_name)).to_s
|
25
27
|
end
|
26
28
|
|
27
|
-
def
|
28
|
-
|
29
|
+
def path_to_version(stack_name)
|
30
|
+
path_to_stack(stack_name) + "/stack-versions/%s" % (stack_name.version || 'latest')
|
29
31
|
end
|
30
32
|
|
31
|
-
def
|
33
|
+
def path_to_stack(stack_name)
|
34
|
+
"/v2/organizations/%s/stacks/%s" % [stack_name.user, stack_name.stack]
|
35
|
+
end
|
36
|
+
|
37
|
+
def push(stack_name, data)
|
32
38
|
raise_unless_token
|
33
|
-
post(
|
39
|
+
post(
|
40
|
+
'/v2/stack-files',
|
41
|
+
{
|
42
|
+
'data' => {
|
43
|
+
'type' => 'stack-files',
|
44
|
+
'attributes' => { 'content' => data }
|
45
|
+
}
|
46
|
+
},
|
47
|
+
{},
|
48
|
+
CT_JSONAPI,
|
49
|
+
true
|
50
|
+
)
|
34
51
|
end
|
35
52
|
|
36
|
-
def show(stack_name,
|
53
|
+
def show(stack_name, include_prerelease: true)
|
37
54
|
raise_unless_read_token
|
38
|
-
get("#{
|
55
|
+
result = get("#{path_to_stack(stack_name)}", { 'include' => 'latest-version', 'include-prerelease' => include_prerelease }, ACCEPT_JSONAPI)
|
56
|
+
if result['included']
|
57
|
+
latest = result['included'].find { |i| i['type'] == 'stack-versions' }
|
58
|
+
return result unless latest
|
59
|
+
result['data']['attributes']['latest-version'] = {}
|
60
|
+
result['data']['attributes']['latest-version']['version'] = latest['attributes']['version']
|
61
|
+
result['data']['attributes']['latest-version']['description'] = latest['attributes']['description']
|
62
|
+
result['data']['attributes']['latest-version']['meta'] = latest['meta']
|
63
|
+
end
|
64
|
+
result
|
39
65
|
end
|
40
66
|
|
41
|
-
def versions(stack_name)
|
67
|
+
def versions(stack_name, include_prerelease: true, include_deleted: false)
|
42
68
|
raise_unless_read_token
|
43
|
-
get("#{
|
69
|
+
get("#{path_to_stack(stack_name)}/stack-versions", { 'include-prerelease' => include_prerelease, 'include-deleted' => include_deleted}, ACCEPT_JSONAPI).dig('data')
|
44
70
|
end
|
45
71
|
|
46
|
-
def pull(stack_name
|
72
|
+
def pull(stack_name)
|
47
73
|
raise_unless_read_token
|
48
|
-
get(
|
74
|
+
get(path_to_version(stack_name) + '/yaml', nil, ACCEPT_YAML)
|
49
75
|
rescue StandardError => ex
|
50
|
-
ex.message << " : #{
|
76
|
+
ex.message << " : #{path_to_version(stack_name)}"
|
51
77
|
raise ex, ex.message
|
52
78
|
end
|
53
79
|
|
54
|
-
def search(query)
|
80
|
+
def search(query, tags: [], include_prerelease: true, include_private: true, include_versionless: true)
|
55
81
|
raise_unless_read_token
|
56
|
-
|
82
|
+
if tags.empty?
|
83
|
+
result = get('/v2/stacks', { 'query' => query, 'include' => 'latest-version', 'include-prerelease' => include_prerelease, 'include-private' => include_private, 'include-versionless' => include_versionless }, ACCEPT_JSONAPI)
|
84
|
+
else
|
85
|
+
result = get('/v2/tags/%s/stacks' % tags.join(','), { 'query' => query, 'include' => 'latest-version', 'include-prerelease' => include_prerelease, 'include-private' => include_private }, ACCEPT_JSONAPI)
|
86
|
+
end
|
87
|
+
|
88
|
+
data = result.dig('data')
|
89
|
+
included = result.dig('included')
|
90
|
+
if included
|
91
|
+
data.each do |row|
|
92
|
+
name = '%s/%s' % [row.fetch('attributes', {}).fetch('organization-id'), row.fetch('attributes', {}).fetch('name')]
|
93
|
+
next if name.nil?
|
94
|
+
included_version = included.find { |i| i.fetch('attributes', {}).fetch('name') == name }
|
95
|
+
if included_version
|
96
|
+
row['attributes']['latest-version'] = {}
|
97
|
+
row['attributes']['latest-version']['version'] = included_version['attributes']['version']
|
98
|
+
row['attributes']['latest-version']['description'] = included_version['attributes']['description']
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
data
|
103
|
+
end
|
104
|
+
|
105
|
+
def destroy(stack_name)
|
106
|
+
raise_unless_token
|
107
|
+
if stack_name.version
|
108
|
+
id = stack_version_id(stack_name)
|
109
|
+
if id.nil?
|
110
|
+
raise Kontena::Errors::StandardError.new(404, 'Not found')
|
111
|
+
end
|
112
|
+
delete('/v2/stack-versions/%s' % id, nil, {}, ACCEPT_JSONAPI)
|
113
|
+
else
|
114
|
+
id = stack_id(stack_name)
|
115
|
+
if id.nil?
|
116
|
+
raise Kontena::Errors::StandardError.new(404, 'Not found')
|
117
|
+
end
|
118
|
+
delete('/v2/stacks/%s' % id, nil, {}, ACCEPT_JSONAPI)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def make_private(stack_name)
|
123
|
+
change_visibility(stack_name, is_private: true)
|
57
124
|
end
|
58
125
|
|
59
|
-
def
|
126
|
+
def make_public(stack_name)
|
127
|
+
change_visibility(stack_name, is_private: false)
|
128
|
+
end
|
129
|
+
|
130
|
+
def create(stack_name, is_private: true)
|
131
|
+
post(
|
132
|
+
'/v2/stacks',
|
133
|
+
stack_data(stack_name, is_private: is_private),
|
134
|
+
{},
|
135
|
+
CT_JSONAPI.merge(ACCEPT_JSONAPI)
|
136
|
+
)
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
def stack_id(stack_name)
|
142
|
+
show(stack_name).dig('data', 'id')
|
143
|
+
end
|
144
|
+
|
145
|
+
def stack_version_id(stack_name)
|
146
|
+
version = versions(stack_name, include_prerelease: true).find { |v| v.dig('attributes', 'version') == stack_name.version }
|
147
|
+
if version
|
148
|
+
version['id']
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def change_visibility(stack_name, is_private: true)
|
60
155
|
raise_unless_token
|
61
|
-
|
156
|
+
put(
|
157
|
+
'/v2/stacks/%s' % stack_id(stack_name),
|
158
|
+
stack_data(stack_name, is_private: is_private),
|
159
|
+
{},
|
160
|
+
CT_JSONAPI.merge(ACCEPT_JSONAPI)
|
161
|
+
)
|
162
|
+
end
|
163
|
+
|
164
|
+
def stack_data(stack_name, is_private: true)
|
165
|
+
{
|
166
|
+
data: {
|
167
|
+
type: 'stacks',
|
168
|
+
attributes: {
|
169
|
+
'name' => stack_name.stack,
|
170
|
+
'organization-id' => stack_name.user,
|
171
|
+
'is-private' => is_private
|
172
|
+
}
|
173
|
+
}
|
174
|
+
}
|
62
175
|
end
|
63
176
|
end
|
64
177
|
end
|
data/omnibus/wrappers/sh/kontena
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
stack: user/stackname
|
2
|
+
version: 0.1.1
|
3
|
+
services:
|
4
|
+
wordpress:
|
5
|
+
extends:
|
6
|
+
file: docker-compose_v2.yml
|
7
|
+
service: wordpress
|
8
|
+
stateful: true
|
9
|
+
environment:
|
10
|
+
WORDPRESS_DB_PASSWORD: ${STACK}_secret
|
11
|
+
instances: 2
|
12
|
+
deploy:
|
13
|
+
strategy: ha
|
14
|
+
mysql:
|
15
|
+
extends:
|
16
|
+
file: docker-compose_v2.yml
|
17
|
+
service: mysql
|
18
|
+
stateful: true
|
19
|
+
environment:
|
20
|
+
- MYSQL_ROOT_PASSWORD=${STACK}_secret
|
21
|
+
meta:
|
22
|
+
tags:
|
23
|
+
- tag1
|
24
|
+
- tag2
|
25
|
+
readme: |
|
26
|
+
Text goes
|
27
|
+
here
|
28
|
+
required_kontena_version: ">= 0.5.0"
|
@@ -10,6 +10,7 @@ describe Kontena::Cli::Stacks::InstallCommand do
|
|
10
10
|
|
11
11
|
before(:each) do
|
12
12
|
ENV['STACK'] = nil
|
13
|
+
allow(client).to receive(:server_version).and_return(Kontena::Cli::VERSION)
|
13
14
|
end
|
14
15
|
|
15
16
|
describe '#execute' do
|
@@ -25,7 +26,8 @@ describe Kontena::Cli::Stacks::InstallCommand do
|
|
25
26
|
'dependencies' => nil,
|
26
27
|
'source' => /stack:/,
|
27
28
|
'parent' => nil,
|
28
|
-
'expose' => nil
|
29
|
+
'expose' => nil,
|
30
|
+
'metadata' => {}
|
29
31
|
}
|
30
32
|
end
|
31
33
|
|
@@ -85,6 +87,29 @@ describe Kontena::Cli::Stacks::InstallCommand do
|
|
85
87
|
subject.run(['-n', 'deptest', '--no-deploy', '-v', 'dep_1.dep_1.dep_var=1', fixture_path('stack-with-dependencies.yml')])
|
86
88
|
end
|
87
89
|
end
|
90
|
+
|
91
|
+
context 'with a stack including metadata' do
|
92
|
+
let(:stack_meta_expectation) do
|
93
|
+
stack_expectation.merge(
|
94
|
+
'metadata' => hash_including(
|
95
|
+
'tags' => array_including('tag1', 'tag2'),
|
96
|
+
'readme' => /Text goes/,
|
97
|
+
'required_kontena_version' => ">= 0.5.0"
|
98
|
+
)
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'includes the metadata in the json sent to master' do
|
103
|
+
expect(client).to receive(:post).with('grids/test-grid/stacks', hash_including(stack_meta_expectation))
|
104
|
+
subject.run(['--no-deploy', fixture_path('kontena_v3_with_metadata.yml')])
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'requires force if master version does not match metadata required_kontena_version' do
|
108
|
+
expect(client).to receive(:server_version).and_return('0.2.0')
|
109
|
+
expect(subject).to receive(:confirm).and_call_original
|
110
|
+
expect{subject.run(['--no-deploy', fixture_path('kontena_v3_with_metadata.yml')])}.to exit_with_error.and output(/version/).to_stdout
|
111
|
+
end
|
112
|
+
end
|
88
113
|
end
|
89
114
|
|
90
115
|
context 'variable value input' do
|
@@ -11,6 +11,7 @@ describe Kontena::Cli::Stacks::UpgradeCommand do
|
|
11
11
|
|
12
12
|
before(:each) do
|
13
13
|
ENV['STACK'] = nil
|
14
|
+
allow(client).to receive(:server_version).and_return(Kontena::Cli::VERSION)
|
14
15
|
end
|
15
16
|
|
16
17
|
describe '#execute' do
|
@@ -69,6 +70,13 @@ describe Kontena::Cli::Stacks::UpgradeCommand do
|
|
69
70
|
subject.run(['--force', 'stack-a', fixture_path('kontena_v3.yml')])
|
70
71
|
end
|
71
72
|
|
73
|
+
it 'requires force if master version does not match metadata required_kontena_version' do
|
74
|
+
expect(client).to receive(:get).with('stacks/test-grid/stack-a').and_return(stack_response)
|
75
|
+
expect(client).to receive(:server_version).and_return('0.2.0')
|
76
|
+
expect(subject).to receive(:confirm).and_call_original
|
77
|
+
expect{subject.run(['--no-deploy', 'stack-a', fixture_path('kontena_v3_with_metadata.yml')])}.to exit_with_error.and output(/version/).to_stdout
|
78
|
+
end
|
79
|
+
|
72
80
|
context '--no-deploy option' do
|
73
81
|
it 'does not trigger deploy' do
|
74
82
|
expect(client).to receive(:get).with('stacks/test-grid/stack-a').and_return(stack_response)
|
@@ -79,7 +87,6 @@ describe Kontena::Cli::Stacks::UpgradeCommand do
|
|
79
87
|
subject.run(['--no-deploy', '--force', 'stack-a', fixture_path('kontena_v3.yml')])
|
80
88
|
end
|
81
89
|
end
|
82
|
-
|
83
90
|
context 'with a stack including dependencies' do
|
84
91
|
|
85
92
|
let(:expectation) {{ 'name' => 'deptest', 'stack' => 'user/depstack1' }}
|
@@ -61,8 +61,7 @@ describe Kontena::Cli::Stacks::YAML::Reader do
|
|
61
61
|
|
62
62
|
it 'returns result hash' do
|
63
63
|
result = subject.execute
|
64
|
-
|
65
|
-
%w(
|
64
|
+
top_level_fields = %w(
|
66
65
|
stack
|
67
66
|
version
|
68
67
|
name
|
@@ -74,9 +73,9 @@ describe Kontena::Cli::Stacks::YAML::Reader do
|
|
74
73
|
source
|
75
74
|
variables
|
76
75
|
parent
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
metadata
|
77
|
+
)
|
78
|
+
expect(result).to match hash_including(*top_level_fields)
|
80
79
|
end
|
81
80
|
|
82
81
|
context 'when extending services' do
|
@@ -178,7 +177,9 @@ describe Kontena::Cli::Stacks::YAML::Reader do
|
|
178
177
|
end
|
179
178
|
|
180
179
|
it 'extends services from a registry stack' do
|
181
|
-
expect(Kontena::StacksCache).to receive(:pull).at_least(:once)
|
180
|
+
expect(Kontena::StacksCache).to receive(:pull).at_least(:once) do |stackname|
|
181
|
+
expect(stackname.to_s).to eq 'registrystack/compose:1.0.0'
|
182
|
+
end.and_return(File.read(fixture_path('docker-compose_v2.yml')))
|
182
183
|
expect(subject.execute['services']).to match array_including(
|
183
184
|
hash_including(
|
184
185
|
"instances"=>2,
|
@@ -332,8 +333,7 @@ describe Kontena::Cli::Stacks::YAML::Reader do
|
|
332
333
|
|
333
334
|
it 'discards empty lines' do
|
334
335
|
result = env_file
|
335
|
-
result <<
|
336
|
-
'
|
336
|
+
result << " \n \n"
|
337
337
|
allow(File).to receive(:readlines).with('.env').and_return(result)
|
338
338
|
variables = subject.send(:read_env_file, '.env')
|
339
339
|
expect(variables).to eq([
|
@@ -23,7 +23,7 @@ describe Kontena::Cli::Stacks::YAML::RegistryLoader do
|
|
23
23
|
let(:subject) { described_class.new('user/stack') }
|
24
24
|
|
25
25
|
before do
|
26
|
-
allow(Kontena::StacksCache).to receive(:pull).with(
|
26
|
+
allow(Kontena::StacksCache).to receive(:pull).with(duck_type(:stack_name)).and_return(
|
27
27
|
fixture('kontena_v3.yml')
|
28
28
|
)
|
29
29
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kontena-cli
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.5.0.
|
4
|
+
version: 1.5.0.rc1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kontena, Inc
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02-
|
11
|
+
date: 2018-02-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -407,6 +407,9 @@ files:
|
|
407
407
|
- lib/kontena/cli/stacks/list_command.rb
|
408
408
|
- lib/kontena/cli/stacks/logs_command.rb
|
409
409
|
- lib/kontena/cli/stacks/monitor_command.rb
|
410
|
+
- lib/kontena/cli/stacks/registry/create_command.rb
|
411
|
+
- lib/kontena/cli/stacks/registry/make_private_command.rb
|
412
|
+
- lib/kontena/cli/stacks/registry/make_public_command.rb
|
410
413
|
- lib/kontena/cli/stacks/registry/pull_command.rb
|
411
414
|
- lib/kontena/cli/stacks/registry/push_command.rb
|
412
415
|
- lib/kontena/cli/stacks/registry/remove_command.rb
|
@@ -535,6 +538,7 @@ files:
|
|
535
538
|
- spec/fixtures/kontena_build_v3.yml
|
536
539
|
- spec/fixtures/kontena_v3.yml
|
537
540
|
- spec/fixtures/kontena_v3_with_compose_variables.yml
|
541
|
+
- spec/fixtures/kontena_v3_with_metadata.yml
|
538
542
|
- spec/fixtures/kontena_v3_with_registry_extends.yml
|
539
543
|
- spec/fixtures/stack-internal-extend.yml
|
540
544
|
- spec/fixtures/stack-invalid.yml
|
@@ -718,6 +722,7 @@ test_files:
|
|
718
722
|
- spec/fixtures/kontena_build_v3.yml
|
719
723
|
- spec/fixtures/kontena_v3.yml
|
720
724
|
- spec/fixtures/kontena_v3_with_compose_variables.yml
|
725
|
+
- spec/fixtures/kontena_v3_with_metadata.yml
|
721
726
|
- spec/fixtures/kontena_v3_with_registry_extends.yml
|
722
727
|
- spec/fixtures/stack-internal-extend.yml
|
723
728
|
- spec/fixtures/stack-invalid.yml
|