kamal 0.16.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/kamal/cli/app.rb +44 -13
  4. data/lib/kamal/cli/base.rb +15 -2
  5. data/lib/kamal/cli/build.rb +18 -1
  6. data/lib/kamal/cli/env.rb +56 -0
  7. data/lib/kamal/cli/healthcheck/poller.rb +64 -0
  8. data/lib/kamal/cli/healthcheck.rb +2 -2
  9. data/lib/kamal/cli/lock.rb +12 -3
  10. data/lib/kamal/cli/main.rb +18 -4
  11. data/lib/kamal/cli/prune.rb +3 -2
  12. data/lib/kamal/cli/server.rb +2 -0
  13. data/lib/kamal/cli/templates/deploy.yml +12 -1
  14. data/lib/kamal/commander.rb +21 -8
  15. data/lib/kamal/commands/accessory.rb +8 -8
  16. data/lib/kamal/commands/app/assets.rb +51 -0
  17. data/lib/kamal/commands/app/containers.rb +23 -0
  18. data/lib/kamal/commands/app/cord.rb +22 -0
  19. data/lib/kamal/commands/app/execution.rb +27 -0
  20. data/lib/kamal/commands/app/images.rb +13 -0
  21. data/lib/kamal/commands/app/logging.rb +18 -0
  22. data/lib/kamal/commands/app.rb +18 -91
  23. data/lib/kamal/commands/auditor.rb +3 -1
  24. data/lib/kamal/commands/base.rb +12 -0
  25. data/lib/kamal/commands/builder/base.rb +6 -0
  26. data/lib/kamal/commands/builder.rb +1 -1
  27. data/lib/kamal/commands/docker.rb +1 -1
  28. data/lib/kamal/commands/healthcheck.rb +15 -12
  29. data/lib/kamal/commands/lock.rb +2 -2
  30. data/lib/kamal/commands/prune.rb +11 -3
  31. data/lib/kamal/commands/server.rb +5 -0
  32. data/lib/kamal/commands/traefik.rb +21 -7
  33. data/lib/kamal/configuration/accessory.rb +14 -2
  34. data/lib/kamal/configuration/role.rb +112 -19
  35. data/lib/kamal/configuration/ssh.rb +1 -1
  36. data/lib/kamal/configuration/volume.rb +22 -0
  37. data/lib/kamal/configuration.rb +73 -44
  38. data/lib/kamal/env_file.rb +41 -0
  39. data/lib/kamal/git.rb +19 -0
  40. data/lib/kamal/utils/sensitive.rb +1 -0
  41. data/lib/kamal/utils.rb +0 -39
  42. data/lib/kamal/version.rb +1 -1
  43. metadata +15 -4
  44. data/lib/kamal/utils/healthcheck_poller.rb +0 -39
@@ -1,5 +1,6 @@
1
1
  class Kamal::Configuration::Role
2
- delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
2
+ CORD_FILE = "cord"
3
+ delegate :argumentize, :optionize, to: Kamal::Utils
3
4
 
4
5
  attr_accessor :name
5
6
 
@@ -15,6 +16,18 @@ class Kamal::Configuration::Role
15
16
  @hosts ||= extract_hosts_from_config
16
17
  end
17
18
 
19
+ def cmd
20
+ specializations["cmd"]
21
+ end
22
+
23
+ def option_args
24
+ if args = specializations["options"]
25
+ optionize args
26
+ else
27
+ []
28
+ end
29
+ end
30
+
18
31
  def labels
19
32
  default_labels.merge(traefik_labels).merge(custom_labels)
20
33
  end
@@ -23,6 +36,7 @@ class Kamal::Configuration::Role
23
36
  argumentize "--label", labels
24
37
  end
25
38
 
39
+
26
40
  def env
27
41
  if config.env && config.env["secret"]
28
42
  merged_env_with_secrets
@@ -31,46 +45,117 @@ class Kamal::Configuration::Role
31
45
  end
32
46
  end
33
47
 
48
+ def env_file
49
+ Kamal::EnvFile.new(env)
50
+ end
51
+
52
+ def host_env_directory
53
+ File.join config.host_env_directory, "roles"
54
+ end
55
+
56
+ def host_env_file_path
57
+ File.join host_env_directory, "#{[config.service, name, config.destination].compact.join("-")}.env"
58
+ end
59
+
34
60
  def env_args
35
- argumentize_env_with_secrets env
61
+ argumentize "--env-file", host_env_file_path
62
+ end
63
+
64
+ def asset_volume_args
65
+ asset_volume&.docker_args
36
66
  end
37
67
 
38
- def health_check_args
68
+
69
+ def health_check_args(cord: true)
39
70
  if health_check_cmd.present?
40
- optionize({ "health-cmd" => health_check_cmd, "health-interval" => health_check_interval })
71
+ if cord && uses_cord?
72
+ optionize({ "health-cmd" => health_check_cmd_with_cord, "health-interval" => health_check_interval })
73
+ .concat(cord_volume.docker_args)
74
+ else
75
+ optionize({ "health-cmd" => health_check_cmd, "health-interval" => health_check_interval })
76
+ end
41
77
  else
42
78
  []
43
79
  end
44
80
  end
45
81
 
46
82
  def health_check_cmd
47
- options = specializations["healthcheck"] || {}
48
- options = config.healthcheck.merge(options) if running_traefik?
83
+ health_check_options["cmd"] || http_health_check(port: health_check_options["port"], path: health_check_options["path"])
84
+ end
49
85
 
50
- options["cmd"] || http_health_check(port: options["port"], path: options["path"])
86
+ def health_check_cmd_with_cord
87
+ "(#{health_check_cmd}) && (stat #{cord_container_file} > /dev/null || exit 1)"
51
88
  end
52
89
 
53
90
  def health_check_interval
54
- options = specializations["healthcheck"] || {}
55
- options = config.healthcheck.merge(options) if running_traefik?
91
+ health_check_options["interval"] || "1s"
92
+ end
93
+
56
94
 
57
- options["interval"] || "1s"
95
+ def running_traefik?
96
+ name.web? || specializations["traefik"]
58
97
  end
59
98
 
60
- def cmd
61
- specializations["cmd"]
99
+
100
+ def uses_cord?
101
+ running_traefik? && cord_volume && health_check_cmd.present?
62
102
  end
63
103
 
64
- def option_args
65
- if args = specializations["options"]
66
- optionize args
67
- else
68
- []
104
+ def cord_host_directory
105
+ File.join config.run_directory_as_docker_volume, "cords", [container_prefix, config.run_id].join("-")
106
+ end
107
+
108
+ def cord_volume
109
+ if (cord = health_check_options["cord"])
110
+ @cord_volume ||= Kamal::Configuration::Volume.new \
111
+ host_path: File.join(config.run_directory, "cords", [container_prefix, config.run_id].join("-")),
112
+ container_path: cord
69
113
  end
70
114
  end
71
115
 
72
- def running_traefik?
73
- name.web? || specializations["traefik"]
116
+ def cord_host_file
117
+ File.join cord_volume.host_path, CORD_FILE
118
+ end
119
+
120
+ def cord_container_directory
121
+ health_check_options.fetch("cord", nil)
122
+ end
123
+
124
+ def cord_container_file
125
+ File.join cord_volume.container_path, CORD_FILE
126
+ end
127
+
128
+
129
+ def container_name(version = nil)
130
+ [ container_prefix, version || config.version ].compact.join("-")
131
+ end
132
+
133
+ def container_prefix
134
+ [ config.service, name, config.destination ].compact.join("-")
135
+ end
136
+
137
+
138
+ def asset_path
139
+ specializations["asset_path"] || config.asset_path
140
+ end
141
+
142
+ def assets?
143
+ asset_path.present? && running_traefik?
144
+ end
145
+
146
+ def asset_volume(version = nil)
147
+ if assets?
148
+ Kamal::Configuration::Volume.new \
149
+ host_path: asset_volume_path(version), container_path: asset_path
150
+ end
151
+ end
152
+
153
+ def asset_extracted_path(version = nil)
154
+ File.join config.run_directory, "assets", "extracted", container_name(version)
155
+ end
156
+
157
+ def asset_volume_path(version = nil)
158
+ File.join config.run_directory, "assets", "volumes", container_name(version)
74
159
  end
75
160
 
76
161
  private
@@ -152,4 +237,12 @@ class Kamal::Configuration::Role
152
237
  def http_health_check(port:, path:)
153
238
  "curl -f #{URI.join("http://localhost:#{port}", path)} || exit 1" if path.present? || port.present?
154
239
  end
240
+
241
+ def health_check_options
242
+ @health_check_options ||= begin
243
+ options = specializations["healthcheck"] || {}
244
+ options = config.healthcheck.merge(options) if running_traefik?
245
+ options
246
+ end
247
+ end
155
248
  end
@@ -18,7 +18,7 @@ class Kamal::Configuration::Ssh
18
18
  end
19
19
 
20
20
  def options
21
- { user: user, proxy: proxy, auth_methods: [ "publickey" ], logger: logger, keepalive: true, keepalive_interval: 30 }.compact
21
+ { user: user, proxy: proxy, logger: logger, keepalive: true, keepalive_interval: 30 }.compact
22
22
  end
23
23
 
24
24
  def to_h
@@ -0,0 +1,22 @@
1
+ class Kamal::Configuration::Volume
2
+ attr_reader :host_path, :container_path
3
+ delegate :argumentize, to: Kamal::Utils
4
+
5
+ def initialize(host_path:, container_path:)
6
+ @host_path = host_path
7
+ @container_path = container_path
8
+ end
9
+
10
+ def docker_args
11
+ argumentize "--volume", "#{host_path_for_docker_volume}:#{container_path}"
12
+ end
13
+
14
+ private
15
+ def host_path_for_docker_volume
16
+ if Pathname.new(host_path).absolute?
17
+ host_path
18
+ else
19
+ File.join "$(pwd)", host_path
20
+ end
21
+ end
22
+ end
@@ -7,10 +7,9 @@ require "net/ssh/proxy/jump"
7
7
 
8
8
  class Kamal::Configuration
9
9
  delegate :service, :image, :servers, :env, :labels, :registry, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
10
- delegate :argumentize, :argumentize_env_with_secrets, :optionize, to: Kamal::Utils
10
+ delegate :argumentize, :optionize, to: Kamal::Utils
11
11
 
12
- attr_accessor :destination
13
- attr_accessor :raw_config
12
+ attr_reader :destination, :raw_config
14
13
 
15
14
  class << self
16
15
  def create_from(config_file:, destination: nil, version: nil)
@@ -54,7 +53,18 @@ class Kamal::Configuration
54
53
  end
55
54
 
56
55
  def abbreviated_version
57
- Kamal::Utils.abbreviate_version(version)
56
+ if version
57
+ # Don't abbreviate <sha>_uncommitted_<etc>
58
+ if version.include?("_")
59
+ version
60
+ else
61
+ version[0...7]
62
+ end
63
+ end
64
+ end
65
+
66
+ def minimum_version
67
+ raw_config.minimum_version
58
68
  end
59
69
 
60
70
 
@@ -87,10 +97,6 @@ class Kamal::Configuration
87
97
  roles.select(&:running_traefik?).flat_map(&:hosts).uniq
88
98
  end
89
99
 
90
- def boot
91
- Kamal::Configuration::Boot.new(config: self)
92
- end
93
-
94
100
 
95
101
  def repository
96
102
  [ raw_config.registry["server"], image ].compact.join("/")
@@ -108,15 +114,11 @@ class Kamal::Configuration
108
114
  "#{service}-#{version}"
109
115
  end
110
116
 
111
-
112
- def env_args
113
- if raw_config.env.present?
114
- argumentize_env_with_secrets(raw_config.env)
115
- else
116
- []
117
- end
117
+ def require_destination?
118
+ raw_config.require_destination
118
119
  end
119
120
 
121
+
120
122
  def volume_args
121
123
  if raw_config.volumes.present?
122
124
  argumentize "--volume", raw_config.volumes
@@ -135,6 +137,18 @@ class Kamal::Configuration
135
137
  end
136
138
 
137
139
 
140
+ def boot
141
+ Kamal::Configuration::Boot.new(config: self)
142
+ end
143
+
144
+ def builder
145
+ Kamal::Configuration::Builder.new(config: self)
146
+ end
147
+
148
+ def traefik
149
+ raw_config.traefik || {}
150
+ end
151
+
138
152
  def ssh
139
153
  Kamal::Configuration::Ssh.new(config: self)
140
154
  end
@@ -145,21 +159,50 @@ class Kamal::Configuration
145
159
 
146
160
 
147
161
  def healthcheck
148
- { "path" => "/up", "port" => 3000, "max_attempts" => 7 }.merge(raw_config.healthcheck || {})
162
+ { "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "cord" => "/tmp/kamal-cord", "log_lines" => 50 }.merge(raw_config.healthcheck || {})
163
+ end
164
+
165
+ def healthcheck_service
166
+ [ "healthcheck", service, destination ].compact.join("-")
149
167
  end
150
168
 
151
169
  def readiness_delay
152
170
  raw_config.readiness_delay || 7
153
171
  end
154
172
 
155
- def minimum_version
156
- raw_config.minimum_version
173
+ def run_id
174
+ @run_id ||= SecureRandom.hex(16)
157
175
  end
158
176
 
159
- def valid?
160
- ensure_required_keys_present && ensure_valid_kamal_version
177
+
178
+ def run_directory
179
+ raw_config.run_directory || ".kamal"
180
+ end
181
+
182
+ def run_directory_as_docker_volume
183
+ if Pathname.new(run_directory).absolute?
184
+ run_directory
185
+ else
186
+ File.join "$(pwd)", run_directory
187
+ end
161
188
  end
162
189
 
190
+ def hooks_path
191
+ raw_config.hooks_path || ".kamal/hooks"
192
+ end
193
+
194
+ def host_env_directory
195
+ "#{run_directory}/env"
196
+ end
197
+
198
+ def asset_path
199
+ raw_config.asset_path
200
+ end
201
+
202
+
203
+ def valid?
204
+ ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version
205
+ end
163
206
 
164
207
  def to_h
165
208
  {
@@ -170,7 +213,6 @@ class Kamal::Configuration
170
213
  repository: repository,
171
214
  absolute_image: absolute_image,
172
215
  service_with_version: service_with_version,
173
- env_args: env_args,
174
216
  volume_args: volume_args,
175
217
  ssh_options: ssh.to_h,
176
218
  sshkit: sshkit.to_h,
@@ -181,28 +223,17 @@ class Kamal::Configuration
181
223
  }.compact
182
224
  end
183
225
 
184
- def traefik
185
- raw_config.traefik || {}
186
- end
187
-
188
- def hooks_path
189
- raw_config.hooks_path || ".kamal/hooks"
190
- end
191
-
192
- def builder
193
- Kamal::Configuration::Builder.new(config: self)
194
- end
195
-
196
- # Will raise KeyError if any secret ENVs are missing
197
- def ensure_env_available
198
- env_args
199
- roles.each(&:env_args)
200
-
201
- true
202
- end
203
226
 
204
227
  private
205
228
  # Will raise ArgumentError if any required config keys are missing
229
+ def ensure_destination_if_required
230
+ if require_destination? && destination.nil?
231
+ raise ArgumentError, "You must specify a destination"
232
+ end
233
+
234
+ true
235
+ end
236
+
206
237
  def ensure_required_keys_present
207
238
  %i[ service image registry servers ].each do |key|
208
239
  raise ArgumentError, "Missing required configuration for #{key}" unless raw_config[key].present?
@@ -240,10 +271,8 @@ class Kamal::Configuration
240
271
 
241
272
  def git_version
242
273
  @git_version ||=
243
- if system("git rev-parse")
244
- uncommitted_suffix = Kamal::Utils.uncommitted_changes.present? ? "_uncommitted_#{SecureRandom.hex(8)}" : ""
245
-
246
- "#{`git rev-parse HEAD`.strip}#{uncommitted_suffix}"
274
+ if Kamal::Git.used?
275
+ [ Kamal::Git.revision, Kamal::Git.uncommitted_changes.present? ? "_uncommitted_#{SecureRandom.hex(8)}" : "" ].join
247
276
  else
248
277
  raise "Can't use commit hash as version, no git repository found in #{Dir.pwd}"
249
278
  end
@@ -0,0 +1,41 @@
1
+ # Encode an env hash as a string where secret values have been looked up and all values escaped for Docker.
2
+ class Kamal::EnvFile
3
+ def initialize(env)
4
+ @env = env
5
+ end
6
+
7
+ def to_s
8
+ env_file = StringIO.new.tap do |contents|
9
+ if (secrets = @env["secret"]).present?
10
+ @env.fetch("secret", @env)&.each do |key|
11
+ contents << docker_env_file_line(key, ENV.fetch(key))
12
+ end
13
+
14
+ @env["clear"]&.each do |key, value|
15
+ contents << docker_env_file_line(key, value)
16
+ end
17
+ else
18
+ @env.fetch("clear", @env)&.each do |key, value|
19
+ contents << docker_env_file_line(key, value)
20
+ end
21
+ end
22
+ end.string
23
+
24
+ # Ensure the file has some contents to avoid the SSHKIT empty file warning
25
+ env_file.presence || "\n"
26
+ end
27
+
28
+ alias to_str to_s
29
+
30
+ private
31
+ def docker_env_file_line(key, value)
32
+ "#{key.to_s}=#{escape_docker_env_file_value(value)}\n"
33
+ end
34
+
35
+ # Escape a value to make it safe to dump in a docker file.
36
+ def escape_docker_env_file_value(value)
37
+ # Doublequotes are treated literally in docker env files
38
+ # so remove leading and trailing ones and unescape any others
39
+ value.to_s.dump[1..-2].gsub(/\\"/, "\"")
40
+ end
41
+ end
data/lib/kamal/git.rb ADDED
@@ -0,0 +1,19 @@
1
+ module Kamal::Git
2
+ extend self
3
+
4
+ def used?
5
+ system("git rev-parse")
6
+ end
7
+
8
+ def user_name
9
+ `git config user.name`.strip
10
+ end
11
+
12
+ def revision
13
+ `git rev-parse HEAD`.strip
14
+ end
15
+
16
+ def uncommitted_changes
17
+ `git status --porcelain`.strip
18
+ end
19
+ end
@@ -1,4 +1,5 @@
1
1
  require "active_support/core_ext/module/delegation"
2
+ require "sshkit"
2
3
 
3
4
  class Kamal::Utils::Sensitive
4
5
  # So SSHKit knows to redact these values.
data/lib/kamal/utils.rb CHANGED
@@ -16,16 +16,6 @@ module Kamal::Utils
16
16
  end
17
17
  end
18
18
 
19
- # Return a list of shell arguments using the same named argument against the passed attributes,
20
- # but redacts and expands secrets.
21
- def argumentize_env_with_secrets(env)
22
- if (secrets = env["secret"]).present?
23
- argumentize("-e", secrets.to_h { |key| [ key, ENV.fetch(key) ] }, sensitive: true) + argumentize("-e", env["clear"])
24
- else
25
- argumentize "-e", env.fetch("clear", env)
26
- end
27
- end
28
-
29
19
  # Returns a list of shell-dashed option arguments. If the value is true, it's treated like a value-less option.
30
20
  def optionize(args, with: nil)
31
21
  options = if with
@@ -62,39 +52,10 @@ module Kamal::Utils
62
52
  end
63
53
  end
64
54
 
65
- def unredacted(value)
66
- case
67
- when value.respond_to?(:unredacted)
68
- value.unredacted
69
- when value.respond_to?(:transform_values)
70
- value.transform_values { |value| unredacted value }
71
- when value.respond_to?(:map)
72
- value.map { |element| unredacted element }
73
- else
74
- value
75
- end
76
- end
77
-
78
55
  # Escape a value to make it safe for shell use.
79
56
  def escape_shell_value(value)
80
57
  value.to_s.dump
81
58
  .gsub(/`/, '\\\\`')
82
59
  .gsub(DOLLAR_SIGN_WITHOUT_SHELL_EXPANSION_REGEX, '\$')
83
60
  end
84
-
85
- # Abbreviate a git revhash for concise display
86
- def abbreviate_version(version)
87
- if version
88
- # Don't abbreviate <sha>_uncommitted_<etc>
89
- if version.include?("_")
90
- version
91
- else
92
- version[0...7]
93
- end
94
- end
95
- end
96
-
97
- def uncommitted_changes
98
- `git status --porcelain`.strip
99
- end
100
61
  end
data/lib/kamal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "0.16.1"
2
+ VERSION = "1.1.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: 0.16.1
4
+ version: 1.1.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-08-24 00:00:00.000000000 Z
11
+ date: 2023-11-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -194,7 +194,9 @@ files:
194
194
  - lib/kamal/cli/app.rb
195
195
  - lib/kamal/cli/base.rb
196
196
  - lib/kamal/cli/build.rb
197
+ - lib/kamal/cli/env.rb
197
198
  - lib/kamal/cli/healthcheck.rb
199
+ - lib/kamal/cli/healthcheck/poller.rb
198
200
  - lib/kamal/cli/lock.rb
199
201
  - lib/kamal/cli/main.rb
200
202
  - lib/kamal/cli/prune.rb
@@ -211,6 +213,12 @@ files:
211
213
  - lib/kamal/commands.rb
212
214
  - lib/kamal/commands/accessory.rb
213
215
  - lib/kamal/commands/app.rb
216
+ - lib/kamal/commands/app/assets.rb
217
+ - lib/kamal/commands/app/containers.rb
218
+ - lib/kamal/commands/app/cord.rb
219
+ - lib/kamal/commands/app/execution.rb
220
+ - lib/kamal/commands/app/images.rb
221
+ - lib/kamal/commands/app/logging.rb
214
222
  - lib/kamal/commands/auditor.rb
215
223
  - lib/kamal/commands/base.rb
216
224
  - lib/kamal/commands/builder.rb
@@ -226,6 +234,7 @@ files:
226
234
  - lib/kamal/commands/lock.rb
227
235
  - lib/kamal/commands/prune.rb
228
236
  - lib/kamal/commands/registry.rb
237
+ - lib/kamal/commands/server.rb
229
238
  - lib/kamal/commands/traefik.rb
230
239
  - lib/kamal/configuration.rb
231
240
  - lib/kamal/configuration/accessory.rb
@@ -234,10 +243,12 @@ files:
234
243
  - lib/kamal/configuration/role.rb
235
244
  - lib/kamal/configuration/ssh.rb
236
245
  - lib/kamal/configuration/sshkit.rb
246
+ - lib/kamal/configuration/volume.rb
247
+ - lib/kamal/env_file.rb
248
+ - lib/kamal/git.rb
237
249
  - lib/kamal/sshkit_with_ext.rb
238
250
  - lib/kamal/tags.rb
239
251
  - lib/kamal/utils.rb
240
- - lib/kamal/utils/healthcheck_poller.rb
241
252
  - lib/kamal/utils/sensitive.rb
242
253
  - lib/kamal/version.rb
243
254
  homepage: https://github.com/basecamp/kamal
@@ -259,7 +270,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
259
270
  - !ruby/object:Gem::Version
260
271
  version: '0'
261
272
  requirements: []
262
- rubygems_version: 3.4.18
273
+ rubygems_version: 3.4.21
263
274
  signing_key:
264
275
  specification_version: 4
265
276
  summary: Deploy web apps in containers to servers running Docker with zero downtime.
@@ -1,39 +0,0 @@
1
- class Kamal::Utils::HealthcheckPoller
2
- TRAEFIK_HEALTHY_DELAY = 2
3
-
4
- class HealthcheckError < StandardError; end
5
-
6
- class << self
7
- def wait_for_healthy(pause_after_ready: false, &block)
8
- attempt = 1
9
- max_attempts = KAMAL.config.healthcheck["max_attempts"]
10
-
11
- begin
12
- case status = block.call
13
- when "healthy"
14
- sleep TRAEFIK_HEALTHY_DELAY if pause_after_ready
15
- when "running" # No health check configured
16
- sleep KAMAL.config.readiness_delay if pause_after_ready
17
- else
18
- raise HealthcheckError, "container not ready (#{status})"
19
- end
20
- rescue HealthcheckError => e
21
- if attempt <= max_attempts
22
- info "#{e.message}, retrying in #{attempt}s (attempt #{attempt}/#{max_attempts})..."
23
- sleep attempt
24
- attempt += 1
25
- retry
26
- else
27
- raise
28
- end
29
- end
30
-
31
- info "Container is healthy!"
32
- end
33
-
34
- private
35
- def info(message)
36
- SSHKit.config.output.info(message)
37
- end
38
- end
39
- end