nocoffee-kamal 2.3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|