kamal-insecure 2.7.0
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +313 -0
- data/lib/kamal/cli/alias/command.rb +10 -0
- data/lib/kamal/cli/app/assets.rb +24 -0
- data/lib/kamal/cli/app/boot.rb +126 -0
- data/lib/kamal/cli/app/error_pages.rb +33 -0
- data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
- data/lib/kamal/cli/app.rb +400 -0
- data/lib/kamal/cli/base.rb +223 -0
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +204 -0
- data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +42 -0
- data/lib/kamal/cli/lock.rb +45 -0
- data/lib/kamal/cli/main.rb +277 -0
- data/lib/kamal/cli/proxy.rb +290 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +19 -0
- data/lib/kamal/cli/secrets.rb +49 -0
- data/lib/kamal/cli/server.rb +50 -0
- data/lib/kamal/cli/templates/deploy.yml +101 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +17 -0
- data/lib/kamal/cli.rb +9 -0
- data/lib/kamal/commander/specifics.rb +62 -0
- data/lib/kamal/commander.rb +167 -0
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +32 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +28 -0
- data/lib/kamal/commands/app/proxy.rb +32 -0
- data/lib/kamal/commands/app.rb +124 -0
- data/lib/kamal/commands/auditor.rb +39 -0
- data/lib/kamal/commands/base.rb +134 -0
- data/lib/kamal/commands/builder/base.rb +124 -0
- data/lib/kamal/commands/builder/clone.rb +31 -0
- data/lib/kamal/commands/builder/cloud.rb +22 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +14 -0
- data/lib/kamal/commands/builder/pack.rb +46 -0
- data/lib/kamal/commands/builder/remote.rb +63 -0
- data/lib/kamal/commands/builder.rb +48 -0
- data/lib/kamal/commands/docker.rb +34 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +127 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +16 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +241 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +25 -0
- data/lib/kamal/configuration/builder.rb +211 -0
- data/lib/kamal/configuration/docs/accessory.yml +128 -0
- data/lib/kamal/configuration/docs/alias.yml +26 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +132 -0
- data/lib/kamal/configuration/docs/configuration.yml +184 -0
- data/lib/kamal/configuration/docs/env.yml +116 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/proxy.yml +164 -0
- data/lib/kamal/configuration/docs/registry.yml +56 -0
- data/lib/kamal/configuration/docs/role.yml +53 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +70 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +38 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/proxy/boot.rb +129 -0
- data/lib/kamal/configuration/proxy.rb +124 -0
- data/lib/kamal/configuration/registry.rb +32 -0
- data/lib/kamal/configuration/role.rb +222 -0
- data/lib/kamal/configuration/servers.rb +25 -0
- data/lib/kamal/configuration/ssh.rb +57 -0
- data/lib/kamal/configuration/sshkit.rb +22 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +13 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +15 -0
- data/lib/kamal/configuration/validator/configuration.rb +6 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/proxy.rb +25 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +13 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +191 -0
- data/lib/kamal/configuration/volume.rb +22 -0
- data/lib/kamal/configuration.rb +372 -0
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +37 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
- data/lib/kamal/secrets/adapters/base.rb +33 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
- data/lib/kamal/secrets/adapters/doppler.rb +57 -0
- data/lib/kamal/secrets/adapters/enpass.rb +71 -0
- data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
- data/lib/kamal/secrets/adapters/one_password.rb +104 -0
- data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
- data/lib/kamal/secrets/adapters/test.rb +14 -0
- data/lib/kamal/secrets/adapters.rb +16 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +33 -0
- data/lib/kamal/secrets.rb +42 -0
- data/lib/kamal/sshkit_with_ext.rb +142 -0
- data/lib/kamal/tags.rb +40 -0
- data/lib/kamal/utils/sensitive.rb +20 -0
- data/lib/kamal/utils.rb +110 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +14 -0
- metadata +365 -0
@@ -0,0 +1,54 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Env < Kamal::Configuration::Validator
|
2
|
+
SPECIAL_KEYS = [ "clear", "secret", "tags" ]
|
3
|
+
|
4
|
+
def validate!
|
5
|
+
if known_keys.any?
|
6
|
+
validate_complex_env!
|
7
|
+
else
|
8
|
+
validate_simple_env!
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
def validate_simple_env!
|
14
|
+
validate_hash_of!(config, String)
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_complex_env!
|
18
|
+
unknown_keys_error unknown_keys if unknown_keys.any?
|
19
|
+
|
20
|
+
with_context("clear") { validate_hash_of!(config["clear"], String) } if config.key?("clear")
|
21
|
+
with_context("secret") { validate_array_of!(config["secret"], String) } if config.key?("secret")
|
22
|
+
validate_tags! if config.key?("tags")
|
23
|
+
end
|
24
|
+
|
25
|
+
def known_keys
|
26
|
+
@known_keys ||= config.keys & SPECIAL_KEYS
|
27
|
+
end
|
28
|
+
|
29
|
+
def unknown_keys
|
30
|
+
@unknown_keys ||= config.keys - SPECIAL_KEYS
|
31
|
+
end
|
32
|
+
|
33
|
+
def validate_tags!
|
34
|
+
if context == "env"
|
35
|
+
with_context("tags") do
|
36
|
+
validate_type! config["tags"], Hash
|
37
|
+
|
38
|
+
config["tags"].each do |tag, value|
|
39
|
+
with_context(tag) do
|
40
|
+
validate_type! value, Hash
|
41
|
+
|
42
|
+
Kamal::Configuration::Validator::Env.new(
|
43
|
+
value,
|
44
|
+
example: example["tags"].values[1],
|
45
|
+
context: context
|
46
|
+
).validate!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
else
|
51
|
+
error "tags are only allowed in the root env"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Proxy < Kamal::Configuration::Validator
|
2
|
+
def validate!
|
3
|
+
unless config.nil?
|
4
|
+
super
|
5
|
+
|
6
|
+
if config["host"].blank? && config["hosts"].blank? && config["ssl"]
|
7
|
+
error "Must set a host to enable automatic SSL"
|
8
|
+
end
|
9
|
+
|
10
|
+
if (config.keys & [ "host", "hosts" ]).size > 1
|
11
|
+
error "Specify one of 'host' or 'hosts', not both"
|
12
|
+
end
|
13
|
+
|
14
|
+
if config["ssl"].is_a?(Hash)
|
15
|
+
if config["ssl"]["certificate_pem"].present? && config["ssl"]["private_key_pem"].blank?
|
16
|
+
error "Missing private_key_pem setting (required when certificate_pem is present)"
|
17
|
+
end
|
18
|
+
|
19
|
+
if config["ssl"]["private_key_pem"].present? && config["ssl"]["certificate_pem"].blank?
|
20
|
+
error "Missing certificate_pem setting (required when private_key_pem is present)"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Registry < Kamal::Configuration::Validator
|
2
|
+
STRING_OR_ONE_ITEM_ARRAY_KEYS = [ "username", "password" ]
|
3
|
+
|
4
|
+
def validate!
|
5
|
+
validate_against_example! \
|
6
|
+
config.except(*STRING_OR_ONE_ITEM_ARRAY_KEYS),
|
7
|
+
example.except(*STRING_OR_ONE_ITEM_ARRAY_KEYS)
|
8
|
+
|
9
|
+
validate_string_or_one_item_array! "username"
|
10
|
+
validate_string_or_one_item_array! "password"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
def validate_string_or_one_item_array!(key)
|
15
|
+
with_context(key) do
|
16
|
+
value = config[key]
|
17
|
+
|
18
|
+
error "is required" unless value.present?
|
19
|
+
|
20
|
+
unless value.is_a?(String) || (value.is_a?(Array) && value.size == 1 && value.first.is_a?(String))
|
21
|
+
error "should be a string or an array with one string (for secret lookup)"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Role < Kamal::Configuration::Validator
|
2
|
+
def validate!
|
3
|
+
validate_type! config, Array, Hash
|
4
|
+
|
5
|
+
if config.is_a?(Array)
|
6
|
+
validate_servers!(config)
|
7
|
+
else
|
8
|
+
super
|
9
|
+
validate_labels!(config["labels"])
|
10
|
+
validate_docker_options!(config["options"])
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
class Kamal::Configuration::Validator
|
2
|
+
attr_reader :config, :example, :context
|
3
|
+
|
4
|
+
def initialize(config, example:, context:)
|
5
|
+
@config = config
|
6
|
+
@example = example
|
7
|
+
@context = context
|
8
|
+
end
|
9
|
+
|
10
|
+
def validate!
|
11
|
+
validate_against_example! config, example
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def validate_against_example!(validation_config, example)
|
16
|
+
validate_type! validation_config, example.class
|
17
|
+
|
18
|
+
if example.class == Hash
|
19
|
+
check_unknown_keys! validation_config, example
|
20
|
+
|
21
|
+
validation_config.each do |key, value|
|
22
|
+
next if extension?(key)
|
23
|
+
with_context(key) do
|
24
|
+
example_value = example[key]
|
25
|
+
|
26
|
+
if example_value == "..."
|
27
|
+
unless key.to_s == "proxy" && boolean?(value.class)
|
28
|
+
validate_type! value, *(Array if key == :servers), Hash
|
29
|
+
end
|
30
|
+
elsif key.to_s == "ssl"
|
31
|
+
validate_type! value, TrueClass, FalseClass, Hash
|
32
|
+
elsif key == "hosts"
|
33
|
+
validate_servers! value
|
34
|
+
elsif example_value.is_a?(Array)
|
35
|
+
if key == "arch"
|
36
|
+
validate_array_of_or_type! value, example_value.first.class
|
37
|
+
else
|
38
|
+
validate_array_of! value, example_value.first.class
|
39
|
+
end
|
40
|
+
elsif example_value.is_a?(Hash)
|
41
|
+
case key.to_s
|
42
|
+
when "options", "args"
|
43
|
+
validate_type! value, Hash
|
44
|
+
when "labels"
|
45
|
+
validate_hash_of! value, example_value.first[1].class
|
46
|
+
else
|
47
|
+
validate_against_example! value, example_value
|
48
|
+
end
|
49
|
+
else
|
50
|
+
validate_type! value, example_value.class
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def valid_type?(value, type)
|
59
|
+
value.is_a?(type) ||
|
60
|
+
(type == String && stringish?(value)) ||
|
61
|
+
(boolean?(type) && boolean?(value.class))
|
62
|
+
end
|
63
|
+
|
64
|
+
def type_description(type)
|
65
|
+
if type == Integer || type == Array
|
66
|
+
"an #{type.name.downcase}"
|
67
|
+
elsif type == TrueClass || type == FalseClass
|
68
|
+
"a boolean"
|
69
|
+
else
|
70
|
+
"a #{type.name.downcase}"
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def boolean?(type)
|
75
|
+
type == TrueClass || type == FalseClass
|
76
|
+
end
|
77
|
+
|
78
|
+
def stringish?(value)
|
79
|
+
value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
80
|
+
end
|
81
|
+
|
82
|
+
def validate_array_of_or_type!(value, type)
|
83
|
+
if value.is_a?(Array)
|
84
|
+
validate_array_of! value, type
|
85
|
+
else
|
86
|
+
validate_type! value, type
|
87
|
+
end
|
88
|
+
rescue Kamal::ConfigurationError
|
89
|
+
type_error(Array, type)
|
90
|
+
end
|
91
|
+
|
92
|
+
def validate_array_of!(array, type)
|
93
|
+
validate_type! array, Array
|
94
|
+
|
95
|
+
array.each_with_index do |value, index|
|
96
|
+
with_context(index) do
|
97
|
+
validate_type! value, type
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def validate_hash_of!(hash, type)
|
103
|
+
validate_type! hash, Hash
|
104
|
+
|
105
|
+
hash.each do |key, value|
|
106
|
+
with_context(key) do
|
107
|
+
validate_type! value, type
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def validate_servers!(servers)
|
113
|
+
validate_type! servers, Array
|
114
|
+
|
115
|
+
servers.each_with_index do |server, index|
|
116
|
+
with_context(index) do
|
117
|
+
validate_type! server, String, Hash
|
118
|
+
|
119
|
+
if server.is_a?(Hash)
|
120
|
+
error "multiple hosts found" unless server.size == 1
|
121
|
+
host, tags = server.first
|
122
|
+
|
123
|
+
with_context(host) do
|
124
|
+
validate_type! tags, String, Array
|
125
|
+
validate_array_of! tags, String if tags.is_a?(Array)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def validate_type!(value, *types)
|
133
|
+
type_error(*types) unless types.any? { |type| valid_type?(value, type) }
|
134
|
+
end
|
135
|
+
|
136
|
+
def error(message)
|
137
|
+
raise Kamal::ConfigurationError, "#{error_context}#{message}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def type_error(*expected_types)
|
141
|
+
error "should be #{expected_types.map { |type| type_description(type) }.join(" or ")}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def unknown_keys_error(unknown_keys)
|
145
|
+
error "unknown #{"key".pluralize(unknown_keys.count)}: #{unknown_keys.join(", ")}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def error_context
|
149
|
+
"#{context}: " if context.present?
|
150
|
+
end
|
151
|
+
|
152
|
+
def with_context(context)
|
153
|
+
old_context = @context
|
154
|
+
@context = [ @context, context ].select(&:present?).join("/")
|
155
|
+
yield
|
156
|
+
ensure
|
157
|
+
@context = old_context
|
158
|
+
end
|
159
|
+
|
160
|
+
def allow_extensions?
|
161
|
+
false
|
162
|
+
end
|
163
|
+
|
164
|
+
def extension?(key)
|
165
|
+
key.to_s.start_with?("x-")
|
166
|
+
end
|
167
|
+
|
168
|
+
def check_unknown_keys!(config, example)
|
169
|
+
unknown_keys = config.keys - example.keys
|
170
|
+
unknown_keys.reject! { |key| extension?(key) } if allow_extensions?
|
171
|
+
unknown_keys_error unknown_keys if unknown_keys.present?
|
172
|
+
end
|
173
|
+
|
174
|
+
def validate_labels!(labels)
|
175
|
+
return true if labels.blank?
|
176
|
+
|
177
|
+
with_context("labels") do
|
178
|
+
labels.each do |key, _|
|
179
|
+
with_context(key) do
|
180
|
+
error "invalid label. destination, role, and service are reserved labels" if %w[destination role service].include?(key)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
def validate_docker_options!(options)
|
187
|
+
if options
|
188
|
+
error "Cannot set restart policy in docker options, unless-stopped is required" if options["restart"]
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class Kamal::Configuration::Volume
|
2
|
+
attr_reader :host_path, :container_path
|
3
|
+
delegate :argumentize, to: Kamal::Utils
|
4
|
+
|
5
|
+
def initialize(host_path:, container_path:)
|
6
|
+
@host_path = host_path
|
7
|
+
@container_path = container_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def docker_args
|
11
|
+
argumentize "--volume", "#{host_path_for_docker_volume}:#{container_path}"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def host_path_for_docker_volume
|
16
|
+
if Pathname.new(host_path).absolute?
|
17
|
+
host_path
|
18
|
+
else
|
19
|
+
File.join "$(pwd)", host_path
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,372 @@
|
|
1
|
+
require "active_support/ordered_options"
|
2
|
+
require "active_support/core_ext/string/inquiry"
|
3
|
+
require "active_support/core_ext/module/delegation"
|
4
|
+
require "active_support/core_ext/hash/keys"
|
5
|
+
require "erb"
|
6
|
+
require "net/ssh/proxy/jump"
|
7
|
+
|
8
|
+
class Kamal::Configuration
|
9
|
+
delegate :service, :image, :labels, :hooks_path, to: :raw_config, allow_nil: true
|
10
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
11
|
+
|
12
|
+
attr_reader :destination, :raw_config, :secrets
|
13
|
+
attr_reader :accessories, :aliases, :boot, :builder, :env, :logging, :proxy, :proxy_boot, :servers, :ssh, :sshkit, :registry
|
14
|
+
|
15
|
+
include Validation
|
16
|
+
|
17
|
+
class << self
|
18
|
+
def create_from(config_file:, destination: nil, version: nil)
|
19
|
+
ENV["KAMAL_DESTINATION"] = destination
|
20
|
+
|
21
|
+
raw_config = load_config_files(config_file, *destination_config_file(config_file, destination))
|
22
|
+
|
23
|
+
new raw_config, destination: destination, version: version
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
def load_config_files(*files)
|
28
|
+
files.inject({}) { |config, file| config.deep_merge! load_config_file(file) }
|
29
|
+
end
|
30
|
+
|
31
|
+
def load_config_file(file)
|
32
|
+
if file.exist?
|
33
|
+
# Newer Psych doesn't load aliases by default
|
34
|
+
load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
|
35
|
+
YAML.send(load_method, ERB.new(File.read(file)).result).symbolize_keys
|
36
|
+
else
|
37
|
+
raise "Configuration file not found in #{file}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def destination_config_file(base_config_file, destination)
|
42
|
+
base_config_file.sub_ext(".#{destination}.yml") if destination
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def initialize(raw_config, destination: nil, version: nil, validate: true)
|
47
|
+
@raw_config = ActiveSupport::InheritableOptions.new(raw_config)
|
48
|
+
@destination = destination
|
49
|
+
@declared_version = version
|
50
|
+
|
51
|
+
validate! raw_config, example: validation_yml.symbolize_keys, context: "", with: Kamal::Configuration::Validator::Configuration
|
52
|
+
|
53
|
+
@secrets = Kamal::Secrets.new(destination: destination)
|
54
|
+
|
55
|
+
# Eager load config to validate it, these are first as they have dependencies later on
|
56
|
+
@servers = Servers.new(config: self)
|
57
|
+
@registry = Registry.new(config: @raw_config, secrets: secrets)
|
58
|
+
|
59
|
+
@accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
|
60
|
+
@aliases = @raw_config.aliases&.keys&.to_h { |name| [ name, Alias.new(name, config: self) ] } || {}
|
61
|
+
@boot = Boot.new(config: self)
|
62
|
+
@builder = Builder.new(config: self)
|
63
|
+
@env = Env.new(config: @raw_config.env || {}, secrets: secrets)
|
64
|
+
|
65
|
+
@logging = Logging.new(logging_config: @raw_config.logging)
|
66
|
+
@proxy = Proxy.new(config: self, proxy_config: @raw_config.proxy, secrets: secrets)
|
67
|
+
@proxy_boot = Proxy::Boot.new(config: self)
|
68
|
+
@ssh = Ssh.new(config: self)
|
69
|
+
@sshkit = Sshkit.new(config: self)
|
70
|
+
|
71
|
+
ensure_destination_if_required
|
72
|
+
ensure_required_keys_present
|
73
|
+
ensure_valid_kamal_version
|
74
|
+
ensure_retain_containers_valid
|
75
|
+
ensure_valid_service_name
|
76
|
+
ensure_no_traefik_reboot_hooks
|
77
|
+
ensure_one_host_for_ssl_roles
|
78
|
+
ensure_unique_hosts_for_ssl_roles
|
79
|
+
end
|
80
|
+
|
81
|
+
def version=(version)
|
82
|
+
@declared_version = version
|
83
|
+
end
|
84
|
+
|
85
|
+
def version
|
86
|
+
@declared_version.presence || ENV["VERSION"] || git_version
|
87
|
+
end
|
88
|
+
|
89
|
+
def abbreviated_version
|
90
|
+
if version
|
91
|
+
# Don't abbreviate <sha>_uncommitted_<etc>
|
92
|
+
if version.include?("_")
|
93
|
+
version
|
94
|
+
else
|
95
|
+
version[0...7]
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def minimum_version
|
101
|
+
raw_config.minimum_version
|
102
|
+
end
|
103
|
+
|
104
|
+
def service_and_destination
|
105
|
+
[ service, destination ].compact.join("-")
|
106
|
+
end
|
107
|
+
|
108
|
+
def roles
|
109
|
+
servers.roles
|
110
|
+
end
|
111
|
+
|
112
|
+
def role(name)
|
113
|
+
roles.detect { |r| r.name == name.to_s }
|
114
|
+
end
|
115
|
+
|
116
|
+
def accessory(name)
|
117
|
+
accessories.detect { |a| a.name == name.to_s }
|
118
|
+
end
|
119
|
+
|
120
|
+
def all_hosts
|
121
|
+
(roles + accessories).flat_map(&:hosts).uniq
|
122
|
+
end
|
123
|
+
|
124
|
+
def app_hosts
|
125
|
+
roles.flat_map(&:hosts).uniq
|
126
|
+
end
|
127
|
+
|
128
|
+
def primary_host
|
129
|
+
primary_role&.primary_host
|
130
|
+
end
|
131
|
+
|
132
|
+
def primary_role_name
|
133
|
+
raw_config.primary_role || "web"
|
134
|
+
end
|
135
|
+
|
136
|
+
def primary_role
|
137
|
+
role(primary_role_name)
|
138
|
+
end
|
139
|
+
|
140
|
+
def allow_empty_roles?
|
141
|
+
raw_config.allow_empty_roles
|
142
|
+
end
|
143
|
+
|
144
|
+
def proxy_roles
|
145
|
+
roles.select(&:running_proxy?)
|
146
|
+
end
|
147
|
+
|
148
|
+
def proxy_role_names
|
149
|
+
proxy_roles.flat_map(&:name)
|
150
|
+
end
|
151
|
+
|
152
|
+
def proxy_accessories
|
153
|
+
accessories.select(&:running_proxy?)
|
154
|
+
end
|
155
|
+
|
156
|
+
def proxy_hosts
|
157
|
+
(proxy_roles.flat_map(&:hosts) + proxy_accessories.flat_map(&:hosts)).uniq
|
158
|
+
end
|
159
|
+
|
160
|
+
def repository
|
161
|
+
[ registry.server, image ].compact.join("/")
|
162
|
+
end
|
163
|
+
|
164
|
+
def absolute_image
|
165
|
+
"#{repository}:#{version}"
|
166
|
+
end
|
167
|
+
|
168
|
+
def latest_image
|
169
|
+
"#{repository}:#{latest_tag}"
|
170
|
+
end
|
171
|
+
|
172
|
+
def latest_tag
|
173
|
+
[ "latest", *destination ].join("-")
|
174
|
+
end
|
175
|
+
|
176
|
+
def service_with_version
|
177
|
+
"#{service}-#{version}"
|
178
|
+
end
|
179
|
+
|
180
|
+
def require_destination?
|
181
|
+
raw_config.require_destination
|
182
|
+
end
|
183
|
+
|
184
|
+
def retain_containers
|
185
|
+
raw_config.retain_containers || 5
|
186
|
+
end
|
187
|
+
|
188
|
+
def volume_args
|
189
|
+
if raw_config.volumes.present?
|
190
|
+
argumentize "--volume", raw_config.volumes
|
191
|
+
else
|
192
|
+
[]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def logging_args
|
197
|
+
logging.args
|
198
|
+
end
|
199
|
+
|
200
|
+
def readiness_delay
|
201
|
+
raw_config.readiness_delay || 7
|
202
|
+
end
|
203
|
+
|
204
|
+
def deploy_timeout
|
205
|
+
raw_config.deploy_timeout || 30
|
206
|
+
end
|
207
|
+
|
208
|
+
def drain_timeout
|
209
|
+
raw_config.drain_timeout || 30
|
210
|
+
end
|
211
|
+
|
212
|
+
def run_directory
|
213
|
+
".kamal"
|
214
|
+
end
|
215
|
+
|
216
|
+
def apps_directory
|
217
|
+
File.join run_directory, "apps"
|
218
|
+
end
|
219
|
+
|
220
|
+
def app_directory
|
221
|
+
File.join apps_directory, service_and_destination
|
222
|
+
end
|
223
|
+
|
224
|
+
def env_directory
|
225
|
+
File.join app_directory, "env"
|
226
|
+
end
|
227
|
+
|
228
|
+
def assets_directory
|
229
|
+
File.join app_directory, "assets"
|
230
|
+
end
|
231
|
+
|
232
|
+
def hooks_path
|
233
|
+
raw_config.hooks_path || ".kamal/hooks"
|
234
|
+
end
|
235
|
+
|
236
|
+
def asset_path
|
237
|
+
raw_config.asset_path
|
238
|
+
end
|
239
|
+
|
240
|
+
def error_pages_path
|
241
|
+
raw_config.error_pages_path
|
242
|
+
end
|
243
|
+
|
244
|
+
def env_tags
|
245
|
+
@env_tags ||= if (tags = raw_config.env["tags"])
|
246
|
+
tags.collect { |name, config| Env::Tag.new(name, config: config, secrets: secrets) }
|
247
|
+
else
|
248
|
+
[]
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def env_tag(name)
|
253
|
+
env_tags.detect { |t| t.name == name.to_s }
|
254
|
+
end
|
255
|
+
|
256
|
+
def to_h
|
257
|
+
{
|
258
|
+
roles: role_names,
|
259
|
+
hosts: all_hosts,
|
260
|
+
primary_host: primary_host,
|
261
|
+
version: version,
|
262
|
+
repository: repository,
|
263
|
+
absolute_image: absolute_image,
|
264
|
+
service_with_version: service_with_version,
|
265
|
+
volume_args: volume_args,
|
266
|
+
ssh_options: ssh.to_h,
|
267
|
+
sshkit: sshkit.to_h,
|
268
|
+
builder: builder.to_h,
|
269
|
+
accessories: raw_config.accessories,
|
270
|
+
logging: logging_args
|
271
|
+
}.compact
|
272
|
+
end
|
273
|
+
|
274
|
+
private
|
275
|
+
# Will raise ArgumentError if any required config keys are missing
|
276
|
+
def ensure_destination_if_required
|
277
|
+
if require_destination? && destination.nil?
|
278
|
+
raise ArgumentError, "You must specify a destination"
|
279
|
+
end
|
280
|
+
|
281
|
+
true
|
282
|
+
end
|
283
|
+
|
284
|
+
def ensure_required_keys_present
|
285
|
+
%i[ service image registry ].each do |key|
|
286
|
+
raise Kamal::ConfigurationError, "Missing required configuration for #{key}" unless raw_config[key].present?
|
287
|
+
end
|
288
|
+
|
289
|
+
if raw_config.servers.nil?
|
290
|
+
raise Kamal::ConfigurationError, "No servers or accessories specified" unless raw_config.accessories.present?
|
291
|
+
else
|
292
|
+
unless role(primary_role_name).present?
|
293
|
+
raise Kamal::ConfigurationError, "The primary_role #{primary_role_name} isn't defined"
|
294
|
+
end
|
295
|
+
|
296
|
+
if primary_role.hosts.empty?
|
297
|
+
raise Kamal::ConfigurationError, "No servers specified for the #{primary_role.name} primary_role"
|
298
|
+
end
|
299
|
+
|
300
|
+
unless allow_empty_roles?
|
301
|
+
roles.each do |role|
|
302
|
+
if role.hosts.empty?
|
303
|
+
raise Kamal::ConfigurationError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
|
304
|
+
end
|
305
|
+
end
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
true
|
310
|
+
end
|
311
|
+
|
312
|
+
def ensure_valid_service_name
|
313
|
+
raise Kamal::ConfigurationError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/i
|
314
|
+
|
315
|
+
true
|
316
|
+
end
|
317
|
+
|
318
|
+
def ensure_valid_kamal_version
|
319
|
+
if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)
|
320
|
+
raise Kamal::ConfigurationError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"
|
321
|
+
end
|
322
|
+
|
323
|
+
true
|
324
|
+
end
|
325
|
+
|
326
|
+
def ensure_retain_containers_valid
|
327
|
+
raise Kamal::ConfigurationError, "Must retain at least 1 container" if retain_containers < 1
|
328
|
+
|
329
|
+
true
|
330
|
+
end
|
331
|
+
|
332
|
+
def ensure_no_traefik_reboot_hooks
|
333
|
+
hooks = %w[ pre-traefik-reboot post-traefik-reboot ].select { |hook_file| File.exist?(File.join(hooks_path, hook_file)) }
|
334
|
+
|
335
|
+
if hooks.any?
|
336
|
+
raise Kamal::ConfigurationError, "Found #{hooks.join(", ")}, these should be renamed to (pre|post)-proxy-reboot"
|
337
|
+
end
|
338
|
+
|
339
|
+
true
|
340
|
+
end
|
341
|
+
|
342
|
+
def ensure_one_host_for_ssl_roles
|
343
|
+
roles.each(&:ensure_one_host_for_ssl)
|
344
|
+
|
345
|
+
true
|
346
|
+
end
|
347
|
+
|
348
|
+
def ensure_unique_hosts_for_ssl_roles
|
349
|
+
hosts = roles.select(&:ssl?).flat_map { |role| role.proxy.hosts }
|
350
|
+
duplicates = hosts.tally.filter_map { |host, count| host if count > 1 }
|
351
|
+
|
352
|
+
raise Kamal::ConfigurationError, "Different roles can't share the same host for SSL: #{duplicates.join(", ")}" if duplicates.any?
|
353
|
+
|
354
|
+
true
|
355
|
+
end
|
356
|
+
|
357
|
+
def role_names
|
358
|
+
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
359
|
+
end
|
360
|
+
|
361
|
+
def git_version
|
362
|
+
@git_version ||=
|
363
|
+
if Kamal::Git.used?
|
364
|
+
if Kamal::Git.uncommitted_changes.present? && !builder.git_clone?
|
365
|
+
uncommitted_suffix = "_uncommitted_#{SecureRandom.hex(8)}"
|
366
|
+
end
|
367
|
+
[ Kamal::Git.revision, uncommitted_suffix ].compact.join
|
368
|
+
else
|
369
|
+
raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
|
370
|
+
end
|
371
|
+
end
|
372
|
+
end
|