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,129 @@
|
|
1
|
+
class Kamal::Configuration::Proxy::Boot
|
2
|
+
MINIMUM_VERSION = "v0.9.0"
|
3
|
+
DEFAULT_HTTP_PORT = 80
|
4
|
+
DEFAULT_HTTPS_PORT = 443
|
5
|
+
DEFAULT_LOG_MAX_SIZE = "10m"
|
6
|
+
|
7
|
+
attr_reader :config
|
8
|
+
delegate :argumentize, :optionize, to: Kamal::Utils
|
9
|
+
|
10
|
+
def initialize(config:)
|
11
|
+
@config = config
|
12
|
+
end
|
13
|
+
|
14
|
+
def publish_args(http_port, https_port, bind_ips = nil)
|
15
|
+
ensure_valid_bind_ips(bind_ips)
|
16
|
+
|
17
|
+
(bind_ips || [ nil ]).map do |bind_ip|
|
18
|
+
bind_ip = format_bind_ip(bind_ip)
|
19
|
+
publish_http = [ bind_ip, http_port, DEFAULT_HTTP_PORT ].compact.join(":")
|
20
|
+
publish_https = [ bind_ip, https_port, DEFAULT_HTTPS_PORT ].compact.join(":")
|
21
|
+
|
22
|
+
argumentize "--publish", [ publish_http, publish_https ]
|
23
|
+
end.join(" ")
|
24
|
+
end
|
25
|
+
|
26
|
+
def logging_args(max_size)
|
27
|
+
argumentize "--log-opt", "max-size=#{max_size}" if max_size.present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def default_boot_options
|
31
|
+
[
|
32
|
+
*(publish_args(DEFAULT_HTTP_PORT, DEFAULT_HTTPS_PORT, nil)),
|
33
|
+
*(logging_args(DEFAULT_LOG_MAX_SIZE))
|
34
|
+
]
|
35
|
+
end
|
36
|
+
|
37
|
+
def repository_name
|
38
|
+
"basecamp"
|
39
|
+
end
|
40
|
+
|
41
|
+
def image_name
|
42
|
+
"kamal-proxy"
|
43
|
+
end
|
44
|
+
|
45
|
+
def image_default
|
46
|
+
"#{repository_name}/#{image_name}"
|
47
|
+
end
|
48
|
+
|
49
|
+
def container_name
|
50
|
+
"kamal-proxy"
|
51
|
+
end
|
52
|
+
|
53
|
+
def host_directory
|
54
|
+
File.join config.run_directory, "proxy"
|
55
|
+
end
|
56
|
+
|
57
|
+
def options_file
|
58
|
+
File.join host_directory, "options"
|
59
|
+
end
|
60
|
+
|
61
|
+
def image_file
|
62
|
+
File.join host_directory, "image"
|
63
|
+
end
|
64
|
+
|
65
|
+
def image_version_file
|
66
|
+
File.join host_directory, "image_version"
|
67
|
+
end
|
68
|
+
|
69
|
+
def run_command_file
|
70
|
+
File.join host_directory, "run_command"
|
71
|
+
end
|
72
|
+
|
73
|
+
def apps_directory
|
74
|
+
File.join host_directory, "apps-config"
|
75
|
+
end
|
76
|
+
|
77
|
+
def apps_container_directory
|
78
|
+
"/home/kamal-proxy/.apps-config"
|
79
|
+
end
|
80
|
+
|
81
|
+
def apps_volume
|
82
|
+
Kamal::Configuration::Volume.new \
|
83
|
+
host_path: apps_directory,
|
84
|
+
container_path: apps_container_directory
|
85
|
+
end
|
86
|
+
|
87
|
+
def app_directory
|
88
|
+
File.join apps_directory, config.service_and_destination
|
89
|
+
end
|
90
|
+
|
91
|
+
def app_container_directory
|
92
|
+
File.join apps_container_directory, config.service_and_destination
|
93
|
+
end
|
94
|
+
|
95
|
+
def error_pages_directory
|
96
|
+
File.join app_directory, "error_pages"
|
97
|
+
end
|
98
|
+
|
99
|
+
def error_pages_container_directory
|
100
|
+
File.join app_container_directory, "error_pages"
|
101
|
+
end
|
102
|
+
|
103
|
+
def tls_directory
|
104
|
+
File.join app_directory, "tls"
|
105
|
+
end
|
106
|
+
|
107
|
+
def tls_container_directory
|
108
|
+
File.join app_container_directory, "tls"
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def ensure_valid_bind_ips(bind_ips)
|
113
|
+
bind_ips.present? && bind_ips.each do |ip|
|
114
|
+
next if ip =~ Resolv::IPv4::Regex || ip =~ Resolv::IPv6::Regex
|
115
|
+
raise ArgumentError, "Invalid publish IP address: #{ip}"
|
116
|
+
end
|
117
|
+
|
118
|
+
true
|
119
|
+
end
|
120
|
+
|
121
|
+
def format_bind_ip(ip)
|
122
|
+
# Ensure IPv6 address inside square brackets - e.g. [::1]
|
123
|
+
if ip =~ Resolv::IPv6::Regex && ip !~ /\A\[.*\]\z/
|
124
|
+
"[#{ip}]"
|
125
|
+
else
|
126
|
+
ip
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,124 @@
|
|
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, :role_name, :secrets
|
10
|
+
|
11
|
+
def initialize(config:, proxy_config:, role_name: nil, secrets:, context: "proxy")
|
12
|
+
@config = config
|
13
|
+
@proxy_config = proxy_config
|
14
|
+
@proxy_config = {} if @proxy_config.nil?
|
15
|
+
@role_name = role_name
|
16
|
+
@secrets = secrets
|
17
|
+
validate! @proxy_config, with: Kamal::Configuration::Validator::Proxy, context: context
|
18
|
+
end
|
19
|
+
|
20
|
+
def app_port
|
21
|
+
proxy_config.fetch("app_port", 80)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ssl?
|
25
|
+
proxy_config.fetch("ssl", false)
|
26
|
+
end
|
27
|
+
|
28
|
+
def hosts
|
29
|
+
proxy_config["hosts"] || proxy_config["host"]&.split(",") || []
|
30
|
+
end
|
31
|
+
|
32
|
+
def custom_ssl_certificate?
|
33
|
+
ssl = proxy_config["ssl"]
|
34
|
+
return false unless ssl.is_a?(Hash)
|
35
|
+
ssl["certificate_pem"].present? && ssl["private_key_pem"].present?
|
36
|
+
end
|
37
|
+
|
38
|
+
def certificate_pem_content
|
39
|
+
ssl = proxy_config["ssl"]
|
40
|
+
return nil unless ssl.is_a?(Hash)
|
41
|
+
secrets[ssl["certificate_pem"]]
|
42
|
+
end
|
43
|
+
|
44
|
+
def private_key_pem_content
|
45
|
+
ssl = proxy_config["ssl"]
|
46
|
+
return nil unless ssl.is_a?(Hash)
|
47
|
+
secrets[ssl["private_key_pem"]]
|
48
|
+
end
|
49
|
+
|
50
|
+
def host_tls_cert
|
51
|
+
tls_path(config.proxy_boot.tls_directory, "cert.pem")
|
52
|
+
end
|
53
|
+
|
54
|
+
def host_tls_key
|
55
|
+
tls_path(config.proxy_boot.tls_directory, "key.pem")
|
56
|
+
end
|
57
|
+
|
58
|
+
def container_tls_cert
|
59
|
+
tls_path(config.proxy_boot.tls_container_directory, "cert.pem")
|
60
|
+
end
|
61
|
+
|
62
|
+
def container_tls_key
|
63
|
+
tls_path(config.proxy_boot.tls_container_directory, "key.pem") if custom_ssl_certificate?
|
64
|
+
end
|
65
|
+
|
66
|
+
def deploy_options
|
67
|
+
{
|
68
|
+
host: hosts,
|
69
|
+
tls: ssl? ? true : nil,
|
70
|
+
"tls-certificate-path": container_tls_cert,
|
71
|
+
"tls-private-key-path": container_tls_key,
|
72
|
+
"deploy-timeout": seconds_duration(config.deploy_timeout),
|
73
|
+
"drain-timeout": seconds_duration(config.drain_timeout),
|
74
|
+
"health-check-interval": seconds_duration(proxy_config.dig("healthcheck", "interval")),
|
75
|
+
"health-check-timeout": seconds_duration(proxy_config.dig("healthcheck", "timeout")),
|
76
|
+
"health-check-path": proxy_config.dig("healthcheck", "path"),
|
77
|
+
"target-timeout": seconds_duration(proxy_config["response_timeout"]),
|
78
|
+
"buffer-requests": proxy_config.fetch("buffering", { "requests": true }).fetch("requests", true),
|
79
|
+
"buffer-responses": proxy_config.fetch("buffering", { "responses": true }).fetch("responses", true),
|
80
|
+
"buffer-memory": proxy_config.dig("buffering", "memory"),
|
81
|
+
"max-request-body": proxy_config.dig("buffering", "max_request_body"),
|
82
|
+
"max-response-body": proxy_config.dig("buffering", "max_response_body"),
|
83
|
+
"path-prefix": proxy_config.dig("path_prefix"),
|
84
|
+
"strip-path-prefix": proxy_config.dig("strip_path_prefix"),
|
85
|
+
"forward-headers": proxy_config.dig("forward_headers"),
|
86
|
+
"tls-redirect": proxy_config.dig("ssl_redirect"),
|
87
|
+
"log-request-header": proxy_config.dig("logging", "request_headers") || DEFAULT_LOG_REQUEST_HEADERS,
|
88
|
+
"log-response-header": proxy_config.dig("logging", "response_headers"),
|
89
|
+
"error-pages": error_pages
|
90
|
+
}.compact
|
91
|
+
end
|
92
|
+
|
93
|
+
def deploy_command_args(target:)
|
94
|
+
optionize ({ target: "#{target}:#{app_port}" }).merge(deploy_options), with: "="
|
95
|
+
end
|
96
|
+
|
97
|
+
def stop_options(drain_timeout: nil, message: nil)
|
98
|
+
{
|
99
|
+
"drain-timeout": seconds_duration(drain_timeout),
|
100
|
+
message: message
|
101
|
+
}.compact
|
102
|
+
end
|
103
|
+
|
104
|
+
def stop_command_args(**options)
|
105
|
+
optionize stop_options(**options), with: "="
|
106
|
+
end
|
107
|
+
|
108
|
+
def merge(other)
|
109
|
+
self.class.new config: config, proxy_config: other.proxy_config.deep_merge(proxy_config), role_name: role_name, secrets: secrets
|
110
|
+
end
|
111
|
+
|
112
|
+
private
|
113
|
+
def tls_path(directory, filename)
|
114
|
+
File.join([ directory, role_name, filename ].compact) if custom_ssl_certificate?
|
115
|
+
end
|
116
|
+
|
117
|
+
def seconds_duration(value)
|
118
|
+
value ? "#{value}s" : nil
|
119
|
+
end
|
120
|
+
|
121
|
+
def error_pages
|
122
|
+
File.join config.proxy_boot.error_pages_container_directory, config.version if config.error_pages_path
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
class Kamal::Configuration::Registry
|
2
|
+
include Kamal::Configuration::Validation
|
3
|
+
|
4
|
+
def initialize(config:, secrets:, context: "registry")
|
5
|
+
@registry_config = config["registry"] || {}
|
6
|
+
@secrets = secrets
|
7
|
+
validate! registry_config, context: context, with: Kamal::Configuration::Validator::Registry
|
8
|
+
end
|
9
|
+
|
10
|
+
def server
|
11
|
+
registry_config["server"]
|
12
|
+
end
|
13
|
+
|
14
|
+
def username
|
15
|
+
lookup("username")
|
16
|
+
end
|
17
|
+
|
18
|
+
def password
|
19
|
+
lookup("password")
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
attr_reader :registry_config, :secrets
|
24
|
+
|
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,222 @@
|
|
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
|
+
role_config,
|
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 ||= specialized_proxy.merge(config.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 && !proxy.custom_ssl_certificate?
|
154
|
+
raise Kamal::ConfigurationError, "SSL is only supported on a single server unless you provide custom certificates, 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
|
+
secrets: config.secrets,
|
177
|
+
role_name: name,
|
178
|
+
context: "servers/#{name}/proxy"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def tagged_hosts
|
183
|
+
{}.tap do |tagged_hosts|
|
184
|
+
extract_hosts_from_config.map do |host_config|
|
185
|
+
if host_config.is_a?(Hash)
|
186
|
+
host, tags = host_config.first
|
187
|
+
tagged_hosts[host] = Array(tags)
|
188
|
+
elsif host_config.is_a?(String)
|
189
|
+
tagged_hosts[host_config] = []
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def extract_hosts_from_config
|
196
|
+
if config.raw_config.servers.is_a?(Array)
|
197
|
+
config.raw_config.servers
|
198
|
+
else
|
199
|
+
servers = config.raw_config.servers[name]
|
200
|
+
servers.is_a?(Array) ? servers : Array(servers["hosts"])
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def default_labels
|
205
|
+
{ "service" => config.service, "role" => name, "destination" => config.destination }
|
206
|
+
end
|
207
|
+
|
208
|
+
def specializations
|
209
|
+
@specializations ||= role_config.is_a?(Array) ? {} : role_config
|
210
|
+
end
|
211
|
+
|
212
|
+
def role_config
|
213
|
+
@role_config ||= config.raw_config.servers.is_a?(Array) ? {} : config.raw_config.servers[name]
|
214
|
+
end
|
215
|
+
|
216
|
+
def custom_labels
|
217
|
+
Hash.new.tap do |labels|
|
218
|
+
labels.merge!(config.labels) if config.labels.present?
|
219
|
+
labels.merge!(specializations["labels"]) if specializations["labels"].present?
|
220
|
+
end
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,25 @@
|
|
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
|
+
case servers_config
|
17
|
+
when Array
|
18
|
+
[ "web" ]
|
19
|
+
when NilClass
|
20
|
+
[]
|
21
|
+
else
|
22
|
+
servers_config.keys.sort
|
23
|
+
end
|
24
|
+
end
|
25
|
+
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,13 @@
|
|
1
|
+
class Kamal::Configuration::Validator::Accessory < Kamal::Configuration::Validator
|
2
|
+
def validate!
|
3
|
+
super
|
4
|
+
|
5
|
+
if (config.keys & [ "host", "hosts", "role", "roles", "tag", "tags" ]).size != 1
|
6
|
+
error "specify one of `host`, `hosts`, `role`, `roles`, `tag` or `tags`"
|
7
|
+
end
|
8
|
+
|
9
|
+
validate_labels!(config["labels"])
|
10
|
+
|
11
|
+
validate_docker_options!(config["options"])
|
12
|
+
end
|
13
|
+
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,15 @@
|
|
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 "buildpacks only support building for one arch" if config["pack"] && config["arch"].is_a?(Array) && config["arch"].size > 1
|
12
|
+
|
13
|
+
error "Cannot disable local builds, no remote is set" if config["local"] == false && config["remote"].blank?
|
14
|
+
end
|
15
|
+
end
|