dash 2.12.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 (142) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/dash +18 -0
  5. data/bin/kamal +18 -0
  6. data/lib/kamal/cli/accessory.rb +342 -0
  7. data/lib/kamal/cli/alias/command.rb +10 -0
  8. data/lib/kamal/cli/app/assets.rb +24 -0
  9. data/lib/kamal/cli/app/boot.rb +126 -0
  10. data/lib/kamal/cli/app/error_pages.rb +33 -0
  11. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  12. data/lib/kamal/cli/app.rb +368 -0
  13. data/lib/kamal/cli/base.rb +324 -0
  14. data/lib/kamal/cli/build/clone.rb +59 -0
  15. data/lib/kamal/cli/build/port_forwarding.rb +66 -0
  16. data/lib/kamal/cli/build.rb +242 -0
  17. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  18. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  19. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  20. data/lib/kamal/cli/lock.rb +34 -0
  21. data/lib/kamal/cli/main.rb +299 -0
  22. data/lib/kamal/cli/proxy.rb +419 -0
  23. data/lib/kamal/cli/prune.rb +34 -0
  24. data/lib/kamal/cli/registry.rb +49 -0
  25. data/lib/kamal/cli/secrets.rb +50 -0
  26. data/lib/kamal/cli/server.rb +70 -0
  27. data/lib/kamal/cli/templates/deploy.yml +102 -0
  28. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  29. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  30. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  31. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  32. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  33. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  34. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  35. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
  36. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  37. data/lib/kamal/cli/templates/secrets +22 -0
  38. data/lib/kamal/cli.rb +9 -0
  39. data/lib/kamal/commander/specifics.rb +62 -0
  40. data/lib/kamal/commander.rb +230 -0
  41. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  42. data/lib/kamal/commands/accessory.rb +118 -0
  43. data/lib/kamal/commands/app/assets.rb +51 -0
  44. data/lib/kamal/commands/app/containers.rb +31 -0
  45. data/lib/kamal/commands/app/error_pages.rb +9 -0
  46. data/lib/kamal/commands/app/execution.rb +38 -0
  47. data/lib/kamal/commands/app/images.rb +13 -0
  48. data/lib/kamal/commands/app/logging.rb +28 -0
  49. data/lib/kamal/commands/app/proxy.rb +32 -0
  50. data/lib/kamal/commands/app.rb +125 -0
  51. data/lib/kamal/commands/auditor.rb +39 -0
  52. data/lib/kamal/commands/base.rb +147 -0
  53. data/lib/kamal/commands/builder/base.rb +143 -0
  54. data/lib/kamal/commands/builder/clone.rb +32 -0
  55. data/lib/kamal/commands/builder/cloud.rb +22 -0
  56. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  57. data/lib/kamal/commands/builder/local.rb +20 -0
  58. data/lib/kamal/commands/builder/pack.rb +46 -0
  59. data/lib/kamal/commands/builder/remote.rb +75 -0
  60. data/lib/kamal/commands/builder.rb +54 -0
  61. data/lib/kamal/commands/docker.rb +50 -0
  62. data/lib/kamal/commands/hook.rb +20 -0
  63. data/lib/kamal/commands/loadbalancer.rb +130 -0
  64. data/lib/kamal/commands/lock.rb +70 -0
  65. data/lib/kamal/commands/proxy.rb +150 -0
  66. data/lib/kamal/commands/prune.rb +38 -0
  67. data/lib/kamal/commands/registry.rb +38 -0
  68. data/lib/kamal/commands/server.rb +15 -0
  69. data/lib/kamal/commands.rb +2 -0
  70. data/lib/kamal/configuration/accessory.rb +280 -0
  71. data/lib/kamal/configuration/alias.rb +15 -0
  72. data/lib/kamal/configuration/boot.rb +29 -0
  73. data/lib/kamal/configuration/builder.rb +218 -0
  74. data/lib/kamal/configuration/docs/accessory.yml +160 -0
  75. data/lib/kamal/configuration/docs/alias.yml +29 -0
  76. data/lib/kamal/configuration/docs/boot.yml +21 -0
  77. data/lib/kamal/configuration/docs/builder.yml +132 -0
  78. data/lib/kamal/configuration/docs/configuration.yml +228 -0
  79. data/lib/kamal/configuration/docs/env.yml +118 -0
  80. data/lib/kamal/configuration/docs/logging.yml +21 -0
  81. data/lib/kamal/configuration/docs/output.yml +25 -0
  82. data/lib/kamal/configuration/docs/proxy.yml +207 -0
  83. data/lib/kamal/configuration/docs/registry.yml +64 -0
  84. data/lib/kamal/configuration/docs/role.yml +54 -0
  85. data/lib/kamal/configuration/docs/servers.yml +27 -0
  86. data/lib/kamal/configuration/docs/ssh.yml +81 -0
  87. data/lib/kamal/configuration/docs/sshkit.yml +31 -0
  88. data/lib/kamal/configuration/env/tag.rb +13 -0
  89. data/lib/kamal/configuration/env.rb +42 -0
  90. data/lib/kamal/configuration/loadbalancer.rb +34 -0
  91. data/lib/kamal/configuration/logging.rb +33 -0
  92. data/lib/kamal/configuration/output.rb +34 -0
  93. data/lib/kamal/configuration/proxy/boot.rb +124 -0
  94. data/lib/kamal/configuration/proxy/run.rb +152 -0
  95. data/lib/kamal/configuration/proxy.rb +156 -0
  96. data/lib/kamal/configuration/registry.rb +40 -0
  97. data/lib/kamal/configuration/role.rb +247 -0
  98. data/lib/kamal/configuration/servers.rb +25 -0
  99. data/lib/kamal/configuration/ssh.rb +76 -0
  100. data/lib/kamal/configuration/sshkit.rb +26 -0
  101. data/lib/kamal/configuration/validation.rb +27 -0
  102. data/lib/kamal/configuration/validator/accessory.rb +13 -0
  103. data/lib/kamal/configuration/validator/alias.rb +15 -0
  104. data/lib/kamal/configuration/validator/builder.rb +15 -0
  105. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  106. data/lib/kamal/configuration/validator/env.rb +54 -0
  107. data/lib/kamal/configuration/validator/proxy.rb +47 -0
  108. data/lib/kamal/configuration/validator/registry.rb +27 -0
  109. data/lib/kamal/configuration/validator/role.rb +13 -0
  110. data/lib/kamal/configuration/validator/servers.rb +7 -0
  111. data/lib/kamal/configuration/validator.rb +251 -0
  112. data/lib/kamal/configuration/volume.rb +29 -0
  113. data/lib/kamal/configuration.rb +465 -0
  114. data/lib/kamal/docker.rb +30 -0
  115. data/lib/kamal/env_file.rb +44 -0
  116. data/lib/kamal/git.rb +37 -0
  117. data/lib/kamal/otel_shipper.rb +176 -0
  118. data/lib/kamal/output/base_logger.rb +29 -0
  119. data/lib/kamal/output/file_logger.rb +51 -0
  120. data/lib/kamal/output/formatter.rb +36 -0
  121. data/lib/kamal/output/otel_logger.rb +70 -0
  122. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +59 -0
  123. data/lib/kamal/secrets/adapters/base.rb +33 -0
  124. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  125. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  126. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  127. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  128. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  129. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  130. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  131. data/lib/kamal/secrets/adapters/passbolt.rb +129 -0
  132. data/lib/kamal/secrets/adapters/test.rb +16 -0
  133. data/lib/kamal/secrets/adapters.rb +16 -0
  134. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +47 -0
  135. data/lib/kamal/secrets.rb +53 -0
  136. data/lib/kamal/sshkit_with_ext.rb +273 -0
  137. data/lib/kamal/tags.rb +40 -0
  138. data/lib/kamal/utils/sensitive.rb +20 -0
  139. data/lib/kamal/utils.rb +110 -0
  140. data/lib/kamal/version.rb +3 -0
  141. data/lib/kamal.rb +15 -0
  142. metadata +388 -0
@@ -0,0 +1,59 @@
1
+ class Kamal::Cli::Build::Clone
2
+ attr_reader :sshkit
3
+ delegate :info, :error, :execute, :capture_with_info, to: :sshkit
4
+
5
+ def initialize(sshkit)
6
+ @sshkit = sshkit
7
+ end
8
+
9
+ def prepare
10
+ begin
11
+ clone_repo
12
+ rescue SSHKit::Command::Failed => e
13
+ if e.message =~ /already exists and is not an empty directory/
14
+ reset
15
+ else
16
+ raise Kamal::Cli::Build::BuildError, "Failed to clone repo: #{e.message}"
17
+ end
18
+ end
19
+
20
+ validate!
21
+ rescue Kamal::Cli::Build::BuildError => e
22
+ error "Error preparing clone: #{e.message}, deleting and retrying..."
23
+
24
+ FileUtils.rm_rf KAMAL.config.builder.clone_directory
25
+ clone_repo
26
+ validate!
27
+ end
28
+
29
+ private
30
+ def clone_repo
31
+ info "Cloning repo into build directory `#{KAMAL.config.builder.build_directory}`..."
32
+
33
+ FileUtils.mkdir_p KAMAL.config.builder.clone_directory
34
+ execute *KAMAL.builder.clone
35
+ end
36
+
37
+ def reset
38
+ info "Resetting local clone as `#{KAMAL.config.builder.build_directory}` already exists..."
39
+
40
+ KAMAL.builder.clone_reset_steps.each { |step| execute *step }
41
+ rescue SSHKit::Command::Failed => e
42
+ raise Kamal::Cli::Build::BuildError, "Failed to clone repo: #{e.message}"
43
+ end
44
+
45
+ def validate!
46
+ status = capture_with_info(*KAMAL.builder.clone_status).strip
47
+
48
+ unless status.empty?
49
+ raise Kamal::Cli::Build::BuildError, "Clone in #{KAMAL.config.builder.build_directory} is dirty, #{status}"
50
+ end
51
+
52
+ revision = capture_with_info(*KAMAL.builder.clone_revision).strip
53
+ if revision != Kamal::Git.revision
54
+ raise Kamal::Cli::Build::BuildError, "Clone in #{KAMAL.config.builder.build_directory} is not on the correct revision, expected `#{Kamal::Git.revision}` but got `#{revision}`"
55
+ end
56
+ rescue SSHKit::Command::Failed => e
57
+ raise Kamal::Cli::Build::BuildError, "Failed to validate clone: #{e.message}"
58
+ end
59
+ end
@@ -0,0 +1,66 @@
1
+ require "concurrent/atomic/count_down_latch"
2
+
3
+ class Kamal::Cli::Build::PortForwarding
4
+ attr_reader :hosts, :port, :ssh_options
5
+
6
+ def initialize(hosts, port, **ssh_options)
7
+ @hosts = hosts
8
+ @port = port
9
+ @ssh_options = ssh_options
10
+ end
11
+
12
+ def forward
13
+ @done = false
14
+ forward_ports
15
+
16
+ yield
17
+ ensure
18
+ stop
19
+ end
20
+
21
+ private
22
+ def stop
23
+ @done = true
24
+ @threads.to_a.each(&:join)
25
+ end
26
+
27
+ def forward_ports
28
+ ready = Concurrent::CountDownLatch.new(hosts.size)
29
+
30
+ @threads = hosts.map do |host|
31
+ Thread.new do
32
+ begin
33
+ Net::SSH.start(host, ssh_options[:user], **ssh_options.except(:user)) do |ssh|
34
+ ssh.forward.remote(port, "localhost", port, "127.0.0.1") do |remote_port, bind_address|
35
+ if remote_port == :error
36
+ raise "Failed to establish port forward on #{host}"
37
+ else
38
+ ready.count_down
39
+ end
40
+ end
41
+
42
+ ssh.loop(0.1) do
43
+ if @done
44
+ ssh.forward.cancel_remote(port, "127.0.0.1")
45
+ break
46
+ else
47
+ true
48
+ end
49
+ end
50
+ end
51
+ rescue Exception => e
52
+ error "Error setting up port forwarding to #{host}: #{e.class}: #{e.message}"
53
+ error e.backtrace.join("\n")
54
+
55
+ raise
56
+ end
57
+ end
58
+ end
59
+
60
+ raise "Timed out waiting for port forwarding to be established" unless ready.wait(30)
61
+ end
62
+
63
+ def error(message)
64
+ SSHKit.config.output.error(message)
65
+ end
66
+ end
@@ -0,0 +1,242 @@
1
+ class Kamal::Cli::Build < Kamal::Cli::Base
2
+ class BuildError < StandardError; end
3
+
4
+ desc "deliver", "Build app and push app image to registry then pull image on servers"
5
+ def deliver
6
+ invoke :push
7
+ invoke :pull
8
+ end
9
+
10
+ desc "push", "Build and push app image to registry"
11
+ option :output, type: :string, default: "registry", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
12
+ option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
13
+ def push
14
+ cli = self
15
+
16
+ # Ensure pre-connect hooks run before the build, they may be needed for a remote builder
17
+ # or the pre-build hooks.
18
+ pre_connect_if_required
19
+
20
+ ensure_docker_installed
21
+ setup_local_registry if KAMAL.registry.local?
22
+ login_to_registry_locally unless KAMAL.registry.local?
23
+
24
+ run_hook "pre-build"
25
+
26
+ uncommitted_changes = Kamal::Git.uncommitted_changes
27
+
28
+ if KAMAL.config.builder.git_clone?
29
+ if uncommitted_changes.present?
30
+ say "Building from a local git clone, so ignoring these uncommitted changes:\n #{uncommitted_changes}", :yellow
31
+ end
32
+
33
+ run_locally do
34
+ Clone.new(self).prepare
35
+ end
36
+ elsif uncommitted_changes.present?
37
+ say "Building with uncommitted changes:\n #{uncommitted_changes}", :yellow
38
+ end
39
+
40
+ forward_local_registry_port_for_remote_builder do
41
+ with_env(KAMAL.config.builder.secrets) do
42
+ run_locally do
43
+ begin
44
+ execute *KAMAL.builder.inspect_builder
45
+ rescue SSHKit::Command::Failed => e
46
+ if e.message =~ /(context not found|no builder|no compatible builder|does not exist)/
47
+ warn "Missing compatible builder, so creating a new one first"
48
+ begin
49
+ cli.remove
50
+ rescue SSHKit::Command::Failed
51
+ raise unless e.message =~ /(context not found|no builder|does not exist)/
52
+ end
53
+ cli.create
54
+ else
55
+ raise
56
+ end
57
+ end
58
+
59
+ # Get the command here to ensure the Dir.chdir doesn't interfere with it
60
+ push = KAMAL.builder.push(cli.options[:output], no_cache: cli.options[:no_cache])
61
+
62
+ KAMAL.with_verbosity(:debug) do
63
+ Dir.chdir(KAMAL.config.builder.build_directory) { execute *push, env: KAMAL.builder.push_env }
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ desc "pull", "Pull app image from registry onto servers"
71
+ def pull
72
+ login_to_registry_remotely unless KAMAL.registry.local?
73
+
74
+ forward_local_registry_port(KAMAL.hosts, **KAMAL.config.ssh.options) do
75
+ if (first_hosts = mirror_hosts).any?
76
+ #  Pull on a single host per mirror first to seed them
77
+ say "Pulling image on #{first_hosts.join(", ")} to seed the #{"mirror".pluralize(first_hosts.count)}...", :magenta
78
+ pull_on_hosts(first_hosts)
79
+ say "Pulling image on remaining hosts...", :magenta
80
+ pull_on_hosts(KAMAL.app_hosts - first_hosts)
81
+ else
82
+ pull_on_hosts(KAMAL.app_hosts)
83
+ end
84
+ end
85
+ end
86
+
87
+ desc "create", "Create a build setup"
88
+ def create
89
+ if (remote_host = KAMAL.config.builder.remote)
90
+ connect_to_remote_host(remote_host)
91
+ end
92
+
93
+ run_locally do
94
+ begin
95
+ debug "Using builder: #{KAMAL.builder.name}"
96
+ execute *KAMAL.builder.create
97
+ rescue SSHKit::Command::Failed => e
98
+ if e.message =~ /stderr=(.*)/
99
+ error "Couldn't create remote builder: #{$1}"
100
+ false
101
+ else
102
+ raise
103
+ end
104
+ end
105
+ end
106
+ end
107
+
108
+ desc "remove", "Remove build setup"
109
+ def remove
110
+ run_locally do
111
+ debug "Using builder: #{KAMAL.builder.name}"
112
+ execute *KAMAL.builder.remove
113
+ end
114
+ end
115
+
116
+ desc "details", "Show build setup"
117
+ def details
118
+ run_locally do
119
+ puts "Builder: #{KAMAL.builder.name}"
120
+ puts capture(*KAMAL.builder.info)
121
+ end
122
+ end
123
+
124
+ desc "dev", "Build using the working directory, tag it as dirty, and push to local image store."
125
+ option :output, type: :string, default: "docker", banner: "export_type", desc: "Exported type for the build result, and may be any exported type supported by 'buildx --output'."
126
+ option :no_cache, type: :boolean, default: false, desc: "Build without using Docker's build cache"
127
+ def dev
128
+ cli = self
129
+
130
+ ensure_docker_installed
131
+
132
+ docker_included_files = Set.new(Kamal::Docker.included_files)
133
+ git_uncommitted_files = Set.new(Kamal::Git.uncommitted_files)
134
+ git_untracked_files = Set.new(Kamal::Git.untracked_files)
135
+
136
+ docker_uncommitted_files = docker_included_files & git_uncommitted_files
137
+ if docker_uncommitted_files.any?
138
+ say "WARNING: Files with uncommitted changes will be present in the dev container:", :yellow
139
+ docker_uncommitted_files.sort.each { |f| say " #{f}", :yellow }
140
+ say
141
+ end
142
+
143
+ docker_untracked_files = docker_included_files & git_untracked_files
144
+ if docker_untracked_files.any?
145
+ say "WARNING: Untracked files will be present in the dev container:", :yellow
146
+ docker_untracked_files.sort.each { |f| say " #{f}", :yellow }
147
+ say
148
+ end
149
+
150
+ with_env(KAMAL.config.builder.secrets) do
151
+ run_locally do
152
+ build = KAMAL.builder.push(cli.options[:output], tag_as_dirty: true, no_cache: cli.options[:no_cache])
153
+ KAMAL.with_verbosity(:debug) do
154
+ execute(*build)
155
+ end
156
+ end
157
+ end
158
+ end
159
+
160
+ private
161
+ def connect_to_remote_host(remote_host)
162
+ remote_uri = URI.parse(remote_host)
163
+ if remote_uri.scheme == "ssh"
164
+ host = SSHKit::Host.new(
165
+ hostname: remote_uri.host,
166
+ ssh_options: { user: remote_uri.user, port: remote_uri.port }.compact
167
+ )
168
+ on(host, options) do
169
+ execute "true"
170
+ end
171
+ end
172
+ end
173
+
174
+ def mirror_hosts
175
+ if KAMAL.app_hosts.many?
176
+ mirror_hosts = Concurrent::Hash.new
177
+ on(KAMAL.app_hosts) do |host|
178
+ first_mirror = capture_with_info(*KAMAL.builder.first_mirror).strip.presence
179
+ mirror_hosts[first_mirror] ||= host.to_s if first_mirror
180
+ rescue SSHKit::Command::Failed => e
181
+ raise unless e.message =~ /error calling index: reflect: slice index out of range/
182
+ end
183
+ mirror_hosts.values
184
+ else
185
+ []
186
+ end
187
+ end
188
+
189
+ def pull_on_hosts(hosts)
190
+ on(hosts) do
191
+ execute *KAMAL.auditor.record("Pulled image with version #{KAMAL.config.version}"), verbosity: :debug
192
+ execute *KAMAL.builder.clean, raise_on_non_zero_exit: false
193
+ execute *KAMAL.builder.pull
194
+ execute *KAMAL.builder.validate_image
195
+ end
196
+ end
197
+
198
+ def setup_local_registry
199
+ run_locally do
200
+ execute *KAMAL.registry.setup
201
+ end
202
+ end
203
+
204
+ def login_to_registry_locally
205
+ run_locally do
206
+ execute *KAMAL.registry.login
207
+ end
208
+ end
209
+
210
+ def login_to_registry_remotely
211
+ on(KAMAL.app_hosts) do
212
+ execute *KAMAL.registry.login
213
+ end
214
+ end
215
+
216
+ def forward_local_registry_port_for_remote_builder(&block)
217
+ if KAMAL.builder.remote?
218
+ remote_uri = URI(KAMAL.config.builder.remote)
219
+ forward_local_registry_port([ remote_uri.host ], **remote_builder_ssh_options(remote_uri), &block)
220
+ else
221
+ yield
222
+ end
223
+ end
224
+
225
+ def forward_local_registry_port(hosts, **ssh_options, &block)
226
+ if KAMAL.config.registry.local?
227
+ say "Setting up local registry port forwarding to #{hosts.join(', ')}..."
228
+ PortForwarding.new(hosts, KAMAL.config.registry.local_port, **ssh_options).forward(&block)
229
+ else
230
+ yield
231
+ end
232
+ end
233
+
234
+ def remote_builder_ssh_options(remote_uri)
235
+ { user: remote_uri.user,
236
+ port: remote_uri.port,
237
+ keepalive: KAMAL.config.ssh.options[:keepalive],
238
+ keepalive_interval: KAMAL.config.ssh.options[:keepalive_interval],
239
+ logger: KAMAL.config.ssh.options[:logger]
240
+ }.compact
241
+ end
242
+ end
@@ -0,0 +1,33 @@
1
+ require "concurrent/ivar"
2
+
3
+ class Kamal::Cli::Healthcheck::Barrier
4
+ def initialize
5
+ @ivar = Concurrent::IVar.new
6
+ end
7
+
8
+ def close
9
+ set(false)
10
+ end
11
+
12
+ def open
13
+ set(true)
14
+ end
15
+
16
+ def wait
17
+ unless opened?
18
+ raise Kamal::Cli::Healthcheck::Error.new("Halted at barrier")
19
+ end
20
+ end
21
+
22
+ private
23
+ def opened?
24
+ @ivar.value
25
+ end
26
+
27
+ def set(value)
28
+ @ivar.set(value)
29
+ true
30
+ rescue Concurrent::MultipleAssignmentError
31
+ false
32
+ end
33
+ end
@@ -0,0 +1,2 @@
1
+ class Kamal::Cli::Healthcheck::Error < StandardError
2
+ end
@@ -0,0 +1,42 @@
1
+ module Kamal::Cli::Healthcheck::Poller
2
+ extend self
3
+
4
+ def wait_for_healthy(&block)
5
+ attempt = 1
6
+ timeout_at = Time.now + KAMAL.config.deploy_timeout
7
+ readiness_delay = KAMAL.config.readiness_delay
8
+
9
+ begin
10
+ status = block.call
11
+
12
+ if status == "running"
13
+ # Wait for the readiness delay and confirm it is still running
14
+ if readiness_delay > 0
15
+ info "Container is running, waiting for readiness delay of #{readiness_delay} seconds"
16
+ sleep readiness_delay
17
+ status = block.call
18
+ end
19
+ end
20
+
21
+ unless %w[ running healthy ].include?(status)
22
+ raise Kamal::Cli::Healthcheck::Error, "container not ready after #{KAMAL.config.deploy_timeout} seconds (#{status})"
23
+ end
24
+ rescue Kamal::Cli::Healthcheck::Error => e
25
+ time_left = timeout_at - Time.now
26
+ if time_left > 0
27
+ sleep [ attempt, time_left ].min
28
+ attempt += 1
29
+ retry
30
+ else
31
+ raise
32
+ end
33
+ end
34
+
35
+ info "Container is healthy!"
36
+ end
37
+
38
+ private
39
+ def info(message)
40
+ SSHKit.config.output.info(message)
41
+ end
42
+ end
@@ -0,0 +1,34 @@
1
+ class Kamal::Cli::Lock < Kamal::Cli::Base
2
+ desc "status", "Report lock status"
3
+ def status
4
+ handle_missing_lock do
5
+ puts capture_lock_status
6
+ end
7
+ end
8
+
9
+ desc "acquire", "Acquire the deploy lock"
10
+ option :message, aliases: "-m", type: :string, desc: "A lock message", required: true
11
+ def acquire
12
+ ensure_run_directory
13
+
14
+ raise_if_locked do
15
+ execute_lock_acquire(options[:message])
16
+ say "Acquired the deploy lock"
17
+ end
18
+ end
19
+
20
+ desc "release", "Release the deploy lock"
21
+ def release
22
+ handle_missing_lock do
23
+ execute_lock_release
24
+ say "Released the deploy lock"
25
+ end
26
+ end
27
+
28
+ private
29
+ def handle_missing_lock
30
+ yield
31
+ rescue LockMissingError
32
+ say "There is no deploy lock"
33
+ end
34
+ end