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,70 @@
|
|
|
1
|
+
# SSH configuration
|
|
2
|
+
#
|
|
3
|
+
# Kamal uses SSH to connect and run commands on your hosts.
|
|
4
|
+
# By default, it will attempt to connect to the root user on port 22.
|
|
5
|
+
#
|
|
6
|
+
# If you are using a non-root user, you may need to bootstrap your servers manually before using them with Kamal. On Ubuntu, you’d do:
|
|
7
|
+
#
|
|
8
|
+
# ```shell
|
|
9
|
+
# sudo apt update
|
|
10
|
+
# sudo apt upgrade -y
|
|
11
|
+
# sudo apt install -y docker.io curl git
|
|
12
|
+
# sudo usermod -a -G docker app
|
|
13
|
+
# ```
|
|
14
|
+
|
|
15
|
+
# SSH options
|
|
16
|
+
#
|
|
17
|
+
# The options are specified under the ssh key in the configuration file.
|
|
18
|
+
ssh:
|
|
19
|
+
|
|
20
|
+
# The SSH user
|
|
21
|
+
#
|
|
22
|
+
# Defaults to `root`:
|
|
23
|
+
user: app
|
|
24
|
+
|
|
25
|
+
# The SSH port
|
|
26
|
+
#
|
|
27
|
+
# Defaults to 22:
|
|
28
|
+
port: "2222"
|
|
29
|
+
|
|
30
|
+
# Proxy host
|
|
31
|
+
#
|
|
32
|
+
# Specified in the form <host> or <user>@<host>:
|
|
33
|
+
proxy: root@proxy-host
|
|
34
|
+
|
|
35
|
+
# Proxy command
|
|
36
|
+
#
|
|
37
|
+
# A custom proxy command, required for older versions of SSH:
|
|
38
|
+
proxy_command: "ssh -W %h:%p user@proxy"
|
|
39
|
+
|
|
40
|
+
# Log level
|
|
41
|
+
#
|
|
42
|
+
# Defaults to `fatal`. Set this to `debug` if you are having SSH connection issues.
|
|
43
|
+
log_level: debug
|
|
44
|
+
|
|
45
|
+
# Keys only
|
|
46
|
+
#
|
|
47
|
+
# Set to `true` to use only private keys from the `keys` and `key_data` parameters,
|
|
48
|
+
# even if ssh-agent offers more identities. This option is intended for
|
|
49
|
+
# situations where ssh-agent offers many different identities or you
|
|
50
|
+
# need to overwrite all identities and force a single one.
|
|
51
|
+
keys_only: false
|
|
52
|
+
|
|
53
|
+
# Keys
|
|
54
|
+
#
|
|
55
|
+
# An array of file names of private keys to use for public key
|
|
56
|
+
# and host-based authentication:
|
|
57
|
+
keys: [ "~/.ssh/id.pem" ]
|
|
58
|
+
|
|
59
|
+
# Key data
|
|
60
|
+
#
|
|
61
|
+
# An array of strings, with each element of the array being
|
|
62
|
+
# a raw private key in PEM format.
|
|
63
|
+
key_data: [ "-----BEGIN OPENSSH PRIVATE KEY-----" ]
|
|
64
|
+
|
|
65
|
+
# Config
|
|
66
|
+
#
|
|
67
|
+
# Set to true to load the default OpenSSH config files (~/.ssh/config,
|
|
68
|
+
# /etc/ssh_config), to false ignore config files, or to a file path
|
|
69
|
+
# (or array of paths) to load specific configuration. Defaults to true.
|
|
70
|
+
config: true
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# SSHKit
|
|
2
|
+
#
|
|
3
|
+
# [SSHKit](https://github.com/capistrano/sshkit) is the SSH toolkit used by Kamal.
|
|
4
|
+
#
|
|
5
|
+
# The default, settings should be sufficient for most use cases, but
|
|
6
|
+
# when connecting to a large number of hosts, you may need to adjust.
|
|
7
|
+
|
|
8
|
+
# SSHKit options
|
|
9
|
+
#
|
|
10
|
+
# The options are specified under the sshkit key in the configuration file.
|
|
11
|
+
sshkit:
|
|
12
|
+
|
|
13
|
+
# Max concurrent starts
|
|
14
|
+
#
|
|
15
|
+
# Creating SSH connections concurrently can be an issue when deploying to many servers.
|
|
16
|
+
# By default, Kamal will limit concurrent connection starts to 30 at a time.
|
|
17
|
+
max_concurrent_starts: 10
|
|
18
|
+
|
|
19
|
+
# Pool idle timeout
|
|
20
|
+
#
|
|
21
|
+
# Kamal sets a long idle timeout of 900 seconds on connections to try to avoid
|
|
22
|
+
# re-connection storms after an idle period, such as building an image or waiting for CI.
|
|
23
|
+
pool_idle_timeout: 300
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Kamal::Configuration::Env::Tag
|
|
2
|
+
attr_reader :name, :config, :secrets
|
|
3
|
+
|
|
4
|
+
def initialize(name, config:, secrets:)
|
|
5
|
+
@name = name
|
|
6
|
+
@config = config
|
|
7
|
+
@secrets = secrets
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def env
|
|
11
|
+
Kamal::Configuration::Env.new(config: config, secrets: secrets)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
class Kamal::Configuration::Env
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
attr_reader :context, :secrets
|
|
5
|
+
attr_reader :clear, :secret_keys
|
|
6
|
+
delegate :argumentize, to: Kamal::Utils
|
|
7
|
+
|
|
8
|
+
def initialize(config:, secrets:, context: "env")
|
|
9
|
+
@clear = config.fetch("clear", config.key?("secret") || config.key?("tags") ? {} : config)
|
|
10
|
+
@secrets = secrets
|
|
11
|
+
@secret_keys = config.fetch("secret", [])
|
|
12
|
+
@context = context
|
|
13
|
+
validate! config, context: context, with: Kamal::Configuration::Validator::Env
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def clear_args
|
|
17
|
+
argumentize("--env", clear)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def secrets_io
|
|
21
|
+
Kamal::EnvFile.new(secret_keys.to_h { |key| [ key, secrets[key] ] }).to_io
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def merge(other)
|
|
25
|
+
self.class.new \
|
|
26
|
+
config: { "clear" => clear.merge(other.clear), "secret" => secret_keys | other.secret_keys },
|
|
27
|
+
secrets: secrets
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
class Kamal::Configuration::Logging
|
|
2
|
+
delegate :optionize, :argumentize, to: Kamal::Utils
|
|
3
|
+
|
|
4
|
+
include Kamal::Configuration::Validation
|
|
5
|
+
|
|
6
|
+
attr_reader :logging_config
|
|
7
|
+
|
|
8
|
+
def initialize(logging_config:, context: "logging")
|
|
9
|
+
@logging_config = logging_config || {}
|
|
10
|
+
validate! @logging_config, context: context
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def driver
|
|
14
|
+
logging_config["driver"]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def options
|
|
18
|
+
logging_config.fetch("options", {})
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def merge(other)
|
|
22
|
+
self.class.new logging_config: logging_config.deep_merge(other.logging_config)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def args
|
|
26
|
+
if driver.present? || options.present?
|
|
27
|
+
optionize({ "log-driver" => driver }.compact) +
|
|
28
|
+
argumentize("--log-opt", options)
|
|
29
|
+
else
|
|
30
|
+
argumentize("--log-opt", { "max-size" => "10m" })
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
class Kamal::Configuration::Proxy
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
DEFAULT_LOG_REQUEST_HEADERS = [ "Cache-Control", "Last-Modified", "User-Agent" ]
|
|
5
|
+
CONTAINER_NAME = "kamal-proxy"
|
|
6
|
+
|
|
7
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
8
|
+
|
|
9
|
+
attr_reader :config, :proxy_config
|
|
10
|
+
|
|
11
|
+
def initialize(config:, proxy_config:, context: "proxy")
|
|
12
|
+
@config = config
|
|
13
|
+
@proxy_config = proxy_config
|
|
14
|
+
validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def app_port
|
|
18
|
+
proxy_config.fetch("app_port", 80)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def ssl?
|
|
22
|
+
proxy_config.fetch("ssl", false)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def hosts
|
|
26
|
+
proxy_config["hosts"] || proxy_config["host"]&.split(",") || []
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def deploy_options
|
|
30
|
+
{
|
|
31
|
+
host: hosts,
|
|
32
|
+
tls: proxy_config["ssl"].presence,
|
|
33
|
+
"tls-on-demand-url": proxy_config["tls_on_demand_url"],
|
|
34
|
+
"deploy-timeout": seconds_duration(config.deploy_timeout),
|
|
35
|
+
"drain-timeout": seconds_duration(config.drain_timeout),
|
|
36
|
+
"health-check-interval": seconds_duration(proxy_config.dig("healthcheck", "interval")),
|
|
37
|
+
"health-check-timeout": seconds_duration(proxy_config.dig("healthcheck", "timeout")),
|
|
38
|
+
"health-check-path": proxy_config.dig("healthcheck", "path"),
|
|
39
|
+
"target-timeout": seconds_duration(proxy_config["response_timeout"]),
|
|
40
|
+
"buffer-requests": proxy_config.fetch("buffering", { "requests": true }).fetch("requests", true),
|
|
41
|
+
"buffer-responses": proxy_config.fetch("buffering", { "responses": true }).fetch("responses", true),
|
|
42
|
+
"buffer-memory": proxy_config.dig("buffering", "memory"),
|
|
43
|
+
"max-request-body": proxy_config.dig("buffering", "max_request_body"),
|
|
44
|
+
"max-response-body": proxy_config.dig("buffering", "max_response_body"),
|
|
45
|
+
"forward-headers": proxy_config.dig("forward_headers"),
|
|
46
|
+
"log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
|
|
47
|
+
"log-response-header": proxy_config.dig("logging", "response_headers")
|
|
48
|
+
}.compact
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def deploy_command_args(target:)
|
|
52
|
+
optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def merge(other)
|
|
56
|
+
self.class.new config: config, proxy_config: proxy_config.deep_merge(other.proxy_config)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
private
|
|
60
|
+
def seconds_duration(value)
|
|
61
|
+
value ? "#{value}s" : nil
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
class Kamal::Configuration::Registry
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
attr_reader :registry_config, :secrets
|
|
5
|
+
|
|
6
|
+
def initialize(config:)
|
|
7
|
+
@registry_config = config.raw_config.registry || {}
|
|
8
|
+
@secrets = config.secrets
|
|
9
|
+
validate! registry_config, with: Kamal::Configuration::Validator::Registry
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def server
|
|
13
|
+
registry_config["server"]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def username
|
|
17
|
+
lookup("username")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def password
|
|
21
|
+
lookup("password")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
def lookup(key)
|
|
26
|
+
if registry_config[key].is_a?(Array)
|
|
27
|
+
secrets[registry_config[key].first]
|
|
28
|
+
else
|
|
29
|
+
registry_config[key]
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
class Kamal::Configuration::Role
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
|
5
|
+
|
|
6
|
+
attr_reader :name, :config, :specialized_env, :specialized_logging, :specialized_proxy
|
|
7
|
+
|
|
8
|
+
alias to_s name
|
|
9
|
+
|
|
10
|
+
def initialize(name, config:)
|
|
11
|
+
@name, @config = name.inquiry, config
|
|
12
|
+
validate! \
|
|
13
|
+
specializations,
|
|
14
|
+
example: validation_yml["servers"]["workers"],
|
|
15
|
+
context: "servers/#{name}",
|
|
16
|
+
with: Kamal::Configuration::Validator::Role
|
|
17
|
+
|
|
18
|
+
@specialized_env = Kamal::Configuration::Env.new \
|
|
19
|
+
config: specializations.fetch("env", {}),
|
|
20
|
+
secrets: config.secrets,
|
|
21
|
+
context: "servers/#{name}/env"
|
|
22
|
+
|
|
23
|
+
@specialized_logging = Kamal::Configuration::Logging.new \
|
|
24
|
+
logging_config: specializations.fetch("logging", {}),
|
|
25
|
+
context: "servers/#{name}/logging"
|
|
26
|
+
|
|
27
|
+
initialize_specialized_proxy
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def primary_host
|
|
31
|
+
hosts.first
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def hosts
|
|
35
|
+
tagged_hosts.keys
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def env_tags(host)
|
|
39
|
+
tagged_hosts.fetch(host).collect { |tag| config.env_tag(tag) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def cmd
|
|
43
|
+
specializations["cmd"]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def option_args
|
|
47
|
+
if args = specializations["options"]
|
|
48
|
+
optionize args
|
|
49
|
+
else
|
|
50
|
+
[]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def labels
|
|
55
|
+
default_labels.merge(custom_labels)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def label_args
|
|
59
|
+
argumentize "--label", labels
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def logging_args
|
|
63
|
+
logging.args
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def logging
|
|
67
|
+
@logging ||= config.logging.merge(specialized_logging)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def proxy
|
|
71
|
+
@proxy ||= config.proxy.merge(specialized_proxy) if running_proxy?
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def running_proxy?
|
|
75
|
+
@running_proxy
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def ssl?
|
|
79
|
+
running_proxy? && proxy.ssl?
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def stop_args
|
|
83
|
+
# When deploying with the proxy, kamal-proxy will drain request before returning so we don't need to wait.
|
|
84
|
+
timeout = running_proxy? ? nil : config.drain_timeout
|
|
85
|
+
|
|
86
|
+
[ *argumentize("-t", timeout) ]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def env(host)
|
|
90
|
+
@envs ||= {}
|
|
91
|
+
@envs[host] ||= [ config.env, specialized_env, *env_tags(host).map(&:env) ].reduce(:merge)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def env_args(host)
|
|
95
|
+
[ *env(host).clear_args, *argumentize("--env-file", secrets_path) ]
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def env_directory
|
|
99
|
+
File.join(config.env_directory, "roles")
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def secrets_io(host)
|
|
103
|
+
env(host).secrets_io
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def secrets_path
|
|
107
|
+
File.join(config.env_directory, "roles", "#{name}.env")
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def asset_volume_args
|
|
111
|
+
asset_volume&.docker_args
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def primary?
|
|
116
|
+
name == @config.primary_role_name
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def container_name(version = nil)
|
|
121
|
+
[ container_prefix, version || config.version ].compact.join("-")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def container_prefix
|
|
125
|
+
[ config.service, name, config.destination ].compact.join("-")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def asset_path
|
|
130
|
+
specializations["asset_path"] || config.asset_path
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def assets?
|
|
134
|
+
asset_path.present? && running_proxy?
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def asset_volume(version = config.version)
|
|
138
|
+
if assets?
|
|
139
|
+
Kamal::Configuration::Volume.new \
|
|
140
|
+
host_path: asset_volume_directory(version), container_path: asset_path
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def asset_extracted_directory(version = config.version)
|
|
145
|
+
File.join config.assets_directory, "extracted", [ name, version ].join("-")
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def asset_volume_directory(version = config.version)
|
|
149
|
+
File.join config.assets_directory, "volumes", [ name, version ].join("-")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def ensure_one_host_for_ssl
|
|
153
|
+
if running_proxy? && proxy.ssl? && hosts.size > 1
|
|
154
|
+
raise Kamal::ConfigurationError, "SSL is only supported on a single server, found #{hosts.size} servers for role #{name}"
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
private
|
|
159
|
+
def initialize_specialized_proxy
|
|
160
|
+
proxy_specializations = specializations["proxy"]
|
|
161
|
+
|
|
162
|
+
if primary?
|
|
163
|
+
# only false means no proxy for non-primary roles
|
|
164
|
+
@running_proxy = proxy_specializations != false
|
|
165
|
+
else
|
|
166
|
+
# false and nil both mean no proxy for non-primary roles
|
|
167
|
+
@running_proxy = !!proxy_specializations
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
if running_proxy?
|
|
171
|
+
proxy_config = proxy_specializations == true || proxy_specializations.nil? ? {} : proxy_specializations
|
|
172
|
+
|
|
173
|
+
@specialized_proxy = Kamal::Configuration::Proxy.new \
|
|
174
|
+
config: config,
|
|
175
|
+
proxy_config: proxy_config,
|
|
176
|
+
context: "servers/#{name}/proxy"
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def tagged_hosts
|
|
181
|
+
{}.tap do |tagged_hosts|
|
|
182
|
+
extract_hosts_from_config.map do |host_config|
|
|
183
|
+
if host_config.is_a?(Hash)
|
|
184
|
+
host, tags = host_config.first
|
|
185
|
+
tagged_hosts[host] = Array(tags)
|
|
186
|
+
elsif host_config.is_a?(String)
|
|
187
|
+
tagged_hosts[host_config] = []
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def extract_hosts_from_config
|
|
194
|
+
if config.raw_config.servers.is_a?(Array)
|
|
195
|
+
config.raw_config.servers
|
|
196
|
+
else
|
|
197
|
+
servers = config.raw_config.servers[name]
|
|
198
|
+
servers.is_a?(Array) ? servers : Array(servers["hosts"])
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def default_labels
|
|
203
|
+
{ "service" => config.service, "role" => name, "destination" => config.destination }
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def specializations
|
|
207
|
+
if config.raw_config.servers.is_a?(Array) || config.raw_config.servers[name].is_a?(Array)
|
|
208
|
+
{}
|
|
209
|
+
else
|
|
210
|
+
config.raw_config.servers[name]
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def custom_labels
|
|
215
|
+
Hash.new.tap do |labels|
|
|
216
|
+
labels.merge!(config.labels) if config.labels.present?
|
|
217
|
+
labels.merge!(specializations["labels"]) if specializations["labels"].present?
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class Kamal::Configuration::Servers
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
attr_reader :config, :servers_config, :roles
|
|
5
|
+
|
|
6
|
+
def initialize(config:)
|
|
7
|
+
@config = config
|
|
8
|
+
@servers_config = config.raw_config.servers
|
|
9
|
+
validate! servers_config, with: Kamal::Configuration::Validator::Servers
|
|
10
|
+
|
|
11
|
+
@roles = role_names.map { |role_name| Kamal::Configuration::Role.new role_name, config: config }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
def role_names
|
|
16
|
+
servers_config.is_a?(Array) ? [ "web" ] : servers_config.keys.sort
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
class Kamal::Configuration::Ssh
|
|
2
|
+
LOGGER = ::Logger.new(STDERR)
|
|
3
|
+
|
|
4
|
+
include Kamal::Configuration::Validation
|
|
5
|
+
|
|
6
|
+
attr_reader :ssh_config
|
|
7
|
+
|
|
8
|
+
def initialize(config:)
|
|
9
|
+
@ssh_config = config.raw_config.ssh || {}
|
|
10
|
+
validate! ssh_config
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def user
|
|
14
|
+
ssh_config.fetch("user", "root")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def port
|
|
18
|
+
ssh_config.fetch("port", 22)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def proxy
|
|
22
|
+
if (proxy = ssh_config["proxy"])
|
|
23
|
+
Net::SSH::Proxy::Jump.new(proxy.include?("@") ? proxy : "root@#{proxy}")
|
|
24
|
+
elsif (proxy_command = ssh_config["proxy_command"])
|
|
25
|
+
Net::SSH::Proxy::Command.new(proxy_command)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def keys_only
|
|
30
|
+
ssh_config["keys_only"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def keys
|
|
34
|
+
ssh_config["keys"]
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def key_data
|
|
38
|
+
ssh_config["key_data"]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def options
|
|
42
|
+
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30, keys_only: keys_only, keys: keys, key_data: key_data }.compact
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
options.except(:logger).merge(log_level: log_level)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
def logger
|
|
51
|
+
LOGGER.tap { |logger| logger.level = log_level }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def log_level
|
|
55
|
+
ssh_config.fetch("log_level", :fatal)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
class Kamal::Configuration::Sshkit
|
|
2
|
+
include Kamal::Configuration::Validation
|
|
3
|
+
|
|
4
|
+
attr_reader :sshkit_config
|
|
5
|
+
|
|
6
|
+
def initialize(config:)
|
|
7
|
+
@sshkit_config = config.raw_config.sshkit || {}
|
|
8
|
+
validate! sshkit_config
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def max_concurrent_starts
|
|
12
|
+
sshkit_config.fetch("max_concurrent_starts", 30)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def pool_idle_timeout
|
|
16
|
+
sshkit_config.fetch("pool_idle_timeout", 900)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_h
|
|
20
|
+
sshkit_config
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "yaml"
|
|
2
|
+
require "active_support/inflector"
|
|
3
|
+
|
|
4
|
+
module Kamal::Configuration::Validation
|
|
5
|
+
extend ActiveSupport::Concern
|
|
6
|
+
|
|
7
|
+
class_methods do
|
|
8
|
+
def validation_doc
|
|
9
|
+
@validation_doc ||= File.read(File.join(File.dirname(__FILE__), "docs", "#{validation_config_key}.yml"))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def validation_config_key
|
|
13
|
+
@validation_config_key ||= name.demodulize.underscore
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def validate!(config, example: nil, context: nil, with: Kamal::Configuration::Validator)
|
|
18
|
+
context ||= self.class.validation_config_key
|
|
19
|
+
example ||= validation_yml[self.class.validation_config_key]
|
|
20
|
+
|
|
21
|
+
with.new(config, example: example, context: context).validate!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def validation_yml
|
|
25
|
+
@validation_yml ||= YAML.load(self.class.validation_doc)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class Kamal::Configuration::Validator::Alias < Kamal::Configuration::Validator
|
|
2
|
+
def validate!
|
|
3
|
+
super
|
|
4
|
+
|
|
5
|
+
name = context.delete_prefix("aliases/")
|
|
6
|
+
|
|
7
|
+
if name !~ /\A[a-z0-9_-]+\z/
|
|
8
|
+
error "Invalid alias name: '#{name}'. Must only contain lowercase letters, alphanumeric, hyphens and underscores."
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
if Kamal::Cli::Main.commands.include?(name)
|
|
12
|
+
error "Alias '#{name}' conflicts with a built-in command."
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Kamal::Configuration::Validator::Builder < Kamal::Configuration::Validator
|
|
2
|
+
def validate!
|
|
3
|
+
super
|
|
4
|
+
|
|
5
|
+
if config["cache"] && config["cache"]["type"]
|
|
6
|
+
error "Invalid cache type: #{config["cache"]["type"]}" unless [ "gha", "registry" ].include?(config["cache"]["type"])
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
error "Builder arch not set" unless config["arch"].present?
|
|
10
|
+
|
|
11
|
+
error "Cannot disable local builds, no remote is set" if config["local"] == false && config["remote"].blank?
|
|
12
|
+
end
|
|
13
|
+
end
|