kamal 1.0.0 → 1.2.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/app.rb +6 -2
- data/lib/kamal/cli/base.rb +9 -4
- data/lib/kamal/cli/main.rb +6 -3
- data/lib/kamal/cli/templates/deploy.yml +13 -0
- data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +3 -0
- 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 +2 -2
- data/lib/kamal/commands/app.rb +1 -0
- data/lib/kamal/commands/base.rb +1 -1
- data/lib/kamal/commands/docker.rb +1 -1
- data/lib/kamal/commands/healthcheck.rb +1 -1
- data/lib/kamal/commands/traefik.rb +9 -1
- data/lib/kamal/configuration/role.rb +1 -0
- data/lib/kamal/configuration/ssh.rb +5 -1
- data/lib/kamal/configuration.rb +37 -13
- data/lib/kamal/utils/sensitive.rb +1 -0
- 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: efc8817e4bba2417ad480b7297b323d11bd88ec87993fb4c8a1bb09ffbb3f4a4
|
4
|
+
data.tar.gz: 6c1269582ae3ccf90e9b4532b2fecbdd0723493413f82036038a121707f76ce3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d9862df894f9e105bfe38a88ac93289e9fa89b51d69200cea6f5bd9f4483c74d31e2db39aa2d601aeb620d5f1beac5ca05efe94803e42f79cf3ba61e380b3f6d
|
7
|
+
data.tar.gz: 5e03468f451046a73425599db6dba15565d960f244d622bd2248c00e2d3f89db4f96cffed4dd6719d3025211ff0cb725a50020d81b8f09e6a64a6b41a2836b63
|
data/lib/kamal/cli/app.rb
CHANGED
@@ -147,8 +147,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
147
147
|
using_version(version_or_latest) do |version|
|
148
148
|
say "Launching command with version #{version} from new container...", :magenta
|
149
149
|
on(KAMAL.hosts) do |host|
|
150
|
-
|
151
|
-
|
150
|
+
roles = KAMAL.roles_on(host)
|
151
|
+
|
152
|
+
roles.each do |role|
|
153
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
154
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd))
|
155
|
+
end
|
152
156
|
end
|
153
157
|
end
|
154
158
|
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)"
|
@@ -24,6 +24,7 @@ module Kamal::Cli
|
|
24
24
|
|
25
25
|
def initialize(*)
|
26
26
|
super
|
27
|
+
@original_env = ENV.to_h.dup
|
27
28
|
load_envs
|
28
29
|
initialize_commander(options_with_subcommand_class_options)
|
29
30
|
end
|
@@ -37,6 +38,12 @@ module Kamal::Cli
|
|
37
38
|
end
|
38
39
|
end
|
39
40
|
|
41
|
+
def reload_envs
|
42
|
+
ENV.clear
|
43
|
+
ENV.update(@original_env)
|
44
|
+
load_envs
|
45
|
+
end
|
46
|
+
|
40
47
|
def options_with_subcommand_class_options
|
41
48
|
options.merge(@_initializer.last[:class_options] || {})
|
42
49
|
end
|
@@ -75,8 +82,6 @@ module Kamal::Cli
|
|
75
82
|
def mutating
|
76
83
|
return yield if KAMAL.holding_lock?
|
77
84
|
|
78
|
-
KAMAL.config.ensure_env_available
|
79
|
-
|
80
85
|
run_hook "pre-connect"
|
81
86
|
|
82
87
|
ensure_run_directory
|
data/lib/kamal/cli/main.rb
CHANGED
@@ -170,6 +170,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
170
170
|
end
|
171
171
|
|
172
172
|
desc "envify", "Create .env by evaluating .env.erb (or .env.staging.erb -> .env.staging when using -d staging)"
|
173
|
+
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip .env file push"
|
173
174
|
def envify
|
174
175
|
if destination = options[:destination]
|
175
176
|
env_template_path = ".env.#{destination}.erb"
|
@@ -179,10 +180,12 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
179
180
|
env_path = ".env"
|
180
181
|
end
|
181
182
|
|
182
|
-
File.write(env_path, ERB.new(File.read(env_template_path)).result, perm: 0600)
|
183
|
+
File.write(env_path, ERB.new(File.read(env_template_path), trim_mode: "-").result, perm: 0600)
|
183
184
|
|
184
|
-
|
185
|
-
|
185
|
+
unless options[:skip_push]
|
186
|
+
reload_envs
|
187
|
+
invoke "kamal:cli:env:push", options
|
188
|
+
end
|
186
189
|
end
|
187
190
|
|
188
191
|
desc "remove", "Remove Traefik, app, accessories, and registry session from servers"
|
@@ -83,3 +83,16 @@ 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_web_host. This host takes
|
88
|
+
# deploy locks, runs health checks during the deploy, and follow logs, etc.
|
89
|
+
# This role should have traefik enabled.
|
90
|
+
#
|
91
|
+
# Caution: there's no support for role renaming yet, so be careful to cleanup
|
92
|
+
# the previous role on the deployed hosts.
|
93
|
+
# primary_web_role: web
|
94
|
+
|
95
|
+
# Controls if we abort when see a role with no hosts. Disabling this may be
|
96
|
+
# useful for more complex deploy configurations.
|
97
|
+
#
|
98
|
+
# 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
@@ -28,11 +28,11 @@ class Kamal::Commander
|
|
28
28
|
end
|
29
29
|
|
30
30
|
def specific_roles=(role_names)
|
31
|
-
@specific_roles = config.roles
|
31
|
+
@specific_roles = Kamal::Utils.filter_specific_items(role_names, config.roles) if role_names.present?
|
32
32
|
end
|
33
33
|
|
34
34
|
def specific_hosts=(hosts)
|
35
|
-
@specific_hosts = config.all_hosts
|
35
|
+
@specific_hosts = Kamal::Utils.filter_specific_items(hosts, config.all_hosts) if hosts.present?
|
36
36
|
end
|
37
37
|
|
38
38
|
def primary_host
|
data/lib/kamal/commands/app.rb
CHANGED
@@ -18,6 +18,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|
18
18
|
"--name", container_name,
|
19
19
|
*(["--hostname", hostname] if hostname),
|
20
20
|
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
21
|
+
"-e", "KAMAL_VERSION=\"#{config.version}\"",
|
21
22
|
*role_config.env_args,
|
22
23
|
*role_config.health_check_args,
|
23
24
|
*config.logging_args,
|
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
|
|
@@ -16,6 +16,6 @@ class Kamal::Commands::Docker < Kamal::Commands::Base
|
|
16
16
|
|
17
17
|
# Do we have superuser access to install Docker and start system services?
|
18
18
|
def superuser?
|
19
|
-
[ '[ "${EUID:-$(id -u)}" -eq 0 ]' ]
|
19
|
+
[ '[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null' ]
|
20
20
|
end
|
21
21
|
end
|
@@ -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
|
@@ -185,6 +185,7 @@ class Kamal::Configuration::Role
|
|
185
185
|
"traefik.http.services.#{traefik_service}.loadbalancer.server.scheme" => "http",
|
186
186
|
|
187
187
|
"traefik.http.routers.#{traefik_service}.rule" => "PathPrefix(`/`)",
|
188
|
+
"traefik.http.routers.#{traefik_service}.priority" => "2",
|
188
189
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.attempts" => "5",
|
189
190
|
"traefik.http.middlewares.#{traefik_service}-retry.retry.initialinterval" => "500ms",
|
190
191
|
"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
|
@@ -90,13 +92,20 @@ class Kamal::Configuration
|
|
90
92
|
end
|
91
93
|
|
92
94
|
def primary_web_host
|
93
|
-
role(
|
95
|
+
role(primary_web_role)&.primary_host
|
94
96
|
end
|
95
97
|
|
96
|
-
def
|
97
|
-
roles.select(&:running_traefik?)
|
98
|
+
def traefik_roles
|
99
|
+
roles.select(&:running_traefik?)
|
100
|
+
end
|
101
|
+
|
102
|
+
def traefik_role_names
|
103
|
+
traefik_roles.flat_map(&:name)
|
98
104
|
end
|
99
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,16 +208,17 @@ class Kamal::Configuration
|
|
199
208
|
raw_config.asset_path
|
200
209
|
end
|
201
210
|
|
211
|
+
def primary_web_role
|
212
|
+
raw_config.primary_web_role || "web"
|
213
|
+
end
|
202
214
|
|
203
|
-
def
|
204
|
-
|
215
|
+
def allow_empty_roles?
|
216
|
+
raw_config.allow_empty_roles
|
205
217
|
end
|
206
218
|
|
207
|
-
# Will raise KeyError if any secret ENVs are missing
|
208
|
-
def ensure_env_available
|
209
|
-
roles.collect(&:env_file).each(&:to_s)
|
210
219
|
|
211
|
-
|
220
|
+
def valid?
|
221
|
+
ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version
|
212
222
|
end
|
213
223
|
|
214
224
|
def to_h
|
@@ -254,9 +264,23 @@ class Kamal::Configuration
|
|
254
264
|
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
255
265
|
end
|
256
266
|
|
257
|
-
|
258
|
-
|
259
|
-
|
267
|
+
unless role_names.include?(primary_web_role)
|
268
|
+
raise ArgumentError, "The primary_web_role #{primary_web_role} isn't defined"
|
269
|
+
end
|
270
|
+
|
271
|
+
unless traefik_role_names.include?(primary_web_role)
|
272
|
+
raise ArgumentError, "Role #{primary_web_role} needs to have traefik enabled"
|
273
|
+
end
|
274
|
+
|
275
|
+
if role(primary_web_role).hosts.empty?
|
276
|
+
raise ArgumentError, "No servers specified for the #{primary_web_role} primary_web_role"
|
277
|
+
end
|
278
|
+
|
279
|
+
unless allow_empty_roles?
|
280
|
+
roles.each do |role|
|
281
|
+
if role.hosts.empty?
|
282
|
+
raise ArgumentError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
|
283
|
+
end
|
260
284
|
end
|
261
285
|
end
|
262
286
|
|
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.2.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
|
+
date: 2023-11-15 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.21
|
274
276
|
signing_key:
|
275
277
|
specification_version: 4
|
276
278
|
summary: Deploy web apps in containers to servers running Docker with zero downtime.
|