kamal 1.5.2 → 1.7.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/lib/kamal/cli/accessory.rb +30 -24
  3. data/lib/kamal/cli/app/boot.rb +70 -18
  4. data/lib/kamal/cli/app/prepare_assets.rb +1 -1
  5. data/lib/kamal/cli/app.rb +60 -47
  6. data/lib/kamal/cli/base.rb +26 -28
  7. data/lib/kamal/cli/build/clone.rb +61 -0
  8. data/lib/kamal/cli/build.rb +64 -53
  9. data/lib/kamal/cli/env.rb +5 -5
  10. data/lib/kamal/cli/healthcheck/barrier.rb +31 -0
  11. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  12. data/lib/kamal/cli/healthcheck/poller.rb +6 -7
  13. data/lib/kamal/cli/main.rb +49 -44
  14. data/lib/kamal/cli/prune.rb +3 -3
  15. data/lib/kamal/cli/registry.rb +9 -10
  16. data/lib/kamal/cli/server.rb +39 -15
  17. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +1 -1
  18. data/lib/kamal/cli/traefik.rb +13 -11
  19. data/lib/kamal/cli.rb +1 -1
  20. data/lib/kamal/commander.rb +6 -6
  21. data/lib/kamal/commands/accessory.rb +4 -4
  22. data/lib/kamal/commands/app/containers.rb +8 -0
  23. data/lib/kamal/commands/app/execution.rb +3 -3
  24. data/lib/kamal/commands/app/logging.rb +5 -5
  25. data/lib/kamal/commands/app.rb +6 -5
  26. data/lib/kamal/commands/base.rb +2 -3
  27. data/lib/kamal/commands/builder/base.rb +19 -12
  28. data/lib/kamal/commands/builder/clone.rb +28 -0
  29. data/lib/kamal/commands/builder/multiarch/remote.rb +10 -0
  30. data/lib/kamal/commands/builder/multiarch.rb +13 -9
  31. data/lib/kamal/commands/builder/native/cached.rb +14 -6
  32. data/lib/kamal/commands/builder/native/remote.rb +17 -9
  33. data/lib/kamal/commands/builder/native.rb +6 -7
  34. data/lib/kamal/commands/builder.rb +19 -11
  35. data/lib/kamal/commands/registry.rb +4 -13
  36. data/lib/kamal/commands/traefik.rb +8 -47
  37. data/lib/kamal/configuration/accessory.rb +30 -41
  38. data/lib/kamal/configuration/boot.rb +9 -4
  39. data/lib/kamal/configuration/builder.rb +61 -30
  40. data/lib/kamal/configuration/docs/accessory.yml +90 -0
  41. data/lib/kamal/configuration/docs/boot.yml +19 -0
  42. data/lib/kamal/configuration/docs/builder.yml +107 -0
  43. data/lib/kamal/configuration/docs/configuration.yml +157 -0
  44. data/lib/kamal/configuration/docs/env.yml +72 -0
  45. data/lib/kamal/configuration/docs/healthcheck.yml +59 -0
  46. data/lib/kamal/configuration/docs/logging.yml +21 -0
  47. data/lib/kamal/configuration/docs/registry.yml +49 -0
  48. data/lib/kamal/configuration/docs/role.yml +52 -0
  49. data/lib/kamal/configuration/docs/servers.yml +27 -0
  50. data/lib/kamal/configuration/docs/ssh.yml +46 -0
  51. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  52. data/lib/kamal/configuration/docs/traefik.yml +62 -0
  53. data/lib/kamal/configuration/env/tag.rb +12 -0
  54. data/lib/kamal/configuration/env.rb +10 -14
  55. data/lib/kamal/configuration/healthcheck.rb +63 -0
  56. data/lib/kamal/configuration/logging.rb +33 -0
  57. data/lib/kamal/configuration/registry.rb +31 -0
  58. data/lib/kamal/configuration/role.rb +72 -61
  59. data/lib/kamal/configuration/servers.rb +18 -0
  60. data/lib/kamal/configuration/ssh.rb +11 -8
  61. data/lib/kamal/configuration/sshkit.rb +9 -7
  62. data/lib/kamal/configuration/traefik.rb +60 -0
  63. data/lib/kamal/configuration/validation.rb +27 -0
  64. data/lib/kamal/configuration/validator/accessory.rb +9 -0
  65. data/lib/kamal/configuration/validator/builder.rb +9 -0
  66. data/lib/kamal/configuration/validator/env.rb +54 -0
  67. data/lib/kamal/configuration/validator/registry.rb +25 -0
  68. data/lib/kamal/configuration/validator/role.rb +11 -0
  69. data/lib/kamal/configuration/validator/servers.rb +7 -0
  70. data/lib/kamal/configuration/validator.rb +140 -0
  71. data/lib/kamal/configuration.rb +50 -63
  72. data/lib/kamal/git.rb +4 -0
  73. data/lib/kamal/sshkit_with_ext.rb +36 -0
  74. data/lib/kamal/version.rb +1 -1
  75. data/lib/kamal.rb +2 -0
  76. metadata +64 -9
  77. data/lib/kamal/cli/healthcheck.rb +0 -21
  78. data/lib/kamal/commands/healthcheck.rb +0 -59
@@ -0,0 +1,140 @@
1
+ class Kamal::Configuration::Validator
2
+ attr_reader :config, :example, :context
3
+
4
+ def initialize(config, example:, context:)
5
+ @config = config
6
+ @example = example
7
+ @context = context
8
+ end
9
+
10
+ def validate!
11
+ validate_against_example! config, example
12
+ end
13
+
14
+ private
15
+ def validate_against_example!(validation_config, example)
16
+ validate_type! validation_config, Hash
17
+
18
+ if (unknown_keys = validation_config.keys - example.keys).any?
19
+ unknown_keys_error unknown_keys
20
+ end
21
+
22
+ validation_config.each do |key, value|
23
+ with_context(key) do
24
+ example_value = example[key]
25
+
26
+ if example_value == "..."
27
+ validate_type! value, *(Array if key == :servers), Hash
28
+ elsif key == "hosts"
29
+ validate_servers! value
30
+ elsif example_value.is_a?(Array)
31
+ validate_array_of! value, example_value.first.class
32
+ elsif example_value.is_a?(Hash)
33
+ case key.to_s
34
+ when "options"
35
+ validate_type! value, Hash
36
+ when "args", "labels"
37
+ validate_hash_of! value, example_value.first[1].class
38
+ else
39
+ validate_against_example! value, example_value
40
+ end
41
+ else
42
+ validate_type! value, example_value.class
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+
49
+ def valid_type?(value, type)
50
+ value.is_a?(type) ||
51
+ (type == String && stringish?(value)) ||
52
+ (boolean?(type) && boolean?(value.class))
53
+ end
54
+
55
+ def type_description(type)
56
+ if type == Integer || type == Array
57
+ "an #{type.name.downcase}"
58
+ elsif type == TrueClass || type == FalseClass
59
+ "a boolean"
60
+ else
61
+ "a #{type.name.downcase}"
62
+ end
63
+ end
64
+
65
+ def boolean?(type)
66
+ type == TrueClass || type == FalseClass
67
+ end
68
+
69
+ def stringish?(value)
70
+ value.is_a?(String) || value.is_a?(Symbol) || value.is_a?(Numeric) || value.is_a?(TrueClass) || value.is_a?(FalseClass)
71
+ end
72
+
73
+ def validate_array_of!(array, type)
74
+ validate_type! array, Array
75
+
76
+ array.each_with_index do |value, index|
77
+ with_context(index) do
78
+ validate_type! value, type
79
+ end
80
+ end
81
+ end
82
+
83
+ def validate_hash_of!(hash, type)
84
+ validate_type! hash, Hash
85
+
86
+ hash.each do |key, value|
87
+ with_context(key) do
88
+ validate_type! value, type
89
+ end
90
+ end
91
+ end
92
+
93
+ def validate_servers!(servers)
94
+ validate_type! servers, Array
95
+
96
+ servers.each_with_index do |server, index|
97
+ with_context(index) do
98
+ validate_type! server, String, Hash
99
+
100
+ if server.is_a?(Hash)
101
+ error "multiple hosts found" unless server.size == 1
102
+ host, tags = server.first
103
+
104
+ with_context(host) do
105
+ validate_type! tags, String, Array
106
+ validate_array_of! tags, String if tags.is_a?(Array)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+
113
+ def validate_type!(value, *types)
114
+ type_error(*types) unless types.any? { |type| valid_type?(value, type) }
115
+ end
116
+
117
+ def error(message)
118
+ raise Kamal::ConfigurationError, "#{error_context}#{message}"
119
+ end
120
+
121
+ def type_error(*expected_types)
122
+ error "should be #{expected_types.map { |type| type_description(type) }.join(" or ")}"
123
+ end
124
+
125
+ def unknown_keys_error(unknown_keys)
126
+ error "unknown #{"key".pluralize(unknown_keys.count)}: #{unknown_keys.join(", ")}"
127
+ end
128
+
129
+ def error_context
130
+ "#{context}: " if context.present?
131
+ end
132
+
133
+ def with_context(context)
134
+ old_context = @context
135
+ @context = [ @context, context ].select(&:present?).join("/")
136
+ yield
137
+ ensure
138
+ @context = old_context
139
+ end
140
+ end
@@ -1,15 +1,19 @@
1
1
  require "active_support/ordered_options"
2
2
  require "active_support/core_ext/string/inquiry"
3
3
  require "active_support/core_ext/module/delegation"
4
+ require "active_support/core_ext/hash/keys"
4
5
  require "pathname"
5
6
  require "erb"
6
7
  require "net/ssh/proxy/jump"
7
8
 
8
9
  class Kamal::Configuration
9
- delegate :service, :image, :servers, :labels, :registry, :stop_wait_time, :hooks_path, :logging, to: :raw_config, allow_nil: true
10
+ delegate :service, :image, :labels, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
10
11
  delegate :argumentize, :optionize, to: Kamal::Utils
11
12
 
12
13
  attr_reader :destination, :raw_config
14
+ attr_reader :accessories, :boot, :builder, :env, :healthcheck, :logging, :traefik, :servers, :ssh, :sshkit, :registry
15
+
16
+ include Validation
13
17
 
14
18
  class << self
15
19
  def create_from(config_file:, destination: nil, version: nil)
@@ -42,7 +46,29 @@ class Kamal::Configuration
42
46
  @raw_config = ActiveSupport::InheritableOptions.new(raw_config)
43
47
  @destination = destination
44
48
  @declared_version = version
45
- valid? if validate
49
+
50
+ validate! raw_config, example: validation_yml.symbolize_keys, context: ""
51
+
52
+ # Eager load config to validate it, these are first as they have dependencies later on
53
+ @servers = Servers.new(config: self)
54
+ @registry = Registry.new(config: self)
55
+
56
+ @accessories = @raw_config.accessories&.keys&.collect { |name| Accessory.new(name, config: self) } || []
57
+ @boot = Boot.new(config: self)
58
+ @builder = Builder.new(config: self)
59
+ @env = Env.new(config: @raw_config.env || {})
60
+
61
+ @healthcheck = Healthcheck.new(healthcheck_config: @raw_config.healthcheck)
62
+ @logging = Logging.new(logging_config: @raw_config.logging)
63
+ @traefik = Traefik.new(config: self)
64
+ @ssh = Ssh.new(config: self)
65
+ @sshkit = Sshkit.new(config: self)
66
+
67
+ ensure_destination_if_required
68
+ ensure_required_keys_present
69
+ ensure_valid_kamal_version
70
+ ensure_retain_containers_valid
71
+ ensure_valid_service_name
46
72
  end
47
73
 
48
74
 
@@ -71,17 +97,13 @@ class Kamal::Configuration
71
97
 
72
98
 
73
99
  def roles
74
- @roles ||= role_names.collect { |role_name| Role.new(role_name, config: self) }
100
+ servers.roles
75
101
  end
76
102
 
77
103
  def role(name)
78
104
  roles.detect { |r| r.name == name.to_s }
79
105
  end
80
106
 
81
- def accessories
82
- @accessories ||= raw_config.accessories&.keys&.collect { |name| Kamal::Configuration::Accessory.new(name, config: self) } || []
83
- end
84
-
85
107
  def accessory(name)
86
108
  accessories.detect { |a| a.name == name.to_s }
87
109
  end
@@ -120,7 +142,7 @@ class Kamal::Configuration
120
142
  end
121
143
 
122
144
  def repository
123
- [ raw_config.registry["server"], image ].compact.join("/")
145
+ [ registry.server, image ].compact.join("/")
124
146
  end
125
147
 
126
148
  def absolute_image
@@ -157,39 +179,9 @@ class Kamal::Configuration
157
179
  end
158
180
 
159
181
  def logging_args
160
- if logging.present?
161
- optionize({ "log-driver" => logging["driver"] }.compact) +
162
- argumentize("--log-opt", logging["options"])
163
- else
164
- argumentize("--log-opt", { "max-size" => "10m" })
165
- end
166
- end
167
-
168
-
169
- def boot
170
- Kamal::Configuration::Boot.new(config: self)
182
+ logging.args
171
183
  end
172
184
 
173
- def builder
174
- Kamal::Configuration::Builder.new(config: self)
175
- end
176
-
177
- def traefik
178
- raw_config.traefik || {}
179
- end
180
-
181
- def ssh
182
- Kamal::Configuration::Ssh.new(config: self)
183
- end
184
-
185
- def sshkit
186
- Kamal::Configuration::Sshkit.new(config: self)
187
- end
188
-
189
-
190
- def healthcheck
191
- { "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "cord" => "/tmp/kamal-cord", "log_lines" => 50 }.merge(raw_config.healthcheck || {})
192
- end
193
185
 
194
186
  def healthcheck_service
195
187
  [ "healthcheck", service, destination ].compact.join("-")
@@ -229,15 +221,19 @@ class Kamal::Configuration
229
221
  File.join(run_directory, "env")
230
222
  end
231
223
 
232
- def env
233
- raw_config.env || {}
224
+ def env_tags
225
+ @env_tags ||= if (tags = raw_config.env["tags"])
226
+ tags.collect { |name, config| Env::Tag.new(name, config: config) }
227
+ else
228
+ []
229
+ end
234
230
  end
235
231
 
236
-
237
- def valid?
238
- ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version && ensure_retain_containers_valid && ensure_valid_service_name
232
+ def env_tag(name)
233
+ env_tags.detect { |t| t.name == name.to_s }
239
234
  end
240
235
 
236
+
241
237
  def to_h
242
238
  {
243
239
  roles: role_names,
@@ -253,11 +249,10 @@ class Kamal::Configuration
253
249
  builder: builder.to_h,
254
250
  accessories: raw_config.accessories,
255
251
  logging: logging_args,
256
- healthcheck: healthcheck
252
+ healthcheck: healthcheck.to_h
257
253
  }.compact
258
254
  end
259
255
 
260
-
261
256
  private
262
257
  # Will raise ArgumentError if any required config keys are missing
263
258
  def ensure_destination_if_required
@@ -270,29 +265,21 @@ class Kamal::Configuration
270
265
 
271
266
  def ensure_required_keys_present
272
267
  %i[ service image registry servers ].each do |key|
273
- raise ArgumentError, "Missing required configuration for #{key}" unless raw_config[key].present?
274
- end
275
-
276
- if raw_config.registry["username"].blank?
277
- raise ArgumentError, "You must specify a username for the registry in config/deploy.yml"
278
- end
279
-
280
- if raw_config.registry["password"].blank?
281
- raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
268
+ raise Kamal::ConfigurationError, "Missing required configuration for #{key}" unless raw_config[key].present?
282
269
  end
283
270
 
284
- unless role_names.include?(primary_role_name)
285
- raise ArgumentError, "The primary_role #{primary_role_name} isn't defined"
271
+ unless role(primary_role_name).present?
272
+ raise Kamal::ConfigurationError, "The primary_role #{primary_role_name} isn't defined"
286
273
  end
287
274
 
288
275
  if primary_role.hosts.empty?
289
- raise ArgumentError, "No servers specified for the #{primary_role.name} primary_role"
276
+ raise Kamal::ConfigurationError, "No servers specified for the #{primary_role.name} primary_role"
290
277
  end
291
278
 
292
279
  unless allow_empty_roles?
293
280
  roles.each do |role|
294
281
  if role.hosts.empty?
295
- raise ArgumentError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
282
+ raise Kamal::ConfigurationError, "No servers specified for the #{role.name} role. You can ignore this with allow_empty_roles: true"
296
283
  end
297
284
  end
298
285
  end
@@ -301,21 +288,21 @@ class Kamal::Configuration
301
288
  end
302
289
 
303
290
  def ensure_valid_service_name
304
- raise ArgumentError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/i
291
+ raise Kamal::ConfigurationError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/i
305
292
 
306
293
  true
307
294
  end
308
295
 
309
296
  def ensure_valid_kamal_version
310
297
  if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)
311
- raise ArgumentError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"
298
+ raise Kamal::ConfigurationError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"
312
299
  end
313
300
 
314
301
  true
315
302
  end
316
303
 
317
304
  def ensure_retain_containers_valid
318
- raise ArgumentError, "Must retain at least 1 container" if retain_containers < 1
305
+ raise Kamal::ConfigurationError, "Must retain at least 1 container" if retain_containers < 1
319
306
 
320
307
  true
321
308
  end
@@ -328,7 +315,7 @@ class Kamal::Configuration
328
315
  def git_version
329
316
  @git_version ||=
330
317
  if Kamal::Git.used?
331
- if Kamal::Git.uncommitted_changes.present? && !builder.git_archive?
318
+ if Kamal::Git.uncommitted_changes.present? && !builder.git_clone?
332
319
  uncommitted_suffix = "_uncommitted_#{SecureRandom.hex(8)}"
333
320
  end
334
321
  [ Kamal::Git.revision, uncommitted_suffix ].compact.join
data/lib/kamal/git.rb CHANGED
@@ -16,4 +16,8 @@ module Kamal::Git
16
16
  def uncommitted_changes
17
17
  `git status --porcelain`.strip
18
18
  end
19
+
20
+ def root
21
+ `git rev-parse --show-toplevel`.strip
22
+ end
19
23
  end
@@ -103,3 +103,39 @@ class SSHKit::Backend::Netssh
103
103
 
104
104
  prepend LimitConcurrentStartsInstance
105
105
  end
106
+
107
+ class SSHKit::Runner::Parallel
108
+ # SSHKit joins the threads in sequence and fails on the first error it encounters, which means that we wait threads
109
+ # before the first failure to complete but not for ones after.
110
+ #
111
+ # We'll patch it to wait for them all to complete, and to record all the threads that errored so we can see when a
112
+ # problem occurs on multiple hosts.
113
+ module CompleteAll
114
+ def execute
115
+ threads = hosts.map do |host|
116
+ Thread.new(host) do |h|
117
+ backend(h, &block).run
118
+ rescue ::StandardError => e
119
+ e2 = SSHKit::Runner::ExecuteError.new e
120
+ raise e2, "Exception while executing #{host.user ? "as #{host.user}@" : "on host "}#{host}: #{e.message}"
121
+ end
122
+ end
123
+
124
+ exceptions = []
125
+ threads.each do |t|
126
+ begin
127
+ t.join
128
+ rescue SSHKit::Runner::ExecuteError => e
129
+ exceptions << e
130
+ end
131
+ end
132
+ if exceptions.one?
133
+ raise exceptions.first
134
+ elsif exceptions.many?
135
+ raise exceptions.first, [ "Exceptions on #{exceptions.count} hosts:", exceptions.map(&:message) ].join("\n")
136
+ end
137
+ end
138
+ end
139
+
140
+ prepend CompleteAll
141
+ end
data/lib/kamal/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Kamal
2
- VERSION = "1.5.2"
2
+ VERSION = "1.7.0"
3
3
  end
data/lib/kamal.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  module Kamal
2
+ class ConfigurationError < StandardError; end
2
3
  end
3
4
 
4
5
  require "active_support"
5
6
  require "zeitwerk"
7
+ require "yaml"
6
8
 
7
9
  loader = Zeitwerk::Loader.for_gem
8
10
  loader.ignore(File.join(__dir__, "kamal", "sshkit_with_ext.rb"))
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.5.2
4
+ version: 1.7.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: 2024-05-07 00:00:00.000000000 Z
11
+ date: 2024-06-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -28,16 +28,22 @@ dependencies:
28
28
  name: sshkit
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.22.2
34
+ - - "<"
32
35
  - !ruby/object:Gem::Version
33
- version: '1.21'
36
+ version: '2.0'
34
37
  type: :runtime
35
38
  prerelease: false
36
39
  version_requirements: !ruby/object:Gem::Requirement
37
40
  requirements:
38
- - - "~>"
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 1.22.2
44
+ - - "<"
39
45
  - !ruby/object:Gem::Version
40
- version: '1.21'
46
+ version: '2.0'
41
47
  - !ruby/object:Gem::Dependency
42
48
  name: net-ssh
43
49
  requirement: !ruby/object:Gem::Requirement
@@ -108,6 +114,26 @@ dependencies:
108
114
  - - "~>"
109
115
  - !ruby/object:Gem::Version
110
116
  version: '1.2'
117
+ - !ruby/object:Gem::Dependency
118
+ name: x25519
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '1.0'
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: 1.0.10
127
+ type: :runtime
128
+ prerelease: false
129
+ version_requirements: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - "~>"
132
+ - !ruby/object:Gem::Version
133
+ version: '1.0'
134
+ - - ">="
135
+ - !ruby/object:Gem::Version
136
+ version: 1.0.10
111
137
  - !ruby/object:Gem::Dependency
112
138
  name: bcrypt_pbkdf
113
139
  requirement: !ruby/object:Gem::Requirement
@@ -210,8 +236,10 @@ files:
210
236
  - lib/kamal/cli/app/prepare_assets.rb
211
237
  - lib/kamal/cli/base.rb
212
238
  - lib/kamal/cli/build.rb
239
+ - lib/kamal/cli/build/clone.rb
213
240
  - lib/kamal/cli/env.rb
214
- - lib/kamal/cli/healthcheck.rb
241
+ - lib/kamal/cli/healthcheck/barrier.rb
242
+ - lib/kamal/cli/healthcheck/error.rb
215
243
  - lib/kamal/cli/healthcheck/poller.rb
216
244
  - lib/kamal/cli/lock.rb
217
245
  - lib/kamal/cli/main.rb
@@ -243,13 +271,13 @@ files:
243
271
  - lib/kamal/commands/base.rb
244
272
  - lib/kamal/commands/builder.rb
245
273
  - lib/kamal/commands/builder/base.rb
274
+ - lib/kamal/commands/builder/clone.rb
246
275
  - lib/kamal/commands/builder/multiarch.rb
247
276
  - lib/kamal/commands/builder/multiarch/remote.rb
248
277
  - lib/kamal/commands/builder/native.rb
249
278
  - lib/kamal/commands/builder/native/cached.rb
250
279
  - lib/kamal/commands/builder/native/remote.rb
251
280
  - lib/kamal/commands/docker.rb
252
- - lib/kamal/commands/healthcheck.rb
253
281
  - lib/kamal/commands/hook.rb
254
282
  - lib/kamal/commands/lock.rb
255
283
  - lib/kamal/commands/prune.rb
@@ -260,10 +288,37 @@ files:
260
288
  - lib/kamal/configuration/accessory.rb
261
289
  - lib/kamal/configuration/boot.rb
262
290
  - lib/kamal/configuration/builder.rb
291
+ - lib/kamal/configuration/docs/accessory.yml
292
+ - lib/kamal/configuration/docs/boot.yml
293
+ - lib/kamal/configuration/docs/builder.yml
294
+ - lib/kamal/configuration/docs/configuration.yml
295
+ - lib/kamal/configuration/docs/env.yml
296
+ - lib/kamal/configuration/docs/healthcheck.yml
297
+ - lib/kamal/configuration/docs/logging.yml
298
+ - lib/kamal/configuration/docs/registry.yml
299
+ - lib/kamal/configuration/docs/role.yml
300
+ - lib/kamal/configuration/docs/servers.yml
301
+ - lib/kamal/configuration/docs/ssh.yml
302
+ - lib/kamal/configuration/docs/sshkit.yml
303
+ - lib/kamal/configuration/docs/traefik.yml
263
304
  - lib/kamal/configuration/env.rb
305
+ - lib/kamal/configuration/env/tag.rb
306
+ - lib/kamal/configuration/healthcheck.rb
307
+ - lib/kamal/configuration/logging.rb
308
+ - lib/kamal/configuration/registry.rb
264
309
  - lib/kamal/configuration/role.rb
310
+ - lib/kamal/configuration/servers.rb
265
311
  - lib/kamal/configuration/ssh.rb
266
312
  - lib/kamal/configuration/sshkit.rb
313
+ - lib/kamal/configuration/traefik.rb
314
+ - lib/kamal/configuration/validation.rb
315
+ - lib/kamal/configuration/validator.rb
316
+ - lib/kamal/configuration/validator/accessory.rb
317
+ - lib/kamal/configuration/validator/builder.rb
318
+ - lib/kamal/configuration/validator/env.rb
319
+ - lib/kamal/configuration/validator/registry.rb
320
+ - lib/kamal/configuration/validator/role.rb
321
+ - lib/kamal/configuration/validator/servers.rb
267
322
  - lib/kamal/configuration/volume.rb
268
323
  - lib/kamal/env_file.rb
269
324
  - lib/kamal/git.rb
@@ -291,7 +346,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
291
346
  - !ruby/object:Gem::Version
292
347
  version: '0'
293
348
  requirements: []
294
- rubygems_version: 3.5.6
349
+ rubygems_version: 3.5.11
295
350
  signing_key:
296
351
  specification_version: 4
297
352
  summary: Deploy web apps in containers to servers running Docker with zero downtime.
@@ -1,21 +0,0 @@
1
- class Kamal::Cli::Healthcheck < Kamal::Cli::Base
2
- default_command :perform
3
-
4
- desc "perform", "Health check current app version"
5
- def perform
6
- raise "The primary host is not configured to run Traefik" unless KAMAL.config.role(KAMAL.config.primary_role).running_traefik?
7
- on(KAMAL.primary_host) do
8
- begin
9
- execute *KAMAL.healthcheck.run
10
- Poller.wait_for_healthy { capture_with_info(*KAMAL.healthcheck.status) }
11
- rescue Poller::HealthcheckError => e
12
- error capture_with_info(*KAMAL.healthcheck.logs)
13
- error capture_with_pretty_json(*KAMAL.healthcheck.container_health_log)
14
- raise
15
- ensure
16
- execute *KAMAL.healthcheck.stop, raise_on_non_zero_exit: false
17
- execute *KAMAL.healthcheck.remove, raise_on_non_zero_exit: false
18
- end
19
- end
20
- end
21
- end
@@ -1,59 +0,0 @@
1
- class Kamal::Commands::Healthcheck < Kamal::Commands::Base
2
- def run
3
- primary = config.role(config.primary_role)
4
-
5
- docker :run,
6
- "--detach",
7
- "--name", container_name_with_version,
8
- "--publish", "#{exposed_port}:#{config.healthcheck["port"]}",
9
- "--label", "service=#{config.healthcheck_service}",
10
- "-e", "KAMAL_CONTAINER_NAME=\"#{config.healthcheck_service}\"",
11
- *primary.env_args,
12
- *primary.health_check_args(cord: false),
13
- *config.volume_args,
14
- *primary.option_args,
15
- config.absolute_image,
16
- primary.cmd
17
- end
18
-
19
- def status
20
- pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_STATUS_FORMAT))
21
- end
22
-
23
- def container_health_log
24
- pipe container_id, xargs(docker(:inspect, "--format", DOCKER_HEALTH_LOG_FORMAT))
25
- end
26
-
27
- def logs
28
- pipe container_id, xargs(docker(:logs, "--tail", log_lines, "2>&1"))
29
- end
30
-
31
- def stop
32
- pipe container_id, xargs(docker(:stop))
33
- end
34
-
35
- def remove
36
- pipe container_id, xargs(docker(:container, :rm))
37
- end
38
-
39
- private
40
- def container_name_with_version
41
- "#{config.healthcheck_service}-#{config.version}"
42
- end
43
-
44
- def container_id
45
- container_id_for(container_name: container_name_with_version)
46
- end
47
-
48
- def health_url
49
- "http://localhost:#{exposed_port}#{config.healthcheck["path"]}"
50
- end
51
-
52
- def exposed_port
53
- config.healthcheck["exposed_port"]
54
- end
55
-
56
- def log_lines
57
- config.healthcheck["log_lines"]
58
- end
59
- end