kamal 1.1.0 → 1.3.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 +4 -4
- data/lib/kamal/cli/accessory.rb +12 -8
- data/lib/kamal/cli/base.rb +2 -2
- data/lib/kamal/cli/healthcheck.rb +1 -0
- data/lib/kamal/cli/main.rb +4 -2
- data/lib/kamal/cli/templates/deploy.yml +12 -0
- data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-traefik-reboot.sample +3 -0
- data/lib/kamal/cli/traefik.rb +12 -6
- data/lib/kamal/commander.rb +21 -4
- data/lib/kamal/commands/base.rb +1 -1
- data/lib/kamal/commands/healthcheck.rb +5 -5
- data/lib/kamal/commands/traefik.rb +9 -1
- data/lib/kamal/configuration/accessory.rb +10 -6
- data/lib/kamal/configuration/role.rb +10 -1
- data/lib/kamal/configuration/ssh.rb +5 -1
- data/lib/kamal/configuration.rb +36 -9
- data/lib/kamal/utils.rb +16 -0
- data/lib/kamal/version.rb +1 -1
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4a11bf8b2153b8bc4323e4b03e289d7c0c5e60a9dcc9012103ae701ae2c3256b
|
4
|
+
data.tar.gz: bc4b5c88e63a717fc539c71c00fb8207ee78693ade9658ac8effd3ecdd5249ab
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8cbc84077617cfd72ad225cb458b0df9d36fe88b2f2a640bec9f012b4f92272d5cd62617580e75f217715be97e88c126bfe842dcdc1ab262c82db3fc9e0c746
|
7
|
+
data.tar.gz: c3e1de191ebcf4621fe15e59ee8b976491a58e8ffbd40a8862d15040837b7669f7e26372382711b118eb357035f047d920dd7817de7fc415aa5b8aa8a7e74a60
|
data/lib/kamal/cli/accessory.rb
CHANGED
@@ -49,17 +49,21 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
49
49
|
end
|
50
50
|
end
|
51
51
|
|
52
|
-
desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container)"
|
52
|
+
desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container; use NAME=all to boot all accessories)"
|
53
53
|
def reboot(name)
|
54
54
|
mutating do
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
55
|
+
if name == "all"
|
56
|
+
KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
|
57
|
+
else
|
58
|
+
with_accessory(name) do |accessory|
|
59
|
+
on(accessory.hosts) do
|
60
|
+
execute *KAMAL.registry.login
|
61
|
+
end
|
59
62
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
+
stop(name)
|
64
|
+
remove_container(name)
|
65
|
+
boot(name, login: false)
|
66
|
+
end
|
63
67
|
end
|
64
68
|
end
|
65
69
|
end
|
data/lib/kamal/cli/base.rb
CHANGED
@@ -14,8 +14,8 @@ module Kamal::Cli
|
|
14
14
|
class_option :version, desc: "Run commands against a specific app version"
|
15
15
|
|
16
16
|
class_option :primary, type: :boolean, aliases: "-p", desc: "Run commands only on primary host instead of all"
|
17
|
-
class_option :hosts, aliases: "-h", desc: "Run commands on these hosts instead of all (separate by comma)"
|
18
|
-
class_option :roles, aliases: "-r", desc: "Run commands on these roles instead of all (separate by comma)"
|
17
|
+
class_option :hosts, aliases: "-h", desc: "Run commands on these hosts instead of all (separate by comma, supports wildcards with *)"
|
18
|
+
class_option :roles, aliases: "-r", desc: "Run commands on these roles instead of all (separate by comma, supports wildcards with *)"
|
19
19
|
|
20
20
|
class_option :config_file, aliases: "-c", default: "config/deploy.yml", desc: "Path to config file"
|
21
21
|
class_option :destination, aliases: "-d", desc: "Specify destination to be used for config file (staging -> deploy.staging.yml)"
|
@@ -3,6 +3,7 @@ class Kamal::Cli::Healthcheck < Kamal::Cli::Base
|
|
3
3
|
|
4
4
|
desc "perform", "Health check current app version"
|
5
5
|
def perform
|
6
|
+
raise "The primary host is not configured to run Traefik" unless KAMAL.config.role(KAMAL.config.primary_role).running_traefik?
|
6
7
|
on(KAMAL.primary_host) do
|
7
8
|
begin
|
8
9
|
execute *KAMAL.healthcheck.run
|
data/lib/kamal/cli/main.rb
CHANGED
@@ -38,8 +38,10 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
38
38
|
say "Ensure Traefik is running...", :magenta
|
39
39
|
invoke "kamal:cli:traefik:boot", [], invoke_options
|
40
40
|
|
41
|
-
|
42
|
-
|
41
|
+
if KAMAL.config.role(KAMAL.config.primary_role).running_traefik?
|
42
|
+
say "Ensure app can pass healthcheck...", :magenta
|
43
|
+
invoke "kamal:cli:healthcheck:perform", [], invoke_options
|
44
|
+
end
|
43
45
|
|
44
46
|
say "Detect stale containers...", :magenta
|
45
47
|
invoke "kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true)
|
@@ -83,3 +83,15 @@ registry:
|
|
83
83
|
# boot:
|
84
84
|
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
|
85
85
|
# wait: 2
|
86
|
+
|
87
|
+
# Configure the role used to determine the primary_host. This host takes
|
88
|
+
# deploy locks, runs health checks during the deploy, and follow logs, etc.
|
89
|
+
#
|
90
|
+
# Caution: there's no support for role renaming yet, so be careful to cleanup
|
91
|
+
# the previous role on the deployed hosts.
|
92
|
+
# primary_role: web
|
93
|
+
|
94
|
+
# Controls if we abort when see a role with no hosts. Disabling this may be
|
95
|
+
# useful for more complex deploy configurations.
|
96
|
+
#
|
97
|
+
# allow_empty_roles: false
|
data/lib/kamal/cli/traefik.rb
CHANGED
@@ -13,12 +13,18 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
|
|
13
13
|
option :rolling, type: :boolean, default: false, desc: "Reboot traefik on hosts in sequence, rather than in parallel"
|
14
14
|
def reboot
|
15
15
|
mutating do
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
16
|
+
host_groups = options[:rolling] ? KAMAL.traefik_hosts : [KAMAL.traefik_hosts]
|
17
|
+
host_groups.each do |hosts|
|
18
|
+
host_list = Array(hosts).join(",")
|
19
|
+
run_hook "pre-traefik-reboot", hosts: host_list
|
20
|
+
on(hosts) do
|
21
|
+
execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
|
22
|
+
execute *KAMAL.registry.login
|
23
|
+
execute *KAMAL.traefik.stop
|
24
|
+
execute *KAMAL.traefik.remove_container
|
25
|
+
execute *KAMAL.traefik.run
|
26
|
+
end
|
27
|
+
run_hook "post-traefik-reboot", hosts: host_list
|
22
28
|
end
|
23
29
|
end
|
24
30
|
end
|
data/lib/kamal/commander.rb
CHANGED
@@ -24,19 +24,36 @@ class Kamal::Commander
|
|
24
24
|
attr_reader :specific_roles, :specific_hosts
|
25
25
|
|
26
26
|
def specific_primary!
|
27
|
-
self.specific_hosts = [ config.
|
27
|
+
self.specific_hosts = [ config.primary_host ]
|
28
28
|
end
|
29
29
|
|
30
30
|
def specific_roles=(role_names)
|
31
|
-
|
31
|
+
if role_names.present?
|
32
|
+
@specific_roles = Kamal::Utils.filter_specific_items(role_names, config.roles)
|
33
|
+
|
34
|
+
if @specific_roles.empty?
|
35
|
+
raise ArgumentError, "No --roles match for #{role_names.join(',')}"
|
36
|
+
end
|
37
|
+
|
38
|
+
@specific_roles
|
39
|
+
end
|
32
40
|
end
|
33
41
|
|
34
42
|
def specific_hosts=(hosts)
|
35
|
-
|
43
|
+
if hosts.present?
|
44
|
+
@specific_hosts = Kamal::Utils.filter_specific_items(hosts, config.all_hosts)
|
45
|
+
|
46
|
+
if @specific_hosts.empty?
|
47
|
+
raise ArgumentError, "No --hosts match for #{hosts.join(',')}"
|
48
|
+
end
|
49
|
+
|
50
|
+
@specific_hosts
|
51
|
+
end
|
36
52
|
end
|
37
53
|
|
38
54
|
def primary_host
|
39
|
-
|
55
|
+
# Given a list of specific roles, make an effort to match up with the primary_role
|
56
|
+
specific_hosts&.first || specific_roles&.detect { |role| role.name == config.primary_role }&.primary_host || specific_roles&.first&.primary_host || config.primary_host
|
40
57
|
end
|
41
58
|
|
42
59
|
def primary_role
|
data/lib/kamal/commands/base.rb
CHANGED
@@ -18,7 +18,7 @@ module Kamal::Commands
|
|
18
18
|
elsif config.ssh.proxy && config.ssh.proxy.is_a?(Net::SSH::Proxy::Command)
|
19
19
|
cmd << " -o ProxyCommand='#{config.ssh.proxy.command_line_template}'"
|
20
20
|
end
|
21
|
-
cmd << " -t #{config.ssh.user}@#{host} '#{command.join(" ")}'"
|
21
|
+
cmd << " -t #{config.ssh.user}@#{host} -p #{config.ssh.port} '#{command.join(" ")}'"
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Kamal::Commands::Healthcheck < Kamal::Commands::Base
|
2
2
|
|
3
3
|
def run
|
4
|
-
|
4
|
+
primary = config.role(config.primary_role)
|
5
5
|
|
6
6
|
docker :run,
|
7
7
|
"--detach",
|
@@ -9,12 +9,12 @@ class Kamal::Commands::Healthcheck < Kamal::Commands::Base
|
|
9
9
|
"--publish", "#{exposed_port}:#{config.healthcheck["port"]}",
|
10
10
|
"--label", "service=#{config.healthcheck_service}",
|
11
11
|
"-e", "KAMAL_CONTAINER_NAME=\"#{config.healthcheck_service}\"",
|
12
|
-
*
|
13
|
-
*
|
12
|
+
*primary.env_args,
|
13
|
+
*primary.health_check_args(cord: false),
|
14
14
|
*config.volume_args,
|
15
|
-
*
|
15
|
+
*primary.option_args,
|
16
16
|
config.absolute_image,
|
17
|
-
|
17
|
+
primary.cmd
|
18
18
|
end
|
19
19
|
|
20
20
|
def status
|
@@ -6,6 +6,14 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
|
6
6
|
DEFAULT_ARGS = {
|
7
7
|
'log.level' => 'DEBUG'
|
8
8
|
}
|
9
|
+
DEFAULT_LABELS = {
|
10
|
+
# These ensure we serve a 502 rather than a 404 if no containers are available
|
11
|
+
"traefik.http.routers.catchall.entryPoints" => "http",
|
12
|
+
"traefik.http.routers.catchall.rule" => "PathPrefix(`/`)",
|
13
|
+
"traefik.http.routers.catchall.service" => "unavailable",
|
14
|
+
"traefik.http.routers.catchall.priority" => 1,
|
15
|
+
"traefik.http.services.unavailable.loadbalancer.server.port" => "0"
|
16
|
+
}
|
9
17
|
|
10
18
|
def run
|
11
19
|
docker :run, "--name traefik",
|
@@ -97,7 +105,7 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
|
97
105
|
end
|
98
106
|
|
99
107
|
def labels
|
100
|
-
config.traefik["labels"] ||
|
108
|
+
DEFAULT_LABELS.merge(config.traefik["labels"] || {})
|
101
109
|
end
|
102
110
|
|
103
111
|
def image
|
@@ -70,8 +70,8 @@ class Kamal::Configuration::Accessory
|
|
70
70
|
|
71
71
|
def directories
|
72
72
|
specifics["directories"]&.to_h do |host_to_container_mapping|
|
73
|
-
|
74
|
-
[ expand_host_path(
|
73
|
+
host_path, container_path = host_to_container_mapping.split(":")
|
74
|
+
[ expand_host_path(host_path), container_path ]
|
75
75
|
end || {}
|
76
76
|
end
|
77
77
|
|
@@ -138,13 +138,17 @@ class Kamal::Configuration::Accessory
|
|
138
138
|
|
139
139
|
def remote_directories_as_volumes
|
140
140
|
specifics["directories"]&.collect do |host_to_container_mapping|
|
141
|
-
|
142
|
-
[ expand_host_path(
|
141
|
+
host_path, container_path = host_to_container_mapping.split(":")
|
142
|
+
[ expand_host_path(host_path), container_path ].join(":")
|
143
143
|
end || []
|
144
144
|
end
|
145
145
|
|
146
|
-
def expand_host_path(
|
147
|
-
"#{service_data_directory}/#{
|
146
|
+
def expand_host_path(host_path)
|
147
|
+
absolute_path?(host_path) ? host_path : "#{service_data_directory}/#{host_path}"
|
148
|
+
end
|
149
|
+
|
150
|
+
def absolute_path?(path)
|
151
|
+
Pathname.new(path).absolute?
|
148
152
|
end
|
149
153
|
|
150
154
|
def service_data_directory
|
@@ -93,7 +93,15 @@ class Kamal::Configuration::Role
|
|
93
93
|
|
94
94
|
|
95
95
|
def running_traefik?
|
96
|
-
|
96
|
+
if specializations["traefik"].nil?
|
97
|
+
primary?
|
98
|
+
else
|
99
|
+
specializations["traefik"]
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def primary?
|
104
|
+
@config.primary_role == name
|
97
105
|
end
|
98
106
|
|
99
107
|
|
@@ -185,6 +193,7 @@ class Kamal::Configuration::Role
|
|
185
193
|
"traefik.http.services.#{traefik_service}.loadbalancer.server.scheme" => "http",
|
186
194
|
|
187
195
|
"traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
|
196
|
+
"traefik.http.routers.#{traefik_service}.priority" => "2",
|
188
197
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
|
189
198
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
|
190
199
|
"traefik.http.routers.#{traefik_service}.middlewares" => "#{traefik_service}-retry@docker"
|
@@ -9,6 +9,10 @@ class Kamal::Configuration::Ssh
|
|
9
9
|
config.fetch("user", "root")
|
10
10
|
end
|
11
11
|
|
12
|
+
def port
|
13
|
+
config.fetch("port", 22)
|
14
|
+
end
|
15
|
+
|
12
16
|
def proxy
|
13
17
|
if (proxy = config["proxy"])
|
14
18
|
Net::SSH::Proxy::Jump.new(proxy.include?("@") ? proxy : "root@#{proxy}")
|
@@ -18,7 +22,7 @@ class Kamal::Configuration::Ssh
|
|
18
22
|
end
|
19
23
|
|
20
24
|
def options
|
21
|
-
{ user: user, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30 }.compact
|
25
|
+
{ user: user, port: port, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30 }.compact
|
22
26
|
end
|
23
27
|
|
24
28
|
def to_h
|
data/lib/kamal/configuration.rb
CHANGED
@@ -25,7 +25,9 @@ class Kamal::Configuration
|
|
25
25
|
|
26
26
|
def load_config_file(file)
|
27
27
|
if file.exist?
|
28
|
-
|
28
|
+
# Newer Psych doesn't load aliases by default
|
29
|
+
load_method = YAML.respond_to?(:unsafe_load) ? :unsafe_load : :load
|
30
|
+
YAML.send(load_method, ERB.new(IO.read(file)).result).symbolize_keys
|
29
31
|
else
|
30
32
|
raise "Configuration file not found in #{file}"
|
31
33
|
end
|
@@ -89,14 +91,21 @@ class Kamal::Configuration
|
|
89
91
|
roles.flat_map(&:hosts).uniq
|
90
92
|
end
|
91
93
|
|
92
|
-
def
|
93
|
-
role(
|
94
|
+
def primary_host
|
95
|
+
role(primary_role)&.primary_host
|
94
96
|
end
|
95
97
|
|
96
|
-
def
|
97
|
-
roles.select(&:running_traefik?)
|
98
|
+
def traefik_roles
|
99
|
+
roles.select(&:running_traefik?)
|
98
100
|
end
|
99
101
|
|
102
|
+
def traefik_role_names
|
103
|
+
traefik_roles.flat_map(&:name)
|
104
|
+
end
|
105
|
+
|
106
|
+
def traefik_hosts
|
107
|
+
traefik_roles.flat_map(&:hosts).uniq
|
108
|
+
end
|
100
109
|
|
101
110
|
def repository
|
102
111
|
[ raw_config.registry["server"], image ].compact.join("/")
|
@@ -199,6 +208,14 @@ class Kamal::Configuration
|
|
199
208
|
raw_config.asset_path
|
200
209
|
end
|
201
210
|
|
211
|
+
def primary_role
|
212
|
+
raw_config.primary_role || "web"
|
213
|
+
end
|
214
|
+
|
215
|
+
def allow_empty_roles?
|
216
|
+
raw_config.allow_empty_roles
|
217
|
+
end
|
218
|
+
|
202
219
|
|
203
220
|
def valid?
|
204
221
|
ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version
|
@@ -208,7 +225,7 @@ class Kamal::Configuration
|
|
208
225
|
{
|
209
226
|
roles: role_names,
|
210
227
|
hosts: all_hosts,
|
211
|
-
primary_host:
|
228
|
+
primary_host: primary_host,
|
212
229
|
version: version,
|
213
230
|
repository: repository,
|
214
231
|
absolute_image: absolute_image,
|
@@ -247,9 +264,19 @@ class Kamal::Configuration
|
|
247
264
|
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
248
265
|
end
|
249
266
|
|
250
|
-
|
251
|
-
|
252
|
-
|
267
|
+
unless role_names.include?(primary_role)
|
268
|
+
raise ArgumentError, "The primary_role #{primary_role} isn't defined"
|
269
|
+
end
|
270
|
+
|
271
|
+
if role(primary_role).hosts.empty?
|
272
|
+
raise ArgumentError, "No servers specified for the #{primary_role} primary_role"
|
273
|
+
end
|
274
|
+
|
275
|
+
unless allow_empty_roles?
|
276
|
+
roles.each do |role|
|
277
|
+
if role.hosts.empty?
|
278
|
+
raise ArgumentError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
|
279
|
+
end
|
253
280
|
end
|
254
281
|
end
|
255
282
|
|
data/lib/kamal/utils.rb
CHANGED
@@ -58,4 +58,20 @@ module Kamal::Utils
|
|
58
58
|
.gsub(/`/, '\\\\`')
|
59
59
|
.gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$')
|
60
60
|
end
|
61
|
+
|
62
|
+
# Apply a list of host or role filters, including wildcard matches
|
63
|
+
def filter_specific_items(filters, items)
|
64
|
+
matches = []
|
65
|
+
|
66
|
+
Array(filters).select do |filter|
|
67
|
+
matches += Array(items).select do |item|
|
68
|
+
# Only allow * for a wildcard
|
69
|
+
pattern = Regexp.escape(filter).gsub('\*', '.*')
|
70
|
+
# items are roles or hosts
|
71
|
+
(item.respond_to?(:name) ? item.name : item).match(/^#{pattern}$/)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
matches
|
76
|
+
end
|
61
77
|
end
|
data/lib/kamal/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: kamal
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Heinemeier Hansson
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -204,9 +204,11 @@ files:
|
|
204
204
|
- lib/kamal/cli/server.rb
|
205
205
|
- lib/kamal/cli/templates/deploy.yml
|
206
206
|
- lib/kamal/cli/templates/sample_hooks/post-deploy.sample
|
207
|
+
- lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample
|
207
208
|
- lib/kamal/cli/templates/sample_hooks/pre-build.sample
|
208
209
|
- lib/kamal/cli/templates/sample_hooks/pre-connect.sample
|
209
210
|
- lib/kamal/cli/templates/sample_hooks/pre-deploy.sample
|
211
|
+
- lib/kamal/cli/templates/sample_hooks/pre-traefik-reboot.sample
|
210
212
|
- lib/kamal/cli/templates/template.env
|
211
213
|
- lib/kamal/cli/traefik.rb
|
212
214
|
- lib/kamal/commander.rb
|
@@ -270,7 +272,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
270
272
|
- !ruby/object:Gem::Version
|
271
273
|
version: '0'
|
272
274
|
requirements: []
|
273
|
-
rubygems_version: 3.4.
|
275
|
+
rubygems_version: 3.4.22
|
274
276
|
signing_key:
|
275
277
|
specification_version: 4
|
276
278
|
summary: Deploy web apps in containers to servers running Docker with zero downtime.
|