nocoffee-kamal 2.3.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +13 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +287 -0
- data/lib/kamal/cli/alias/command.rb +9 -0
- data/lib/kamal/cli/app/boot.rb +125 -0
- data/lib/kamal/cli/app/prepare_assets.rb +24 -0
- data/lib/kamal/cli/app.rb +335 -0
- data/lib/kamal/cli/base.rb +198 -0
- data/lib/kamal/cli/build/clone.rb +61 -0
- data/lib/kamal/cli/build.rb +162 -0
- data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
- data/lib/kamal/cli/healthcheck/error.rb +2 -0
- data/lib/kamal/cli/healthcheck/poller.rb +42 -0
- data/lib/kamal/cli/lock.rb +45 -0
- data/lib/kamal/cli/main.rb +279 -0
- data/lib/kamal/cli/proxy.rb +257 -0
- data/lib/kamal/cli/prune.rb +34 -0
- data/lib/kamal/cli/registry.rb +17 -0
- data/lib/kamal/cli/secrets.rb +43 -0
- data/lib/kamal/cli/server.rb +48 -0
- data/lib/kamal/cli/templates/deploy.yml +98 -0
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
- data/lib/kamal/cli/templates/secrets +17 -0
- data/lib/kamal/cli.rb +8 -0
- data/lib/kamal/commander/specifics.rb +54 -0
- data/lib/kamal/commander.rb +176 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app/assets.rb +51 -0
- data/lib/kamal/commands/app/containers.rb +31 -0
- data/lib/kamal/commands/app/execution.rb +30 -0
- data/lib/kamal/commands/app/images.rb +13 -0
- data/lib/kamal/commands/app/logging.rb +18 -0
- data/lib/kamal/commands/app/proxy.rb +16 -0
- data/lib/kamal/commands/app.rb +115 -0
- data/lib/kamal/commands/auditor.rb +33 -0
- data/lib/kamal/commands/base.rb +98 -0
- data/lib/kamal/commands/builder/base.rb +111 -0
- data/lib/kamal/commands/builder/clone.rb +31 -0
- data/lib/kamal/commands/builder/hybrid.rb +21 -0
- data/lib/kamal/commands/builder/local.rb +14 -0
- data/lib/kamal/commands/builder/remote.rb +63 -0
- data/lib/kamal/commands/builder.rb +56 -0
- data/lib/kamal/commands/docker.rb +34 -0
- data/lib/kamal/commands/hook.rb +20 -0
- data/lib/kamal/commands/lock.rb +70 -0
- data/lib/kamal/commands/proxy.rb +87 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +14 -0
- data/lib/kamal/commands/server.rb +15 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +186 -0
- data/lib/kamal/configuration/alias.rb +15 -0
- data/lib/kamal/configuration/boot.rb +25 -0
- data/lib/kamal/configuration/builder.rb +191 -0
- data/lib/kamal/configuration/docs/accessory.yml +100 -0
- data/lib/kamal/configuration/docs/alias.yml +26 -0
- data/lib/kamal/configuration/docs/boot.yml +19 -0
- data/lib/kamal/configuration/docs/builder.yml +110 -0
- data/lib/kamal/configuration/docs/configuration.yml +178 -0
- data/lib/kamal/configuration/docs/env.yml +85 -0
- data/lib/kamal/configuration/docs/logging.yml +21 -0
- data/lib/kamal/configuration/docs/proxy.yml +110 -0
- data/lib/kamal/configuration/docs/registry.yml +52 -0
- data/lib/kamal/configuration/docs/role.yml +53 -0
- data/lib/kamal/configuration/docs/servers.yml +27 -0
- data/lib/kamal/configuration/docs/ssh.yml +70 -0
- data/lib/kamal/configuration/docs/sshkit.yml +23 -0
- data/lib/kamal/configuration/env/tag.rb +13 -0
- data/lib/kamal/configuration/env.rb +29 -0
- data/lib/kamal/configuration/logging.rb +33 -0
- data/lib/kamal/configuration/proxy.rb +63 -0
- data/lib/kamal/configuration/registry.rb +32 -0
- data/lib/kamal/configuration/role.rb +220 -0
- data/lib/kamal/configuration/servers.rb +18 -0
- data/lib/kamal/configuration/ssh.rb +57 -0
- data/lib/kamal/configuration/sshkit.rb +22 -0
- data/lib/kamal/configuration/validation.rb +27 -0
- data/lib/kamal/configuration/validator/accessory.rb +9 -0
- data/lib/kamal/configuration/validator/alias.rb +15 -0
- data/lib/kamal/configuration/validator/builder.rb +13 -0
- data/lib/kamal/configuration/validator/configuration.rb +6 -0
- data/lib/kamal/configuration/validator/env.rb +54 -0
- data/lib/kamal/configuration/validator/proxy.rb +15 -0
- data/lib/kamal/configuration/validator/registry.rb +25 -0
- data/lib/kamal/configuration/validator/role.rb +11 -0
- data/lib/kamal/configuration/validator/servers.rb +7 -0
- data/lib/kamal/configuration/validator.rb +171 -0
- data/lib/kamal/configuration/volume.rb +22 -0
- data/lib/kamal/configuration.rb +393 -0
- data/lib/kamal/env_file.rb +44 -0
- data/lib/kamal/git.rb +27 -0
- data/lib/kamal/secrets/adapters/base.rb +23 -0
- data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +39 -0
- data/lib/kamal/secrets/adapters/one_password.rb +70 -0
- data/lib/kamal/secrets/adapters/test.rb +14 -0
- data/lib/kamal/secrets/adapters.rb +14 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +32 -0
- data/lib/kamal/secrets.rb +42 -0
- data/lib/kamal/sshkit_with_ext.rb +142 -0
- data/lib/kamal/tags.rb +40 -0
- data/lib/kamal/utils/sensitive.rb +20 -0
- data/lib/kamal/utils.rb +110 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +14 -0
- 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,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
|