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