kamal 1.1.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2470f58e93ad7f181c9a422a0eef08c741190cb897c962b83aeda9effd4862f5
4
- data.tar.gz: df8ddd4bf5dcf6687fab747bcfb11689dbe58b846ab6e392e99694ce8bfdff88
3
+ metadata.gz: efc8817e4bba2417ad480b7297b323d11bd88ec87993fb4c8a1bb09ffbb3f4a4
4
+ data.tar.gz: 6c1269582ae3ccf90e9b4532b2fecbdd0723493413f82036038a121707f76ce3
5
5
  SHA512:
6
- metadata.gz: 7cef4c1ea22ffe3a0f80dbd2985cb3f3941e70dfcb35509a4ec7210124e517b0b7336ddd571370c32868d9d99442b9cc1186eb6a0cdefaa5db1b0475f117faae
7
- data.tar.gz: 1f6d43624775bb9f1320692c3b4e5d061528b92763886f963cf184978e2405e478c5c6a55347316c69416f512bb5f70c970999d56e2e5c6937b6b83613338106
6
+ metadata.gz: d9862df894f9e105bfe38a88ac93289e9fa89b51d69200cea6f5bd9f4483c74d31e2db39aa2d601aeb620d5f1beac5ca05efe94803e42f79cf3ba61e380b3f6d
7
+ data.tar.gz: 5e03468f451046a73425599db6dba15565d960f244d622bd2248c00e2d3f89db4f96cffed4dd6719d3025211ff0cb725a50020d81b8f09e6a64a6b41a2836b63
@@ -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)"
@@ -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
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Rebooted Traefik on $KAMAL_HOSTS"
@@ -0,0 +1,3 @@
1
+ #!/bin/sh
2
+
3
+ echo "Rebooting Traefik on $KAMAL_HOSTS..."
@@ -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
- on(KAMAL.traefik_hosts, in: options[:rolling] ? :sequence : :parallel) do
17
- execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
18
- execute *KAMAL.registry.login
19
- execute *KAMAL.traefik.stop
20
- execute *KAMAL.traefik.remove_container
21
- execute *KAMAL.traefik.run
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
@@ -28,11 +28,11 @@ class Kamal::Commander
28
28
  end
29
29
 
30
30
  def specific_roles=(role_names)
31
- @specific_roles = config.roles.select { |r| role_names.include?(r.name) } if role_names.present?
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 & hosts if hosts.present?
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
@@ -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
- web = config.role(:web)
4
+ web = config.role(config.primary_web_role)
5
5
 
6
6
  docker :run,
7
7
  "--detach",
@@ -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
@@ -25,7 +25,9 @@ class Kamal::Configuration
25
25
 
26
26
  def load_config_file(file)
27
27
  if file.exist?
28
- YAML.load(ERB.new(IO.read(file)).result).symbolize_keys
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(:web).primary_host
95
+ role(primary_web_role)&.primary_host
94
96
  end
95
97
 
96
- def traefik_hosts
97
- roles.select(&:running_traefik?).flat_map(&:hosts).uniq
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,6 +208,14 @@ 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
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
@@ -247,9 +264,23 @@ 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
- roles.each do |role|
251
- if role.hosts.empty?
252
- raise ArgumentError, "No servers specified for the #{role.name} role"
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
253
284
  end
254
285
  end
255
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
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.0"
3
3
  end
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.1.0
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-03 00:00:00.000000000 Z
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