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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/VERSION +1 -1
- data/bin/kontena +1 -1
- data/kontena-cli.gemspec +3 -3
- data/lib/kontena/cli/certificate/authorize_command.rb +67 -6
- data/lib/kontena/cli/certificate/get_command.rb +7 -0
- data/lib/kontena/cli/certificate/list_command.rb +75 -0
- data/lib/kontena/cli/certificate/register_command.rb +13 -2
- data/lib/kontena/cli/certificate/request_command.rb +20 -0
- data/lib/kontena/cli/certificate/show_command.rb +19 -0
- data/lib/kontena/cli/certificate_command.rb +4 -1
- data/lib/kontena/cli/cloud/master/add_command.rb +1 -1
- data/lib/kontena/cli/common.rb +21 -33
- data/lib/kontena/cli/etcd/health_command.rb +21 -27
- data/lib/kontena/cli/helpers/exec_helper.rb +15 -6
- data/lib/kontena/cli/helpers/health_helper.rb +12 -0
- data/lib/kontena/cli/helpers/log_helper.rb +2 -2
- data/lib/kontena/cli/helpers/time_helper.rb +29 -0
- data/lib/kontena/cli/master/init_cloud_command.rb +19 -0
- data/lib/kontena/cli/master/list_command.rb +1 -1
- data/lib/kontena/cli/master/ssh_command.rb +3 -1
- data/lib/kontena/cli/master/use_command.rb +1 -2
- data/lib/kontena/cli/node_command.rb +1 -0
- data/lib/kontena/cli/nodes/health_command.rb +28 -13
- data/lib/kontena/cli/nodes/list_command.rb +19 -3
- data/lib/kontena/cli/nodes/show_command.rb +4 -2
- data/lib/kontena/cli/nodes/ssh_command.rb +5 -2
- data/lib/kontena/cli/nodes/update_command.rb +2 -0
- data/lib/kontena/cli/plugins/install_command.rb +11 -8
- data/lib/kontena/cli/plugins/list_command.rb +5 -3
- data/lib/kontena/cli/plugins/search_command.rb +4 -2
- data/lib/kontena/cli/plugins/show_command.rb +17 -0
- data/lib/kontena/cli/plugins/uninstall_command.rb +9 -13
- data/lib/kontena/cli/registry/create_command.rb +1 -1
- data/lib/kontena/cli/services/create_command.rb +6 -0
- data/lib/kontena/cli/services/services_helper.rb +33 -6
- data/lib/kontena/cli/services/update_command.rb +6 -0
- data/lib/kontena/cli/stacks/build_command.rb +3 -3
- data/lib/kontena/cli/stacks/common.rb +105 -90
- data/lib/kontena/cli/stacks/deploy_command.rb +7 -3
- data/lib/kontena/cli/stacks/install_command.rb +39 -6
- data/lib/kontena/cli/stacks/list_command.rb +36 -4
- data/lib/kontena/cli/stacks/logs_command.rb +9 -2
- data/lib/kontena/cli/stacks/registry/pull_command.rb +2 -2
- data/lib/kontena/cli/stacks/registry/push_command.rb +20 -9
- data/lib/kontena/cli/stacks/registry/remove_command.rb +4 -4
- data/lib/kontena/cli/stacks/registry/show_command.rb +4 -4
- data/lib/kontena/cli/stacks/remove_command.rb +27 -1
- data/lib/kontena/cli/stacks/service_generator.rb +12 -2
- data/lib/kontena/cli/stacks/show_command.rb +35 -5
- data/lib/kontena/cli/stacks/stack_name.rb +71 -0
- data/lib/kontena/cli/stacks/upgrade_command.rb +127 -14
- data/lib/kontena/cli/stacks/validate_command.rb +38 -10
- data/lib/kontena/cli/stacks/yaml/custom_validators/certificates_validator.rb +22 -0
- data/lib/kontena/cli/stacks/yaml/opto/prompt_resolver.rb +1 -2
- data/lib/kontena/cli/stacks/yaml/reader.rb +211 -185
- data/lib/kontena/cli/stacks/yaml/service_extender.rb +6 -12
- data/lib/kontena/cli/stacks/yaml/stack_file_loader.rb +97 -0
- data/lib/kontena/cli/stacks/yaml/stack_file_loader/file_loader.rb +41 -0
- data/lib/kontena/cli/stacks/yaml/stack_file_loader/registry_loader.rb +24 -0
- data/lib/kontena/cli/stacks/yaml/stack_file_loader/uri_loader.rb +23 -0
- data/lib/kontena/cli/stacks/yaml/validations.rb +16 -0
- data/lib/kontena/cli/stacks/yaml/validator_v3.rb +25 -8
- data/lib/kontena/client.rb +2 -2
- data/lib/kontena/command.rb +11 -0
- data/lib/kontena/main_command.rb +3 -1
- data/lib/kontena/plugin_manager.rb +11 -198
- data/lib/kontena/plugin_manager/cleaner.rb +33 -0
- data/lib/kontena/plugin_manager/common.rb +86 -0
- data/lib/kontena/plugin_manager/installer.rb +54 -0
- data/lib/kontena/plugin_manager/loader.rb +93 -0
- data/lib/kontena/plugin_manager/rubygems_client.rb +42 -23
- data/lib/kontena/plugin_manager/uninstaller.rb +34 -0
- data/lib/kontena/util.rb +24 -0
- data/lib/kontena_cli.rb +1 -0
- data/omnibus/config/projects/kontena.rb +7 -1
- data/omnibus/config/software/{kontena.rb → kontena-cli.rb} +2 -0
- data/spec/fixtures/api/node.json +2 -1
- data/spec/fixtures/stack-internal-extend.yml +6 -1
- data/spec/fixtures/stack-with-dependencies-dep-1-1.yml +8 -0
- data/spec/fixtures/stack-with-dependencies-dep-1.yml +17 -0
- data/spec/fixtures/stack-with-dependencies-dep-2.yml +8 -0
- data/spec/fixtures/stack-with-dependencies-dep-3.yml +5 -0
- data/spec/fixtures/stack-with-dependencies-dep_2-removed.yml +17 -0
- data/spec/fixtures/stack-with-dependencies-dep_3-added.yml +25 -0
- data/spec/fixtures/stack-with-dependencies.yml +22 -0
- data/spec/fixtures/stack-with-variables.yml +3 -0
- data/spec/kontena/cli/etcd/health_command_spec.rb +45 -33
- data/spec/kontena/cli/helpers/exec_helper_spec.rb +2 -1
- data/spec/kontena/cli/master/init_cloud_command_spec.rb +14 -0
- data/spec/kontena/cli/nodes/health_command_spec.rb +74 -10
- data/spec/kontena/cli/nodes/list_command_spec.rb +381 -232
- data/spec/kontena/cli/nodes/show_command_spec.rb +31 -0
- data/spec/kontena/cli/nodes/ssh_command_spec.rb +18 -3
- data/spec/kontena/cli/plugins/install_command_spec.rb +1 -1
- data/spec/kontena/cli/stacks/build_command_spec.rb +6 -12
- data/spec/kontena/cli/stacks/common_spec.rb +42 -69
- data/spec/kontena/cli/stacks/install_command_spec.rb +57 -31
- data/spec/kontena/cli/stacks/list_command_spec.rb +44 -0
- data/spec/kontena/cli/stacks/logs_command_spec.rb +12 -1
- data/spec/kontena/cli/stacks/remove_command_spec.rb +39 -0
- data/spec/kontena/cli/stacks/show_command_spec.rb +16 -0
- data/spec/kontena/cli/stacks/stack_name_spec.rb +21 -0
- data/spec/kontena/cli/stacks/upgrade_command_spec.rb +73 -56
- data/spec/kontena/cli/stacks/validate_command_spec.rb +81 -0
- data/spec/kontena/cli/stacks/yaml/custom_validators/affinities_validator_spec.rb +22 -0
- data/spec/kontena/cli/stacks/yaml/reader_spec.rb +173 -169
- data/spec/kontena/cli/stacks/yaml/service_extender_spec.rb +12 -3
- data/spec/kontena/cli/stacks/yaml/stack_file_loader/file_loader_spec.rb +47 -0
- data/spec/kontena/cli/stacks/yaml/stack_file_loader/registry_loader_spec.rb +53 -0
- data/spec/kontena/cli/stacks/yaml/stack_file_loader/uri_loader_spec.rb +53 -0
- data/spec/kontena/cli/stacks/yaml/stack_file_loader_spec.rb +104 -0
- data/spec/kontena/cli/stacks/yaml/validator_v3_spec.rb +19 -0
- data/spec/kontena/plugin_manager/cleaner_spec.rb +20 -0
- data/spec/kontena/plugin_manager/common_spec.rb +39 -0
- data/spec/kontena/plugin_manager/installer_spec.rb +50 -0
- data/spec/kontena/plugin_manager/loader_spec.rb +5 -0
- data/spec/kontena/plugin_manager/rubygems_client_spec.rb +11 -25
- data/spec/kontena/plugin_manager/uninstaller_spec.rb +19 -0
- data/spec/kontena/plugin_manager_spec.rb +7 -7
- metadata +64 -97
- data/lib/kontena/cli/app_command.rb +0 -22
- data/lib/kontena/cli/apps/build_command.rb +0 -28
- data/lib/kontena/cli/apps/common.rb +0 -172
- data/lib/kontena/cli/apps/config_command.rb +0 -25
- data/lib/kontena/cli/apps/deploy_command.rb +0 -137
- data/lib/kontena/cli/apps/docker_compose_generator.rb +0 -61
- data/lib/kontena/cli/apps/docker_helper.rb +0 -80
- data/lib/kontena/cli/apps/dockerfile_generator.rb +0 -16
- data/lib/kontena/cli/apps/init_command.rb +0 -89
- data/lib/kontena/cli/apps/kontena_yml_generator.rb +0 -105
- data/lib/kontena/cli/apps/list_command.rb +0 -59
- data/lib/kontena/cli/apps/logs_command.rb +0 -37
- data/lib/kontena/cli/apps/monitor_command.rb +0 -93
- data/lib/kontena/cli/apps/remove_command.rb +0 -74
- data/lib/kontena/cli/apps/restart_command.rb +0 -39
- data/lib/kontena/cli/apps/scale_command.rb +0 -33
- data/lib/kontena/cli/apps/service_generator.rb +0 -114
- data/lib/kontena/cli/apps/service_generator_v2.rb +0 -27
- data/lib/kontena/cli/apps/show_command.rb +0 -23
- data/lib/kontena/cli/apps/start_command.rb +0 -40
- data/lib/kontena/cli/apps/stop_command.rb +0 -40
- data/lib/kontena/cli/apps/yaml/custom_validators/affinities_validator.rb +0 -19
- data/lib/kontena/cli/apps/yaml/custom_validators/build_validator.rb +0 -22
- data/lib/kontena/cli/apps/yaml/custom_validators/extends_validator.rb +0 -20
- data/lib/kontena/cli/apps/yaml/custom_validators/hooks_validator.rb +0 -54
- data/lib/kontena/cli/apps/yaml/custom_validators/secrets_validator.rb +0 -22
- data/lib/kontena/cli/apps/yaml/reader.rb +0 -213
- data/lib/kontena/cli/apps/yaml/service_extender.rb +0 -77
- data/lib/kontena/cli/apps/yaml/validations.rb +0 -71
- data/lib/kontena/cli/apps/yaml/validator.rb +0 -38
- data/lib/kontena/cli/apps/yaml/validator_v2.rb +0 -53
- data/spec/fixtures/app.json +0 -42
- data/spec/fixtures/health.yml +0 -26
- data/spec/fixtures/kontena-build.yml +0 -16
- data/spec/fixtures/kontena-internal-extend.yml +0 -8
- data/spec/fixtures/kontena-invalid.yml +0 -4
- data/spec/fixtures/kontena-with-env-file.yml +0 -18
- data/spec/fixtures/kontena-with-variables.yml +0 -19
- data/spec/fixtures/kontena.yml +0 -17
- data/spec/fixtures/kontena_build_v2.yml +0 -26
- data/spec/fixtures/kontena_numeric_version.yml +0 -9
- data/spec/fixtures/kontena_v2.yml +0 -35
- data/spec/fixtures/mysql.yml +0 -3
- data/spec/fixtures/wordpress-scaled.yml +0 -3
- data/spec/fixtures/wordpress.yml +0 -2
- data/spec/kontena/cli/app/build_command_spec.rb +0 -55
- data/spec/kontena/cli/app/common_spec.rb +0 -110
- data/spec/kontena/cli/app/config_command_spec.rb +0 -78
- data/spec/kontena/cli/app/deploy_command_spec.rb +0 -217
- data/spec/kontena/cli/app/docker_helper_spec.rb +0 -155
- data/spec/kontena/cli/app/init_command_spec.rb +0 -109
- data/spec/kontena/cli/app/logs_command_spec.rb +0 -131
- data/spec/kontena/cli/app/scale_spec.rb +0 -51
- data/spec/kontena/cli/app/service_generator_spec.rb +0 -384
- data/spec/kontena/cli/app/service_generator_v2_spec.rb +0 -73
- data/spec/kontena/cli/app/yaml/reader_spec.rb +0 -457
- data/spec/kontena/cli/app/yaml/service_extender_spec.rb +0 -127
- data/spec/kontena/cli/app/yaml/validator_spec.rb +0 -380
- data/spec/kontena/cli/app/yaml/validator_v2_spec.rb +0 -301
@@ -18,38 +18,151 @@ module Kontena::Cli::Stacks
|
|
18
18
|
option '--[no-]deploy', :flag, 'Trigger deploy after upgrade', default: true
|
19
19
|
|
20
20
|
option '--force', :flag, 'Force upgrade'
|
21
|
+
option '--skip-dependencies', :flag, "Do not install any stack dependencies"
|
22
|
+
option '--dry-run', :flag, "Do not perform any uploading", hidden: true
|
21
23
|
|
22
24
|
requires_current_master
|
23
25
|
requires_current_master_token
|
24
26
|
|
27
|
+
def normalize_local_data(stack_data, parent_name)
|
28
|
+
return nil if stack_data.nil? || stack_data.empty?
|
29
|
+
|
30
|
+
depends = stack_data.delete('depends') || []
|
31
|
+
normalized_data = {
|
32
|
+
parent_name => stack_data.merge(
|
33
|
+
:loader => loader_class.for(stack_data['stack'])
|
34
|
+
)
|
35
|
+
}
|
36
|
+
|
37
|
+
depends.each do |stack|
|
38
|
+
key = "#{parent_name}-#{stack['name']}"
|
39
|
+
normalized_data.merge!(normalize_local_data(stack.merge('parent_name' => parent_name), key))
|
40
|
+
end
|
41
|
+
normalized_data
|
42
|
+
end
|
43
|
+
|
44
|
+
def normalize_master_data(stack_name, raise_not_found = false)
|
45
|
+
begin
|
46
|
+
data = fetch_master_data(stack_name)
|
47
|
+
rescue Kontena::Errors::StandardError => ex
|
48
|
+
return nil if ex.status == 404 && !raise_not_found
|
49
|
+
raise ex
|
50
|
+
end
|
51
|
+
depends = data.delete('children') || []
|
52
|
+
|
53
|
+
normalized_data = { stack_name => data }
|
54
|
+
|
55
|
+
return normalized_data if skip_dependencies?
|
56
|
+
|
57
|
+
depends.each do |stack|
|
58
|
+
normalized_data.merge!(normalize_master_data(stack['name']))
|
59
|
+
end
|
60
|
+
normalized_data
|
61
|
+
end
|
62
|
+
|
63
|
+
def merge_data(local_data, remote_data)
|
64
|
+
merged = {}
|
65
|
+
unless local_data.nil? || local_data.empty?
|
66
|
+
local_data.each do |key, data|
|
67
|
+
merged[key] ||= {}
|
68
|
+
merged[key][:local] = data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
unless remote_data.nil? || remote_data.empty?
|
72
|
+
remote_data.each do |key, data|
|
73
|
+
merged[key] ||= {}
|
74
|
+
merged[key][:remote] = data
|
75
|
+
end
|
76
|
+
end
|
77
|
+
merged
|
78
|
+
end
|
79
|
+
|
25
80
|
def execute
|
26
|
-
|
27
|
-
|
81
|
+
set_env_variables(stack_name, current_grid)
|
82
|
+
|
83
|
+
local = spinner "Parsing #{pastel.cyan(source)}" do
|
84
|
+
normalize_local_data({'stack' => source, 'depends' => skip_dependencies? ? nil : loader.dependencies}, stack_name)
|
28
85
|
end
|
29
86
|
|
30
|
-
|
87
|
+
remote = spinner "Reading stack #{pastel.cyan(stack_name)} from master" do
|
88
|
+
normalize_master_data(stack_name, true)
|
89
|
+
end
|
31
90
|
|
32
|
-
|
33
|
-
|
91
|
+
merged = merge_data(local, remote)
|
92
|
+
|
93
|
+
removes = merged.keys.select { |k| merged[k][:local].nil? }
|
94
|
+
|
95
|
+
unless removes.empty?
|
96
|
+
puts
|
97
|
+
puts "Stacks to be removed because they are no longer depended on:"
|
98
|
+
removes.each do |r|
|
99
|
+
puts pastel.yellow("- #{r}")
|
100
|
+
end
|
101
|
+
puts
|
102
|
+
unless force?
|
103
|
+
puts "#{pastel.red('Warning:')} This can not be undone, data will be lost."
|
104
|
+
end
|
105
|
+
confirm unless force?
|
106
|
+
removes.reverse_each do |removed_stack|
|
107
|
+
Kontena.run!('stack', 'remove', '--force', '--keep-dependencies', removed_stack)
|
108
|
+
merged.delete(removed_stack)
|
109
|
+
end
|
34
110
|
end
|
35
111
|
|
36
|
-
|
37
|
-
|
112
|
+
unless force?
|
113
|
+
merged.each do |stackname, data|
|
114
|
+
next if data[:remote].nil?
|
115
|
+
unless data[:local][:loader].stack_name.stack_name == data[:remote]['stack']
|
116
|
+
confirm "Replacing stack #{pastel.cyan(data[:remote]['stack'])} on master with #{pastel.cyan(data[:local][:loader].stack_name.stack_name)}. Are you sure?"
|
117
|
+
end
|
118
|
+
end
|
38
119
|
end
|
39
120
|
|
40
|
-
|
121
|
+
merged.reverse_each do |stackname, data|
|
122
|
+
set_env_variables(stackname, current_grid)
|
123
|
+
data[:local][:stack] = data[:local][:loader].reader.execute(
|
124
|
+
name: stackname,
|
125
|
+
values: (data.dig(:local, 'variables') || {}).merge(dependency_values_from_options(stackname)),
|
126
|
+
defaults: data.dig(:remote, 'variables'),
|
127
|
+
parent_name: data.dig(:local, 'parent_name')
|
128
|
+
)
|
129
|
+
hint_on_validation_notifications(data[:local][:loader].reader.notifications, data[:local][:loader].source)
|
130
|
+
abort_on_validation_errors(data[:local][:loader].reader.errors, data[:local][:loader].source)
|
131
|
+
end
|
132
|
+
|
133
|
+
merged.reverse_each do |stackname, data|
|
134
|
+
stack = data[:local][:stack]
|
135
|
+
if data[:remote]
|
136
|
+
spinner "Upgrading #{stack_name == stackname ? 'stack' : 'dependency'} #{pastel.cyan(stackname)}" do |spin|
|
137
|
+
update_stack(stackname, stack) || spin.fail!
|
138
|
+
end
|
139
|
+
else
|
140
|
+
cmd = ['stack', 'install', '--name', stackname]
|
141
|
+
cmd.concat ['--parent-name', stack['parent_name']] if stack['parent_name']
|
142
|
+
stack['variables'].merge(dependency_values_from_options(stackname)).each do |k, v|
|
143
|
+
cmd.concat ['-v', "#{k}=#{v}"]
|
144
|
+
end
|
145
|
+
cmd << '--no-deploy'
|
146
|
+
cmd << data[:local][:loader].source
|
147
|
+
caret "Installing new dependency #{cmd.last} as #{stackname}"
|
148
|
+
Kontena.run!(cmd)
|
149
|
+
end
|
150
|
+
|
151
|
+
Kontena.run!(['stack', 'deploy', stackname]) if deploy?
|
152
|
+
end
|
41
153
|
end
|
42
154
|
|
43
|
-
def update_stack(
|
44
|
-
|
155
|
+
def update_stack(name, data)
|
156
|
+
return true if dry_run?
|
157
|
+
client.put(stack_url(name), data)
|
45
158
|
end
|
46
159
|
|
47
|
-
def stack_url
|
48
|
-
|
160
|
+
def stack_url(name)
|
161
|
+
"stacks/#{current_grid}/#{name}"
|
49
162
|
end
|
50
163
|
|
51
|
-
def
|
52
|
-
client.get(stack_url)
|
164
|
+
def fetch_master_data(stack_name)
|
165
|
+
client.get(stack_url(stack_name))
|
53
166
|
end
|
54
167
|
end
|
55
168
|
end
|
@@ -16,26 +16,54 @@ module Kontena::Cli::Stacks
|
|
16
16
|
include Common::StackValuesFromOption
|
17
17
|
|
18
18
|
option '--online', :flag, "Enable connections to current master", default: false
|
19
|
+
option '--dependency-tree', :flag, "Show dependency tree"
|
20
|
+
option '--[no-]dependencies', :flag, "Validate dependencies", default: true
|
21
|
+
option '--parent-name', '[PARENT_NAME]', "Set parent name", hidden: true
|
22
|
+
|
23
|
+
def validate_dependencies
|
24
|
+
dependencies = loader.dependencies
|
25
|
+
return if dependencies.nil?
|
26
|
+
dependencies.each do |dependency|
|
27
|
+
target_name = "#{stack_name}-#{dependency['name']}"
|
28
|
+
cmd = ['stack', 'validate']
|
29
|
+
cmd << '--online' if online?
|
30
|
+
cmd.concat ['--parent-name', stack_name]
|
31
|
+
|
32
|
+
dependency['variables'].merge(dependency_values_from_options(dependency['name'])).each do |key, value|
|
33
|
+
next if key == 'PARENT_STACK'
|
34
|
+
cmd.concat ['-v', "#{key}=#{value}"]
|
35
|
+
end
|
36
|
+
cmd << dependency['stack']
|
37
|
+
Kontena.run(cmd)
|
38
|
+
end
|
39
|
+
end
|
19
40
|
|
20
41
|
def execute
|
21
42
|
unless online?
|
22
43
|
config.current_master = nil
|
23
|
-
|
24
|
-
values.merge!('GRID' => 'validate')
|
44
|
+
set_env_variables(stack_name, 'validate', 'validate-platform')
|
25
45
|
end
|
26
46
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
47
|
+
if dependency_tree?
|
48
|
+
puts ::YAML.dump('name' => stack_name, 'stack' => source, 'depends' => stack['dependencies'])
|
49
|
+
exit 0
|
50
|
+
end
|
51
|
+
|
52
|
+
validate_dependencies if dependencies?
|
31
53
|
|
32
|
-
|
54
|
+
hint_on_validation_notifications(reader.notifications, dependencies? ? loader.source : nil)
|
55
|
+
abort_on_validation_errors(reader.errors, dependencies? ? loader.source : nil)
|
56
|
+
|
57
|
+
dump_variables if values_to
|
33
58
|
|
34
59
|
result = reader.fully_interpolated_yaml.merge(
|
35
|
-
|
36
|
-
'variables' => JSON.parse(reader.variables.to_h(with_values: true, with_errors: true).to_json)
|
60
|
+
'variables' => Kontena::Util.stringify_keys(reader.variables.to_h(with_values: true, with_errors: true))
|
37
61
|
)
|
38
|
-
|
62
|
+
if dependencies?
|
63
|
+
puts ::YAML.dump(result).sub(/\A---$/, "---\n# #{loader.source}")
|
64
|
+
else
|
65
|
+
puts ::YAML.dump(result)
|
66
|
+
end
|
39
67
|
end
|
40
68
|
end
|
41
69
|
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Kontena::Cli::Stacks::YAML::Validations::CustomValidators
|
2
|
+
class CertificatesValidator < HashValidator::Validator::Base
|
3
|
+
def initialize
|
4
|
+
super('stacks_valid_certificates')
|
5
|
+
end
|
6
|
+
|
7
|
+
def validate(key, value, validations, errors)
|
8
|
+
unless value.is_a?(Array)
|
9
|
+
errors[key] = 'certificates must be array'
|
10
|
+
return
|
11
|
+
end
|
12
|
+
certificate_item_validation = {
|
13
|
+
'subject' => 'string',
|
14
|
+
'name' => 'string',
|
15
|
+
'type' => 'string'
|
16
|
+
}
|
17
|
+
value.each do |certificate|
|
18
|
+
HashValidator.validator_for(certificate_item_validation).validate(key, certificate, certificate_item_validation, errors)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -1,5 +1,10 @@
|
|
1
|
-
|
2
|
-
|
1
|
+
require_relative 'stack_file_loader'
|
2
|
+
require_relative 'service_extender'
|
3
|
+
require_relative '../service_generator_v2'
|
4
|
+
require_relative 'validator_v3'
|
5
|
+
require 'opto'
|
6
|
+
require 'liquid'
|
7
|
+
require_relative 'opto'
|
3
8
|
|
4
9
|
module Kontena::Cli::Stacks
|
5
10
|
module YAML
|
@@ -8,156 +13,221 @@ module Kontena::Cli::Stacks
|
|
8
13
|
module Setters; end
|
9
14
|
end
|
10
15
|
|
11
|
-
# Workaround for nil-valued variables in Liquid templates:
|
12
|
-
# https://github.com/Shopify/liquid/issues/749
|
13
|
-
# This is something that we can pass in to `Liquid::Template.render` that gets evaluated as nil.
|
14
|
-
# If we pass in a nil value directly, then Liquid ignores it and considers the variable to be undefined.
|
15
16
|
class LiquidNull
|
17
|
+
# Workaround for nil-valued variables in Liquid templates:
|
18
|
+
# https://github.com/Shopify/liquid/issues/749
|
19
|
+
# This is something that we can pass in to `Liquid::Template.render` that gets evaluated as nil.
|
20
|
+
# If we pass in a nil value directly, then Liquid ignores it and considers the variable to be undefined.
|
16
21
|
def to_liquid
|
17
22
|
nil
|
18
23
|
end
|
19
24
|
end
|
20
25
|
|
21
26
|
class Reader
|
27
|
+
# The kontena Stack YAML reader
|
28
|
+
|
22
29
|
include Kontena::Util
|
23
30
|
include Kontena::Cli::Common
|
24
31
|
|
25
|
-
attr_reader :file, :
|
26
|
-
|
27
|
-
def initialize(file, skip_validation: false, skip_variables: false, variables: nil, values: nil, defaults: nil)
|
28
|
-
require_relative 'service_extender'
|
29
|
-
require_relative 'validator_v3'
|
30
|
-
require_relative 'opto'
|
31
|
-
require 'liquid'
|
32
|
-
|
33
|
-
@file = file
|
32
|
+
attr_reader :file, :loader, :errors, :notifications
|
34
33
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
@
|
41
|
-
@registry = 'file://'
|
34
|
+
# @param stack_origin [String] a filename, pointer to registry or an URL
|
35
|
+
# @return [Reader]
|
36
|
+
def initialize(file)
|
37
|
+
if file.kind_of?(StackFileLoader)
|
38
|
+
@file = file.source
|
39
|
+
@loader = file
|
42
40
|
else
|
43
|
-
@
|
44
|
-
@
|
41
|
+
@file = file
|
42
|
+
@loader = StackFileLoader.for(file)
|
45
43
|
end
|
46
44
|
|
47
45
|
@errors = []
|
48
46
|
@notifications = []
|
49
|
-
@skip_validation = skip_validation
|
50
|
-
@skip_variables = skip_variables
|
51
|
-
@variables = variables
|
52
|
-
@values = values
|
53
|
-
@defaults = defaults
|
54
47
|
end
|
55
48
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
49
|
+
# @param without_defaults [TrueClass,FalseClass] strip the GRID, STACK, etc from response
|
50
|
+
# @param without_vault [TrueClass,FalseClass] strip out any values that are going to or coming from VAULT
|
51
|
+
# @return [Hash] a hash of key value pairs representing the values of stack variables
|
52
|
+
def variable_values(without_defaults: false, without_vault: false)
|
53
|
+
result = variables.to_h(values_only: true)
|
54
|
+
if without_defaults
|
55
|
+
result.delete_if { |k, _| default_envs.key?(k.to_s) || k.to_s == 'PARENT_STACK' }
|
56
|
+
end
|
57
|
+
if without_vault
|
58
|
+
result.delete_if { |k, _| variables.option(k).from.include?('vault') || variables.option(k).to.include?('vault') }
|
59
|
+
end
|
60
|
+
result
|
61
|
+
end
|
62
|
+
|
63
|
+
# Values that are set always when parsing stacks
|
64
|
+
# @return [Hash] a hash of key value pairs
|
65
|
+
def default_envs
|
66
|
+
@default_envs ||= {
|
67
|
+
'GRID' => env['GRID'],
|
68
|
+
'STACK' => env['STACK'],
|
69
|
+
'PLATFORM' => env['PLATFORM'] || env['GRID']
|
70
|
+
}
|
60
71
|
end
|
61
72
|
|
73
|
+
# Only uses the values from #default_envs to provide a hash from minimally interpolated
|
74
|
+
# YAML file. Useful for accessing some parts of the YAML without asking any questions.
|
75
|
+
#
|
76
|
+
# @return [Hash] minimally interpolated YAMl from the stack file.
|
62
77
|
def internals_interpolated_yaml
|
63
78
|
@internals_interpolated_yaml ||= ::YAML.safe_load(
|
64
79
|
replace_dollar_dollars(
|
65
80
|
interpolate(
|
66
81
|
raw_content,
|
67
82
|
use_opto: false,
|
68
|
-
substitutions:
|
69
|
-
'GRID' => env['GRID'],
|
70
|
-
'STACK' => env['STACK']
|
71
|
-
},
|
83
|
+
substitutions: default_envs,
|
72
84
|
warnings: false
|
73
85
|
)
|
74
86
|
)
|
75
|
-
)
|
87
|
+
)
|
76
88
|
rescue Psych::SyntaxError => ex
|
77
89
|
raise ex, "Error while parsing #{file} : #{ex.message}"
|
78
90
|
end
|
79
91
|
|
92
|
+
# Uses variable interpolation, prompts as needed, liquid interpolation
|
93
|
+
#
|
94
|
+
# @return [Hash] the most commplete stack parsing outcome
|
80
95
|
def fully_interpolated_yaml
|
81
96
|
return @fully_interpolated_yaml if @fully_interpolated_yaml
|
82
|
-
vars = variables.to_h(values_only: true)
|
83
97
|
@fully_interpolated_yaml = ::YAML.safe_load(
|
84
98
|
replace_dollar_dollars(
|
85
99
|
interpolate(
|
86
100
|
interpolate_liquid(
|
87
101
|
raw_content,
|
88
|
-
|
102
|
+
variable_values
|
89
103
|
),
|
90
104
|
use_opto: true,
|
91
105
|
raise_on_unknown: true
|
92
106
|
)
|
93
107
|
)
|
94
|
-
)
|
108
|
+
)
|
95
109
|
rescue Psych::SyntaxError => ex
|
96
110
|
raise ex, "Error while parsing #{file} : #{ex.message}"
|
97
111
|
end
|
98
112
|
|
113
|
+
# The YAML file raw content
|
114
|
+
def raw_content
|
115
|
+
loader.content
|
116
|
+
end
|
117
|
+
|
118
|
+
# @return [Hash] with zero interpolation/processing. Will mostly fail
|
99
119
|
def raw_yaml
|
100
|
-
|
120
|
+
loader.yaml
|
121
|
+
end
|
122
|
+
|
123
|
+
# Creates an opto option definition compatible hash from the #default_envs hash
|
124
|
+
# @return [Hash]
|
125
|
+
def default_envs_to_options
|
126
|
+
default_envs.each_with_object({}) { |env, obj| obj[env[0]] = { type: :string, value: env[1] } }
|
101
127
|
end
|
102
128
|
|
129
|
+
# Accessor to the Opto variable handler
|
103
130
|
# @return [Opto::Group]
|
104
131
|
def variables
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
defaults: {
|
109
|
-
from: :env,
|
110
|
-
to: :env
|
111
|
-
}
|
132
|
+
@variables ||= ::Opto::Group.new(
|
133
|
+
internals_interpolated_yaml.fetch('variables', {}).merge(default_envs_to_options),
|
134
|
+
defaults: { from: :env }
|
112
135
|
)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
136
|
+
end
|
137
|
+
|
138
|
+
# Accepts a hash of variable_name => variable_value pairs and sets the values as variable default values
|
139
|
+
# Used when previous answers are read from master and passed as default values for upgrade.
|
140
|
+
# @param defaults [Hash] { 'variable_name' => 'variable_value' }
|
141
|
+
def set_variable_defaults(defaults)
|
142
|
+
defaults.each do |key, val|
|
143
|
+
var = variables.option(key.to_s)
|
144
|
+
var.default = val if var
|
118
145
|
end
|
146
|
+
end
|
119
147
|
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
148
|
+
# Set values from a hash to values of the variables.
|
149
|
+
# Used when variable values are read from a file or command line parameters or dependency variable injection
|
150
|
+
# @param [Hash] a hash of variable_name => variable_value pairs
|
151
|
+
def set_variable_values(values)
|
152
|
+
values.each do |key, val|
|
153
|
+
var = variables.option(key.to_s)
|
154
|
+
var.set(val) if var
|
125
155
|
end
|
126
|
-
@variables
|
127
156
|
end
|
128
157
|
|
129
|
-
#
|
130
|
-
#
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
result[:notifications] = notifications
|
144
|
-
result[:services] = errors.count.zero? ? parse_services(service_name) : {}
|
145
|
-
unless skip_variables?
|
146
|
-
result[:variables] = variables.to_h(values_only: true).reject do |k,_|
|
147
|
-
k == 'GRID' || k == 'STACK' || variables.option(k).to.has_key?(:vault) || variables.option(k).from.has_key?(:vault)
|
148
|
-
end
|
149
|
-
end
|
150
|
-
result[:volumes] = errors.count.zero? ? parse_volumes : {}
|
158
|
+
# Creates a set of variables using the 'depends' section. The variable name is the name of the dependency
|
159
|
+
# and the variable value is the generated child stack name. For example,.have something like:
|
160
|
+
# depends:
|
161
|
+
# redis:
|
162
|
+
# stack: foo/redis
|
163
|
+
# you will get a new variable called "redis" and its value will be "this-stack-name-redis".
|
164
|
+
# This variable can be used to interpolate for example a hostname to some environment variable:
|
165
|
+
# environment:
|
166
|
+
# - "REDIS_HOST=redis.${REDIS}"
|
167
|
+
def create_dependency_variables(dependencies, name)
|
168
|
+
return if dependencies.nil?
|
169
|
+
dependencies.each do |options|
|
170
|
+
variables.build_option(name: options['name'].to_s, type: :string, value: "#{name}-#{options['name']}")
|
171
|
+
create_dependency_variables(options['depends'], "#{name}.#{options['name']}")
|
151
172
|
end
|
152
|
-
result
|
153
173
|
end
|
154
174
|
|
175
|
+
# If this stack is a part of a dependency chain and has a parent, the variable $PARENT_STACK will
|
176
|
+
# interpolate to the name of the parent stack.
|
177
|
+
def create_parent_variable(parent_name)
|
178
|
+
variables.build_option(name: 'PARENT_STACK', type: :string, value: parent_name)
|
179
|
+
end
|
180
|
+
|
181
|
+
# @return [Boolean] did this stack come from a local file?
|
182
|
+
def from_file?
|
183
|
+
loader.origin == 'file'
|
184
|
+
end
|
185
|
+
|
186
|
+
# @param [String] service_name (set when using extends)
|
187
|
+
# @param name [String] override stackname (default is to parse it from the YAML, but if you set it through -n it needs to be overriden)
|
188
|
+
# @param parent_name [String] parent stack name
|
189
|
+
# @param skip_validation [Boolean] skip running validations
|
190
|
+
# @param values [Hash] force-set variable values using variable_name => variable_value key pairs
|
191
|
+
# @param defaults [Hash] set variable defaults from variable_name => variable_value key pairs
|
192
|
+
# @return [Hash]
|
193
|
+
def execute(service_name = nil, name: loader.stack_name.stack, parent_name: nil, skip_validation: false, values: nil, defaults: nil)
|
194
|
+
set_variable_defaults(defaults) if defaults
|
195
|
+
set_variable_values(values) if values
|
196
|
+
create_dependency_variables(dependencies, name)
|
197
|
+
create_parent_variable(parent_name) if parent_name
|
155
198
|
|
156
|
-
def process_variables
|
157
199
|
variables.run
|
158
|
-
raise RuntimeError, "Variable validation failed: #{variables.errors.inspect}" unless variables.valid?
|
200
|
+
raise RuntimeError, "Variable validation failed: #{variables.errors.inspect} in #{file}" unless variables.valid? || skip_validation
|
201
|
+
|
202
|
+
validate unless skip_validation
|
203
|
+
|
204
|
+
{}.tap do |result|
|
205
|
+
Dir.chdir(from_file? ? File.dirname(File.expand_path(file)) : Dir.pwd) do
|
206
|
+
result['stack'] = raw_yaml['stack']
|
207
|
+
result['version'] = loader.stack_name.version || '0.0.1'
|
208
|
+
result['name'] = name
|
209
|
+
result['registry'] = loader.registry
|
210
|
+
result['expose'] = fully_interpolated_yaml['expose']
|
211
|
+
result['services'] = errors.empty? ? parse_services(service_name) : {}
|
212
|
+
result['volumes'] = errors.empty? ? parse_volumes : {}
|
213
|
+
result['dependencies'] = dependencies
|
214
|
+
result['source'] = raw_content
|
215
|
+
result['variables'] = variable_values(without_defaults: true, without_vault: true)
|
216
|
+
result['parent_name'] = parent_name
|
217
|
+
end
|
218
|
+
end
|
159
219
|
end
|
160
220
|
|
221
|
+
# Returns an array of hashes containing the dependency tree starting from this file
|
222
|
+
# @return [Array<Hash>]]
|
223
|
+
def dependencies
|
224
|
+
@dependencies ||= loader.dependencies
|
225
|
+
end
|
226
|
+
|
227
|
+
# Interpolate any Liquid templating in the YAML content
|
228
|
+
# @param content [String] file content
|
229
|
+
# @param vars [Hash] key-value pairs
|
230
|
+
# @return [String]
|
161
231
|
# @raise [Liquid::Error]
|
162
232
|
def interpolate_liquid(content, vars)
|
163
233
|
Liquid::Template.error_mode = :strict
|
@@ -169,41 +239,13 @@ module Kontena::Cli::Stacks
|
|
169
239
|
template.render!(vars, strict_variables: true, strict_filters: true)
|
170
240
|
end
|
171
241
|
|
172
|
-
|
173
|
-
@stack_name ||= parse_stack_name(raw_yaml['stack'].to_s)[:stack]
|
174
|
-
end
|
175
|
-
|
176
|
-
def stack_version
|
177
|
-
@stack_version ||= raw_yaml['version'] || parse_stack_name(raw_yaml['stack'].to_s)[:version] || '0.0.1'
|
178
|
-
end
|
179
|
-
|
180
|
-
# @return [Array] array of validation errors
|
242
|
+
# @return [Array<Hash>] array of validation errors
|
181
243
|
def validate
|
182
244
|
result = validator.validate(fully_interpolated_yaml)
|
183
245
|
store_failures(result)
|
184
246
|
result
|
185
247
|
end
|
186
248
|
|
187
|
-
def skip_validation?
|
188
|
-
!!@skip_validation
|
189
|
-
end
|
190
|
-
|
191
|
-
def skip_variables?
|
192
|
-
!!@skip_variables
|
193
|
-
end
|
194
|
-
|
195
|
-
def from_registry?
|
196
|
-
file =~ /\A[a-zA-Z0-9\_\.\-]+\/[a-zA-Z0-9\_\.\-]+(?::.*)?\z/ && !File.exist?(file)
|
197
|
-
end
|
198
|
-
|
199
|
-
def from_url?
|
200
|
-
file =~ /\A(?:http|https|ftp):\/\//
|
201
|
-
end
|
202
|
-
|
203
|
-
def from_file?
|
204
|
-
!from_registry? && !from_url?
|
205
|
-
end
|
206
|
-
|
207
249
|
# @return [Kontena::Cli::Stacks::YAML::ValidatorV3]
|
208
250
|
def validator
|
209
251
|
@validator ||= YAML::ValidatorV3.new
|
@@ -214,12 +256,12 @@ module Kontena::Cli::Stacks
|
|
214
256
|
if process_hash?(config)
|
215
257
|
volumes[name].delete('only_if')
|
216
258
|
volumes[name].delete('skip_if')
|
217
|
-
volumes[name] = process_volume(config)
|
259
|
+
volumes[name] = process_volume(name, config)
|
218
260
|
else
|
219
261
|
volumes.delete(name)
|
220
262
|
end
|
221
263
|
end
|
222
|
-
volumes
|
264
|
+
volumes.map { |name, vol| vol.merge('name' => name) }
|
223
265
|
end
|
224
266
|
|
225
267
|
##
|
@@ -228,7 +270,7 @@ module Kontena::Cli::Stacks
|
|
228
270
|
def parse_services(service_name = nil)
|
229
271
|
if service_name.nil?
|
230
272
|
services.each do |name, config|
|
231
|
-
services[name] = process_config(config)
|
273
|
+
services[name] = process_config(config, name)
|
232
274
|
if process_hash?(config)
|
233
275
|
services[name].delete('only_if')
|
234
276
|
services[name].delete('skip_if')
|
@@ -236,10 +278,10 @@ module Kontena::Cli::Stacks
|
|
236
278
|
services.delete(name)
|
237
279
|
end
|
238
280
|
end
|
239
|
-
services
|
281
|
+
services.map { |name, svc| svc.merge('name' => name) }
|
240
282
|
else
|
241
|
-
raise ("Service '#{service_name}' not found in #{file}") unless services.
|
242
|
-
process_config(services[service_name])
|
283
|
+
raise ("Service '#{service_name}' not found in #{file}") unless services.key?(service_name)
|
284
|
+
process_config(services[service_name], service_name)
|
243
285
|
end
|
244
286
|
end
|
245
287
|
|
@@ -249,7 +291,6 @@ module Kontena::Cli::Stacks
|
|
249
291
|
# @return [Boolean]
|
250
292
|
def process_hash?(hash)
|
251
293
|
return true unless hash['skip_if'] || hash['only_if']
|
252
|
-
return true if skip_variables? || variables.empty?
|
253
294
|
|
254
295
|
skip_lambdas = normalize_ifs(hash['skip_if'])
|
255
296
|
only_lambdas = normalize_ifs(hash['only_if'])
|
@@ -266,36 +307,49 @@ module Kontena::Cli::Stacks
|
|
266
307
|
end
|
267
308
|
|
268
309
|
# @param [Hash] service_config
|
269
|
-
def process_config(service_config)
|
310
|
+
def process_config(service_config, name=nil)
|
270
311
|
normalize_env_vars(service_config)
|
271
312
|
merge_env_vars(service_config)
|
272
313
|
expand_build_context(service_config)
|
273
314
|
normalize_build_args(service_config)
|
274
|
-
if service_config.
|
315
|
+
if service_config.key?('extends')
|
275
316
|
service_config = extend_config(service_config)
|
276
317
|
service_config.delete('extends')
|
277
318
|
end
|
278
|
-
|
319
|
+
if name
|
320
|
+
exit_with_error("Image is missing for #{name}. Aborting.") unless service_config['image'] # why isn't this a validation?
|
321
|
+
ServiceGeneratorV2.new(service_config).generate.merge('name' => name)
|
322
|
+
else
|
323
|
+
ServiceGeneratorV2.new(service_config).generate
|
324
|
+
end
|
279
325
|
end
|
280
326
|
|
281
|
-
def process_volume(volume_config)
|
327
|
+
def process_volume(name, volume_config)
|
328
|
+
return [] if volume_config.nil? || volume_config.empty?
|
329
|
+
if volume_config['external'].is_a?(TrueClass)
|
330
|
+
volume_config['external'] = name
|
331
|
+
elsif volume_config['external']['name']
|
332
|
+
volume_config['external'] = volume_config['external']['name']
|
333
|
+
end
|
334
|
+
volume_config['name'] = name
|
282
335
|
volume_config
|
283
336
|
end
|
284
337
|
|
285
338
|
def volumes
|
286
|
-
@volumes ||= fully_interpolated_yaml
|
339
|
+
@volumes ||= fully_interpolated_yaml.fetch('volumes', {})
|
287
340
|
end
|
288
341
|
|
289
342
|
# @return [Hash] - services from YAML file
|
290
343
|
def services
|
291
|
-
@services ||= fully_interpolated_yaml
|
344
|
+
@services ||= fully_interpolated_yaml.fetch('services', {})
|
292
345
|
end
|
293
346
|
|
294
347
|
def from_external_file(filename, service_name)
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
348
|
+
external_reader = Reader.new(filename)
|
349
|
+
outcome = external_reader.execute(service_name)
|
350
|
+
errors.concat external_reader.errors unless errors.any? { |item| item.key?(filename) }
|
351
|
+
notifications.concat external_reader.notifications unless notifications.any? { |item| item.key?(filename) }
|
352
|
+
outcome['services']
|
299
353
|
end
|
300
354
|
|
301
355
|
private
|
@@ -314,8 +368,9 @@ module Kontena::Cli::Stacks
|
|
314
368
|
if use_opto
|
315
369
|
opt = variables.option(var)
|
316
370
|
if opt.nil?
|
317
|
-
|
318
|
-
|
371
|
+
to_env = variables.find { |opt| Array(opt.to[:env]).include?(var) }
|
372
|
+
if to_env
|
373
|
+
val = to_env.value
|
319
374
|
else
|
320
375
|
raise RuntimeError, "Undeclared variable '#{var}' in #{file}:#{line_num} -- #{row}" if raise_on_unknown
|
321
376
|
end
|
@@ -348,7 +403,7 @@ module Kontena::Cli::Stacks
|
|
348
403
|
# @example
|
349
404
|
# normalize_ifs( 'wp' ) # lambdas return true if variable wp is not null or false or 'false'
|
350
405
|
# normalize_ifs( wp: 1 ) # lambdas return true if value of wp is 1
|
351
|
-
# normalize_ifs( [
|
406
|
+
# normalize_ifs( ['wp, :ws'] ) # lambdas return true if wp and ws are not not null or false or 'false'
|
352
407
|
# normalize_ifs( wp: 1, ws: 1) # lambdas return true if wp and ws are 1
|
353
408
|
# normalize_ifs(nil) # returns nil
|
354
409
|
def normalize_ifs(ifs)
|
@@ -373,34 +428,27 @@ module Kontena::Cli::Stacks
|
|
373
428
|
# @param [Hash] service_config
|
374
429
|
# @return [Hash] updated service config
|
375
430
|
def extend_config(service_config)
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
431
|
+
extends = service_config['extends']
|
432
|
+
case extends
|
433
|
+
when NilClass
|
434
|
+
return
|
435
|
+
when String
|
436
|
+
raise ("Service '#{extends}' not found in #{file}") unless services.key?(extends)
|
437
|
+
parent_config = process_config(services[extends])
|
438
|
+
when Hash
|
439
|
+
target = extends['file'] || extends['stack']
|
440
|
+
parent_config = from_external_file(target, extends['service'])
|
384
441
|
else
|
385
|
-
raise
|
386
|
-
parent_config = process_config(services[extended_service])
|
442
|
+
raise TypeError, "Extends must be a hash or string"
|
387
443
|
end
|
388
444
|
ServiceExtender.new(service_config).extend_from(parent_config)
|
389
445
|
end
|
390
446
|
|
391
|
-
def extended_service(extend_config)
|
392
|
-
if extend_config.kind_of?(Hash)
|
393
|
-
extend_config['service']
|
394
|
-
elsif extend_config.kind_of?(String)
|
395
|
-
extend_config
|
396
|
-
else
|
397
|
-
nil
|
398
|
-
end
|
399
|
-
end
|
400
|
-
|
401
447
|
def store_failures(data)
|
402
|
-
errors
|
403
|
-
notifications
|
448
|
+
data['errors'] = data[:errors] unless data['errors']
|
449
|
+
data['notifications'] = data[:notifications] unless data['notifications']
|
450
|
+
errors << { file => data['errors'] || data[:errors] } unless data['errors'].empty?
|
451
|
+
notifications << { file => data['notifications'] } unless data['notifications'].empty?
|
404
452
|
end
|
405
453
|
|
406
454
|
|
@@ -439,37 +487,15 @@ module Kontena::Cli::Stacks
|
|
439
487
|
|
440
488
|
# @param [Hash] options - service config
|
441
489
|
def normalize_build_args(options)
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
end
|
450
|
-
end
|
451
|
-
|
452
|
-
# Takes a stack name such as user/foo:1.0.0 and breaks it into components
|
453
|
-
# @param [String] stack_name
|
454
|
-
# @return [Hash] a hash with :user, :stack and :version
|
455
|
-
def parse_stack_name(stack_name)
|
456
|
-
return {} if stack_name.nil?
|
457
|
-
return {} if stack_name.empty?
|
458
|
-
name, version = stack_name.split(':', 2)
|
459
|
-
if name.include?('/')
|
460
|
-
user, stack = name.split('/', 2)
|
461
|
-
else
|
462
|
-
user = nil
|
463
|
-
stack = name
|
464
|
-
end
|
465
|
-
{
|
466
|
-
user: user,
|
467
|
-
stack: stack,
|
468
|
-
version: version
|
469
|
-
}
|
490
|
+
build = options['build']
|
491
|
+
return unless build.kind_of?(Hash)
|
492
|
+
args = build['args']
|
493
|
+
return unless args
|
494
|
+
return unless args.kind_of?(Array)
|
495
|
+
build.delete('args')
|
496
|
+
build['args'] = args.map { |arg| arg.split('=', 2) }.to_h
|
470
497
|
end
|
471
498
|
|
472
|
-
|
473
499
|
def env
|
474
500
|
ENV
|
475
501
|
end
|