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