kamal 0.16.1 → 1.1.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.
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