nocoffee-kamal 2.3.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (114) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +13 -0
  4. data/bin/kamal +18 -0
  5. data/lib/kamal/cli/accessory.rb +287 -0
  6. data/lib/kamal/cli/alias/command.rb +9 -0
  7. data/lib/kamal/cli/app/boot.rb +125 -0
  8. data/lib/kamal/cli/app/prepare_assets.rb +24 -0
  9. data/lib/kamal/cli/app.rb +335 -0
  10. data/lib/kamal/cli/base.rb +198 -0
  11. data/lib/kamal/cli/build/clone.rb +61 -0
  12. data/lib/kamal/cli/build.rb +162 -0
  13. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  14. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  15. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  16. data/lib/kamal/cli/lock.rb +45 -0
  17. data/lib/kamal/cli/main.rb +279 -0
  18. data/lib/kamal/cli/proxy.rb +257 -0
  19. data/lib/kamal/cli/prune.rb +34 -0
  20. data/lib/kamal/cli/registry.rb +17 -0
  21. data/lib/kamal/cli/secrets.rb +43 -0
  22. data/lib/kamal/cli/server.rb +48 -0
  23. data/lib/kamal/cli/templates/deploy.yml +98 -0
  24. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  25. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  26. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
  30. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  31. data/lib/kamal/cli/templates/secrets +17 -0
  32. data/lib/kamal/cli.rb +8 -0
  33. data/lib/kamal/commander/specifics.rb +54 -0
  34. data/lib/kamal/commander.rb +176 -0
  35. data/lib/kamal/commands/accessory.rb +113 -0
  36. data/lib/kamal/commands/app/assets.rb +51 -0
  37. data/lib/kamal/commands/app/containers.rb +31 -0
  38. data/lib/kamal/commands/app/execution.rb +30 -0
  39. data/lib/kamal/commands/app/images.rb +13 -0
  40. data/lib/kamal/commands/app/logging.rb +18 -0
  41. data/lib/kamal/commands/app/proxy.rb +16 -0
  42. data/lib/kamal/commands/app.rb +115 -0
  43. data/lib/kamal/commands/auditor.rb +33 -0
  44. data/lib/kamal/commands/base.rb +98 -0
  45. data/lib/kamal/commands/builder/base.rb +111 -0
  46. data/lib/kamal/commands/builder/clone.rb +31 -0
  47. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  48. data/lib/kamal/commands/builder/local.rb +14 -0
  49. data/lib/kamal/commands/builder/remote.rb +63 -0
  50. data/lib/kamal/commands/builder.rb +56 -0
  51. data/lib/kamal/commands/docker.rb +34 -0
  52. data/lib/kamal/commands/hook.rb +20 -0
  53. data/lib/kamal/commands/lock.rb +70 -0
  54. data/lib/kamal/commands/proxy.rb +87 -0
  55. data/lib/kamal/commands/prune.rb +38 -0
  56. data/lib/kamal/commands/registry.rb +14 -0
  57. data/lib/kamal/commands/server.rb +15 -0
  58. data/lib/kamal/commands.rb +2 -0
  59. data/lib/kamal/configuration/accessory.rb +186 -0
  60. data/lib/kamal/configuration/alias.rb +15 -0
  61. data/lib/kamal/configuration/boot.rb +25 -0
  62. data/lib/kamal/configuration/builder.rb +191 -0
  63. data/lib/kamal/configuration/docs/accessory.yml +100 -0
  64. data/lib/kamal/configuration/docs/alias.yml +26 -0
  65. data/lib/kamal/configuration/docs/boot.yml +19 -0
  66. data/lib/kamal/configuration/docs/builder.yml +110 -0
  67. data/lib/kamal/configuration/docs/configuration.yml +178 -0
  68. data/lib/kamal/configuration/docs/env.yml +85 -0
  69. data/lib/kamal/configuration/docs/logging.yml +21 -0
  70. data/lib/kamal/configuration/docs/proxy.yml +110 -0
  71. data/lib/kamal/configuration/docs/registry.yml +52 -0
  72. data/lib/kamal/configuration/docs/role.yml +53 -0
  73. data/lib/kamal/configuration/docs/servers.yml +27 -0
  74. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  75. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  76. data/lib/kamal/configuration/env/tag.rb +13 -0
  77. data/lib/kamal/configuration/env.rb +29 -0
  78. data/lib/kamal/configuration/logging.rb +33 -0
  79. data/lib/kamal/configuration/proxy.rb +63 -0
  80. data/lib/kamal/configuration/registry.rb +32 -0
  81. data/lib/kamal/configuration/role.rb +220 -0
  82. data/lib/kamal/configuration/servers.rb +18 -0
  83. data/lib/kamal/configuration/ssh.rb +57 -0
  84. data/lib/kamal/configuration/sshkit.rb +22 -0
  85. data/lib/kamal/configuration/validation.rb +27 -0
  86. data/lib/kamal/configuration/validator/accessory.rb +9 -0
  87. data/lib/kamal/configuration/validator/alias.rb +15 -0
  88. data/lib/kamal/configuration/validator/builder.rb +13 -0
  89. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  90. data/lib/kamal/configuration/validator/env.rb +54 -0
  91. data/lib/kamal/configuration/validator/proxy.rb +15 -0
  92. data/lib/kamal/configuration/validator/registry.rb +25 -0
  93. data/lib/kamal/configuration/validator/role.rb +11 -0
  94. data/lib/kamal/configuration/validator/servers.rb +7 -0
  95. data/lib/kamal/configuration/validator.rb +171 -0
  96. data/lib/kamal/configuration/volume.rb +22 -0
  97. data/lib/kamal/configuration.rb +393 -0
  98. data/lib/kamal/env_file.rb +44 -0
  99. data/lib/kamal/git.rb +27 -0
  100. data/lib/kamal/secrets/adapters/base.rb +23 -0
  101. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  102. data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
  103. data/lib/kamal/secrets/adapters/one_password.rb +70 -0
  104. data/lib/kamal/secrets/adapters/test.rb +14 -0
  105. data/lib/kamal/secrets/adapters.rb +14 -0
  106. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
  107. data/lib/kamal/secrets.rb +42 -0
  108. data/lib/kamal/sshkit_with_ext.rb +142 -0
  109. data/lib/kamal/tags.rb +40 -0
  110. data/lib/kamal/utils/sensitive.rb +20 -0
  111. data/lib/kamal/utils.rb +110 -0
  112. data/lib/kamal/version.rb +3 -0
  113. data/lib/kamal.rb +14 -0
  114. metadata +349 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: eb35407e368cbba3b4910024786b8ab7cdde387c95f7fc2c13f42b012e4a70aa
4
+ data.tar.gz: ef27660c970f54c9ba62305fa42f308835c661551f117e7964984d3437eb650c
5
+ SHA512:
6
+ metadata.gz: d0b0ce4e4a9fe6943b2601f5ae9a50ff6cbe6a3a57d1e085a9f31d19e82aaa13434af30962c5392a794cdad3fb18598b0d9dad57d7cc53f84ad6ec82811f791f
7
+ data.tar.gz: 5d6f6279a3c19bea79f62b523b27167f092488c8a75eb6f825f959ee3e6cc09ed2f8d249194d249f68abf513e0d417d5f00a2da9b70f12ecd8726c1003c1173a
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2023 David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # Kamal: Deploy web apps anywhere
2
+
3
+ From bare metal to cloud VMs, deploy web apps anywhere with zero downtime. Kamal uses [kamal-proxy](https://github.com/basecamp/kamal-proxy) to seamlessly switch requests between containers. Works seamlessly across multiple servers, using SSHKit to execute commands. Originally built for Rails apps, Kamal will work with any type of web app that can be containerized with Docker.
4
+
5
+ ➡️ See [kamal-deploy.org](https://kamal-deploy.org) for documentation on [installation](https://kamal-deploy.org/docs/installation), [configuration](https://kamal-deploy.org/docs/configuration), and [commands](https://kamal-deploy.org/docs/commands).
6
+
7
+ ## Contributing to the documentation
8
+
9
+ Please help us improve Kamal's documentation on the [the basecamp/kamal-site repository](https://github.com/basecamp/kamal-site).
10
+
11
+ ## License
12
+
13
+ Kamal is released under the [MIT License](https://opensource.org/licenses/MIT).
data/bin/kamal ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Prevent failures from being reported twice.
4
+ Thread.report_on_exception = false
5
+
6
+ require "kamal"
7
+
8
+ begin
9
+ Kamal::Cli::Main.start(ARGV)
10
+ rescue SSHKit::Runner::ExecuteError => e
11
+ puts " \e[31mERROR (#{e.cause.class}): #{e.message}\e[0m"
12
+ puts e.cause.backtrace if ENV["VERBOSE"]
13
+ exit 1
14
+ rescue => e
15
+ puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
16
+ puts e.backtrace if ENV["VERBOSE"]
17
+ exit 1
18
+ end
@@ -0,0 +1,287 @@
1
+ require "active_support/core_ext/array/conversions"
2
+
3
+ class Kamal::Cli::Accessory < Kamal::Cli::Base
4
+ desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
5
+ def boot(name, prepare: true)
6
+ with_lock do
7
+ if name == "all"
8
+ KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
9
+ else
10
+ prepare(name) if prepare
11
+
12
+ with_accessory(name) do |accessory, hosts|
13
+ directories(name)
14
+ upload(name)
15
+
16
+ on(hosts) do
17
+ execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
18
+ execute *accessory.ensure_env_directory
19
+ upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
20
+ execute *accessory.run
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+
27
+ desc "upload [NAME]", "Upload accessory files to host", hide: true
28
+ def upload(name)
29
+ with_lock do
30
+ with_accessory(name) do |accessory, hosts|
31
+ on(hosts) do
32
+ accessory.files.each do |(local, remote)|
33
+ accessory.ensure_local_file_present(local)
34
+
35
+ execute *accessory.make_directory_for(remote)
36
+ upload! local, remote
37
+ execute :chmod, "755", remote
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ desc "directories [NAME]", "Create accessory directories on host", hide: true
45
+ def directories(name)
46
+ with_lock do
47
+ with_accessory(name) do |accessory, hosts|
48
+ on(hosts) do
49
+ accessory.directories.keys.each do |host_path|
50
+ execute *accessory.make_directory(host_path)
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container; use NAME=all to boot all accessories)"
58
+ def reboot(name)
59
+ with_lock do
60
+ if name == "all"
61
+ KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
62
+ else
63
+ prepare(name)
64
+ stop(name)
65
+ remove_container(name)
66
+ boot(name, prepare: false)
67
+ end
68
+ end
69
+ end
70
+
71
+ desc "start [NAME]", "Start existing accessory container on host"
72
+ def start(name)
73
+ with_lock do
74
+ with_accessory(name) do |accessory, hosts|
75
+ on(hosts) do
76
+ execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
77
+ execute *accessory.start
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ desc "stop [NAME]", "Stop existing accessory container on host"
84
+ def stop(name)
85
+ with_lock do
86
+ with_accessory(name) do |accessory, hosts|
87
+ on(hosts) do
88
+ execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
89
+ execute *accessory.stop, raise_on_non_zero_exit: false
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ desc "restart [NAME]", "Restart existing accessory container on host"
96
+ def restart(name)
97
+ with_lock do
98
+ stop(name)
99
+ start(name)
100
+ end
101
+ end
102
+
103
+ desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
104
+ def details(name)
105
+ if name == "all"
106
+ KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
107
+ else
108
+ type = "Accessory #{name}"
109
+ with_accessory(name) do |accessory, hosts|
110
+ on(hosts) { puts_by_host host, capture_with_info(*accessory.info), type: type }
111
+ end
112
+ end
113
+ end
114
+
115
+ desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
116
+ option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
117
+ option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
118
+ def exec(name, cmd)
119
+ with_accessory(name) do |accessory, hosts|
120
+ case
121
+ when options[:interactive] && options[:reuse]
122
+ say "Launching interactive command with via SSH from existing container...", :magenta
123
+ run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
124
+
125
+ when options[:interactive]
126
+ say "Launching interactive command via SSH from new container...", :magenta
127
+ run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
128
+
129
+ when options[:reuse]
130
+ say "Launching command from existing container...", :magenta
131
+ on(hosts) do
132
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
133
+ capture_with_info(*accessory.execute_in_existing_container(cmd))
134
+ end
135
+
136
+ else
137
+ say "Launching command from new container...", :magenta
138
+ on(hosts) do
139
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
140
+ capture_with_info(*accessory.execute_in_new_container(cmd))
141
+ end
142
+ end
143
+ end
144
+ end
145
+
146
+ desc "logs [NAME]", "Show log lines from accessory on host (use --help to show options)"
147
+ option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
148
+ option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
149
+ option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
150
+ option :grep_options, aliases: "-o", desc: "Additional options supplied to grep"
151
+ option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
152
+ option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
153
+ def logs(name)
154
+ with_accessory(name) do |accessory, hosts|
155
+ grep = options[:grep]
156
+ grep_options = options[:grep_options]
157
+ timestamps = !options[:skip_timestamps]
158
+
159
+ if options[:follow]
160
+ run_locally do
161
+ info "Following logs on #{hosts}..."
162
+ info accessory.follow_logs(timestamps: timestamps, grep: grep, grep_options: grep_options)
163
+ exec accessory.follow_logs(timestamps: timestamps, grep: grep, grep_options: grep_options)
164
+ end
165
+ else
166
+ since = options[:since]
167
+ lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
168
+
169
+ on(hosts) do
170
+ puts capture_with_info(*accessory.logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
177
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
178
+ def remove(name)
179
+ confirming "This will remove all containers, images and data directories for #{name}. Are you sure?" do
180
+ with_lock do
181
+ if name == "all"
182
+ KAMAL.accessory_names.each { |accessory_name| remove_accessory(accessory_name) }
183
+ else
184
+ remove_accessory(name)
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ desc "remove_container [NAME]", "Remove accessory container from host", hide: true
191
+ def remove_container(name)
192
+ with_lock do
193
+ with_accessory(name) do |accessory, hosts|
194
+ on(hosts) do
195
+ execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
196
+ execute *accessory.remove_container
197
+ end
198
+ end
199
+ end
200
+ end
201
+
202
+ desc "remove_image [NAME]", "Remove accessory image from host", hide: true
203
+ def remove_image(name)
204
+ with_lock do
205
+ with_accessory(name) do |accessory, hosts|
206
+ on(hosts) do
207
+ execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
208
+ execute *accessory.remove_image
209
+ end
210
+ end
211
+ end
212
+ end
213
+
214
+ desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
215
+ def remove_service_directory(name)
216
+ with_lock do
217
+ with_accessory(name) do |accessory, hosts|
218
+ on(hosts) do
219
+ execute *accessory.remove_service_directory
220
+ end
221
+ end
222
+ end
223
+ end
224
+
225
+ desc "upgrade", "Upgrade accessories from Kamal 1.x to 2.0 (restart them in 'kamal' network)"
226
+ option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
227
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
228
+ def upgrade(name)
229
+ confirming "This will restart all accessories" do
230
+ with_lock do
231
+ host_groups = options[:rolling] ? KAMAL.accessory_hosts : [ KAMAL.accessory_hosts ]
232
+ host_groups.each do |hosts|
233
+ host_list = Array(hosts).join(",")
234
+ KAMAL.with_specific_hosts(hosts) do
235
+ say "Upgrading #{name} accessories on #{host_list}...", :magenta
236
+ reboot name
237
+ say "Upgraded #{name} accessories on #{host_list}...", :magenta
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ private
245
+ def with_accessory(name)
246
+ if KAMAL.config.accessory(name)
247
+ accessory = KAMAL.accessory(name)
248
+ yield accessory, accessory_hosts(accessory)
249
+ else
250
+ error_on_missing_accessory(name)
251
+ end
252
+ end
253
+
254
+ def error_on_missing_accessory(name)
255
+ options = KAMAL.accessory_names.presence
256
+
257
+ error \
258
+ "No accessory by the name of '#{name}'" +
259
+ (options ? " (options: #{options.to_sentence})" : "")
260
+ end
261
+
262
+ def accessory_hosts(accessory)
263
+ if KAMAL.specific_hosts&.any?
264
+ KAMAL.specific_hosts & accessory.hosts
265
+ else
266
+ accessory.hosts
267
+ end
268
+ end
269
+
270
+ def remove_accessory(name)
271
+ stop(name)
272
+ remove_container(name)
273
+ remove_image(name)
274
+ remove_service_directory(name)
275
+ end
276
+
277
+ def prepare(name)
278
+ with_accessory(name) do |accessory, hosts|
279
+ on(hosts) do
280
+ execute *KAMAL.registry.login
281
+ execute *KAMAL.docker.create_network
282
+ rescue SSHKit::Command::Failed => e
283
+ raise unless e.message.include?("already exists")
284
+ end
285
+ end
286
+ end
287
+ end
@@ -0,0 +1,9 @@
1
+ class Kamal::Cli::Alias::Command < Thor::DynamicCommand
2
+ def run(instance, args = [])
3
+ if (_alias = KAMAL.config.aliases[name])
4
+ Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
5
+ else
6
+ super
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,125 @@
1
+ class Kamal::Cli::App::Boot
2
+ attr_reader :host, :role, :version, :barrier, :sshkit
3
+ delegate :execute, :capture_with_info, :capture_with_pretty_json, :info, :error, :upload!, to: :sshkit
4
+ delegate :assets?, :running_proxy?, to: :role
5
+
6
+ def initialize(host, role, sshkit, version, barrier)
7
+ @host = host
8
+ @role = role
9
+ @version = version
10
+ @barrier = barrier
11
+ @sshkit = sshkit
12
+ end
13
+
14
+ def run
15
+ old_version = old_version_renamed_if_clashing
16
+
17
+ wait_at_barrier if queuer?
18
+
19
+ begin
20
+ start_new_version
21
+ rescue => e
22
+ close_barrier if gatekeeper?
23
+ stop_new_version
24
+ raise
25
+ end
26
+
27
+ release_barrier if gatekeeper?
28
+
29
+ if old_version
30
+ stop_old_version(old_version)
31
+ end
32
+ end
33
+
34
+ private
35
+ def old_version_renamed_if_clashing
36
+ if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
37
+ renamed_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
38
+ info "Renaming container #{version} to #{renamed_version} as already deployed on #{host}"
39
+ audit("Renaming container #{version} to #{renamed_version}")
40
+ execute *app.rename_container(version: version, new_version: renamed_version)
41
+ end
42
+
43
+ capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip.presence
44
+ end
45
+
46
+ def start_new_version
47
+ audit "Booted app version #{version}"
48
+ hostname = "#{host.to_s[0...51].gsub(/\.+$/, '')}-#{SecureRandom.hex(6)}"
49
+
50
+ execute *app.ensure_env_directory
51
+ upload! role.secrets_io(host), role.secrets_path, mode: "0600"
52
+
53
+ execute *app.run(hostname: hostname)
54
+ if running_proxy?
55
+ endpoint = capture_with_info(*app.container_id_for_version(version)).strip
56
+ raise Kamal::Cli::BootError, "Failed to get endpoint for #{role} on #{host}, did the container boot?" if endpoint.empty?
57
+ execute *app.deploy(target: endpoint)
58
+ else
59
+ Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
60
+ end
61
+ rescue => e
62
+ error "Failed to boot #{role} on #{host}"
63
+ raise e
64
+ end
65
+
66
+ def stop_new_version
67
+ execute *app.stop(version: version), raise_on_non_zero_exit: false
68
+ end
69
+
70
+ def stop_old_version(version)
71
+ execute *app.stop(version: version), raise_on_non_zero_exit: false
72
+ execute *app.clean_up_assets if assets?
73
+ end
74
+
75
+ def release_barrier
76
+ if barrier.open
77
+ info "First #{KAMAL.primary_role} container is healthy on #{host}, booting any other roles"
78
+ end
79
+ end
80
+
81
+ def wait_at_barrier
82
+ info "Waiting for the first healthy #{KAMAL.primary_role} container before booting #{role} on #{host}..."
83
+ barrier.wait
84
+ info "First #{KAMAL.primary_role} container is healthy, booting #{role} on #{host}..."
85
+ rescue Kamal::Cli::Healthcheck::Error
86
+ info "First #{KAMAL.primary_role} container is unhealthy, not booting #{role} on #{host}"
87
+ raise
88
+ end
89
+
90
+ def close_barrier
91
+ if barrier.close
92
+ info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
93
+ begin
94
+ error capture_with_info(*app.logs(version: version))
95
+ error capture_with_info(*app.container_health_log(version: version))
96
+ rescue SSHKit::Command::Failed
97
+ error "Could not fetch logs for #{version}"
98
+ end
99
+ end
100
+ end
101
+
102
+ def barrier_role?
103
+ role == KAMAL.primary_role
104
+ end
105
+
106
+ def app
107
+ @app ||= KAMAL.app(role: role, host: host)
108
+ end
109
+
110
+ def auditor
111
+ @auditor = KAMAL.auditor(role: role)
112
+ end
113
+
114
+ def audit(message)
115
+ execute *auditor.record(message), verbosity: :debug
116
+ end
117
+
118
+ def gatekeeper?
119
+ barrier && barrier_role?
120
+ end
121
+
122
+ def queuer?
123
+ barrier && !barrier_role?
124
+ end
125
+ end
@@ -0,0 +1,24 @@
1
+ class Kamal::Cli::App::PrepareAssets
2
+ attr_reader :host, :role, :sshkit
3
+ delegate :execute, :capture_with_info, :info, to: :sshkit
4
+ delegate :assets?, to: :role
5
+
6
+ def initialize(host, role, sshkit)
7
+ @host = host
8
+ @role = role
9
+ @sshkit = sshkit
10
+ end
11
+
12
+ def run
13
+ if assets?
14
+ execute *app.extract_assets
15
+ old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
16
+ execute *app.sync_asset_volumes(old_version: old_version)
17
+ end
18
+ end
19
+
20
+ private
21
+ def app
22
+ @app ||= KAMAL.app(role: role, host: host)
23
+ end
24
+ end