kamal-insecure 2.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 (130) 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 +313 -0
  6. data/lib/kamal/cli/alias/command.rb +10 -0
  7. data/lib/kamal/cli/app/assets.rb +24 -0
  8. data/lib/kamal/cli/app/boot.rb +126 -0
  9. data/lib/kamal/cli/app/error_pages.rb +33 -0
  10. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  11. data/lib/kamal/cli/app.rb +400 -0
  12. data/lib/kamal/cli/base.rb +223 -0
  13. data/lib/kamal/cli/build/clone.rb +61 -0
  14. data/lib/kamal/cli/build.rb +204 -0
  15. data/lib/kamal/cli/healthcheck/barrier.rb +33 -0
  16. data/lib/kamal/cli/healthcheck/error.rb +2 -0
  17. data/lib/kamal/cli/healthcheck/poller.rb +42 -0
  18. data/lib/kamal/cli/lock.rb +45 -0
  19. data/lib/kamal/cli/main.rb +277 -0
  20. data/lib/kamal/cli/proxy.rb +290 -0
  21. data/lib/kamal/cli/prune.rb +34 -0
  22. data/lib/kamal/cli/registry.rb +19 -0
  23. data/lib/kamal/cli/secrets.rb +49 -0
  24. data/lib/kamal/cli/server.rb +50 -0
  25. data/lib/kamal/cli/templates/deploy.yml +101 -0
  26. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  28. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
  29. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  30. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  31. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
  32. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
  33. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +122 -0
  34. data/lib/kamal/cli/templates/sample_hooks/pre-proxy-reboot.sample +3 -0
  35. data/lib/kamal/cli/templates/secrets +17 -0
  36. data/lib/kamal/cli.rb +9 -0
  37. data/lib/kamal/commander/specifics.rb +62 -0
  38. data/lib/kamal/commander.rb +167 -0
  39. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  40. data/lib/kamal/commands/accessory.rb +113 -0
  41. data/lib/kamal/commands/app/assets.rb +51 -0
  42. data/lib/kamal/commands/app/containers.rb +31 -0
  43. data/lib/kamal/commands/app/error_pages.rb +9 -0
  44. data/lib/kamal/commands/app/execution.rb +32 -0
  45. data/lib/kamal/commands/app/images.rb +13 -0
  46. data/lib/kamal/commands/app/logging.rb +28 -0
  47. data/lib/kamal/commands/app/proxy.rb +32 -0
  48. data/lib/kamal/commands/app.rb +124 -0
  49. data/lib/kamal/commands/auditor.rb +39 -0
  50. data/lib/kamal/commands/base.rb +134 -0
  51. data/lib/kamal/commands/builder/base.rb +124 -0
  52. data/lib/kamal/commands/builder/clone.rb +31 -0
  53. data/lib/kamal/commands/builder/cloud.rb +22 -0
  54. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  55. data/lib/kamal/commands/builder/local.rb +14 -0
  56. data/lib/kamal/commands/builder/pack.rb +46 -0
  57. data/lib/kamal/commands/builder/remote.rb +63 -0
  58. data/lib/kamal/commands/builder.rb +48 -0
  59. data/lib/kamal/commands/docker.rb +34 -0
  60. data/lib/kamal/commands/hook.rb +20 -0
  61. data/lib/kamal/commands/lock.rb +70 -0
  62. data/lib/kamal/commands/proxy.rb +127 -0
  63. data/lib/kamal/commands/prune.rb +38 -0
  64. data/lib/kamal/commands/registry.rb +16 -0
  65. data/lib/kamal/commands/server.rb +15 -0
  66. data/lib/kamal/commands.rb +2 -0
  67. data/lib/kamal/configuration/accessory.rb +241 -0
  68. data/lib/kamal/configuration/alias.rb +15 -0
  69. data/lib/kamal/configuration/boot.rb +25 -0
  70. data/lib/kamal/configuration/builder.rb +211 -0
  71. data/lib/kamal/configuration/docs/accessory.yml +128 -0
  72. data/lib/kamal/configuration/docs/alias.yml +26 -0
  73. data/lib/kamal/configuration/docs/boot.yml +19 -0
  74. data/lib/kamal/configuration/docs/builder.yml +132 -0
  75. data/lib/kamal/configuration/docs/configuration.yml +184 -0
  76. data/lib/kamal/configuration/docs/env.yml +116 -0
  77. data/lib/kamal/configuration/docs/logging.yml +21 -0
  78. data/lib/kamal/configuration/docs/proxy.yml +164 -0
  79. data/lib/kamal/configuration/docs/registry.yml +56 -0
  80. data/lib/kamal/configuration/docs/role.yml +53 -0
  81. data/lib/kamal/configuration/docs/servers.yml +27 -0
  82. data/lib/kamal/configuration/docs/ssh.yml +70 -0
  83. data/lib/kamal/configuration/docs/sshkit.yml +23 -0
  84. data/lib/kamal/configuration/env/tag.rb +13 -0
  85. data/lib/kamal/configuration/env.rb +38 -0
  86. data/lib/kamal/configuration/logging.rb +33 -0
  87. data/lib/kamal/configuration/proxy/boot.rb +129 -0
  88. data/lib/kamal/configuration/proxy.rb +124 -0
  89. data/lib/kamal/configuration/registry.rb +32 -0
  90. data/lib/kamal/configuration/role.rb +222 -0
  91. data/lib/kamal/configuration/servers.rb +25 -0
  92. data/lib/kamal/configuration/ssh.rb +57 -0
  93. data/lib/kamal/configuration/sshkit.rb +22 -0
  94. data/lib/kamal/configuration/validation.rb +27 -0
  95. data/lib/kamal/configuration/validator/accessory.rb +13 -0
  96. data/lib/kamal/configuration/validator/alias.rb +15 -0
  97. data/lib/kamal/configuration/validator/builder.rb +15 -0
  98. data/lib/kamal/configuration/validator/configuration.rb +6 -0
  99. data/lib/kamal/configuration/validator/env.rb +54 -0
  100. data/lib/kamal/configuration/validator/proxy.rb +25 -0
  101. data/lib/kamal/configuration/validator/registry.rb +25 -0
  102. data/lib/kamal/configuration/validator/role.rb +13 -0
  103. data/lib/kamal/configuration/validator/servers.rb +7 -0
  104. data/lib/kamal/configuration/validator.rb +191 -0
  105. data/lib/kamal/configuration/volume.rb +22 -0
  106. data/lib/kamal/configuration.rb +372 -0
  107. data/lib/kamal/docker.rb +30 -0
  108. data/lib/kamal/env_file.rb +44 -0
  109. data/lib/kamal/git.rb +37 -0
  110. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
  111. data/lib/kamal/secrets/adapters/base.rb +33 -0
  112. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  113. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  114. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  115. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  116. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  117. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  118. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  119. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  120. data/lib/kamal/secrets/adapters/test.rb +14 -0
  121. data/lib/kamal/secrets/adapters.rb +16 -0
  122. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +33 -0
  123. data/lib/kamal/secrets.rb +42 -0
  124. data/lib/kamal/sshkit_with_ext.rb +142 -0
  125. data/lib/kamal/tags.rb +40 -0
  126. data/lib/kamal/utils/sensitive.rb +20 -0
  127. data/lib/kamal/utils.rb +110 -0
  128. data/lib/kamal/version.rb +3 -0
  129. data/lib/kamal.rb +14 -0
  130. metadata +365 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: bf1698db231c02cb26727e394ea52f0e81ee5f7d7406ddd9c224bb3486658f36
4
+ data.tar.gz: ebfffb70f35a6c0d0de37f05eaac85da88825a16e6b97d642ae8c1e3bbbf24e3
5
+ SHA512:
6
+ metadata.gz: 1cf64fa62b7f99f5d5125bd3d8284115300396cd336b92fe957d17793790441c7e00ef90977bd48caf2e349a1f142c9ceb01dad72f2de953fbc3f6e33c8c9f53
7
+ data.tar.gz: 0fd3635f7e485d47c14d5bd22dd7afdd93d8b9edd271a02a89ac7c73e71a5fd1960528539751ec3b25d747b7b929067c6fda24457f30c056575be5a853522e23
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,313 @@
1
+ require "active_support/core_ext/array/conversions"
2
+ require "concurrent/array"
3
+
4
+ class Kamal::Cli::Accessory < Kamal::Cli::Base
5
+ desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
6
+ def boot(name, prepare: true)
7
+ with_lock do
8
+ if name == "all"
9
+ KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
10
+ else
11
+ prepare(name) if prepare
12
+
13
+ with_accessory(name) do |accessory, hosts|
14
+ booted_hosts = Concurrent::Array.new
15
+ on(hosts) do |host|
16
+ booted_hosts << host.to_s if capture_with_info(*accessory.info(all: true, quiet: true)).strip.presence
17
+ end
18
+
19
+ if booted_hosts.any?
20
+ say "Skipping booting `#{name}` on #{booted_hosts.sort.join(", ")}, a container already exists", :yellow
21
+ hosts -= booted_hosts
22
+ end
23
+
24
+ directories(name)
25
+ upload(name)
26
+
27
+ on(hosts) do |host|
28
+ execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
29
+ execute *accessory.ensure_env_directory
30
+ upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
31
+ execute *accessory.run(host: host)
32
+
33
+ if accessory.running_proxy?
34
+ target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
35
+ execute *accessory.deploy(target: target)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ desc "upload [NAME]", "Upload accessory files to host", hide: true
44
+ def upload(name)
45
+ with_lock do
46
+ with_accessory(name) do |accessory, hosts|
47
+ on(hosts) do
48
+ accessory.files.each do |(local, remote)|
49
+ accessory.ensure_local_file_present(local)
50
+
51
+ execute *accessory.make_directory_for(remote)
52
+ upload! local, remote
53
+ execute :chmod, "755", remote
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ desc "directories [NAME]", "Create accessory directories on host", hide: true
61
+ def directories(name)
62
+ with_lock do
63
+ with_accessory(name) do |accessory, hosts|
64
+ on(hosts) do
65
+ accessory.directories.keys.each do |host_path|
66
+ execute *accessory.make_directory(host_path)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container; use NAME=all to boot all accessories)"
74
+ def reboot(name)
75
+ with_lock do
76
+ if name == "all"
77
+ KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
78
+ else
79
+ prepare(name)
80
+ stop(name)
81
+ remove_container(name)
82
+ boot(name, prepare: false)
83
+ end
84
+ end
85
+ end
86
+
87
+ desc "start [NAME]", "Start existing accessory container on host"
88
+ def start(name)
89
+ with_lock do
90
+ with_accessory(name) do |accessory, hosts|
91
+ on(hosts) do
92
+ execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
93
+ execute *accessory.start
94
+ if accessory.running_proxy?
95
+ target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
96
+ execute *accessory.deploy(target: target)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ desc "stop [NAME]", "Stop existing accessory container on host"
104
+ def stop(name)
105
+ with_lock do
106
+ with_accessory(name) do |accessory, hosts|
107
+ on(hosts) do
108
+ execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
109
+ execute *accessory.stop, raise_on_non_zero_exit: false
110
+
111
+ if accessory.running_proxy?
112
+ target = capture_with_info(*accessory.container_id_for(container_name: accessory.service_name, only_running: true)).strip
113
+ execute *accessory.remove if target
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ desc "restart [NAME]", "Restart existing accessory container on host"
121
+ def restart(name)
122
+ with_lock do
123
+ stop(name)
124
+ start(name)
125
+ end
126
+ end
127
+
128
+ desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
129
+ def details(name)
130
+ if name == "all"
131
+ KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
132
+ else
133
+ type = "Accessory #{name}"
134
+ with_accessory(name) do |accessory, hosts|
135
+ on(hosts) { puts_by_host host, capture_with_info(*accessory.info), type: type }
136
+ end
137
+ end
138
+ end
139
+
140
+ desc "exec [NAME] [CMD...]", "Execute a custom command on servers within the accessory container (use --help to show options)"
141
+ option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
142
+ option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
143
+ def exec(name, *cmd)
144
+ pre_connect_if_required
145
+
146
+ cmd = Kamal::Utils.join_commands(cmd)
147
+ with_accessory(name) do |accessory, hosts|
148
+ case
149
+ when options[:interactive] && options[:reuse]
150
+ say "Launching interactive command via SSH from existing container...", :magenta
151
+ run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
152
+
153
+ when options[:interactive]
154
+ say "Launching interactive command via SSH from new container...", :magenta
155
+ on(accessory.hosts.first) { execute *KAMAL.registry.login }
156
+ run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
157
+
158
+ when options[:reuse]
159
+ say "Launching command from existing container...", :magenta
160
+ on(hosts) do |host|
161
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
162
+ puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
163
+ end
164
+
165
+ else
166
+ say "Launching command from new container...", :magenta
167
+ on(hosts) do |host|
168
+ execute *KAMAL.registry.login
169
+ execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
170
+ puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
171
+ end
172
+ end
173
+ end
174
+ end
175
+
176
+ desc "logs [NAME]", "Show log lines from accessory on host (use --help to show options)"
177
+ option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
178
+ option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
179
+ option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
180
+ option :grep_options, desc: "Additional options supplied to grep"
181
+ option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
182
+ option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
183
+ def logs(name)
184
+ with_accessory(name) do |accessory, hosts|
185
+ grep = options[:grep]
186
+ grep_options = options[:grep_options]
187
+ timestamps = !options[:skip_timestamps]
188
+
189
+ if options[:follow]
190
+ run_locally do
191
+ info "Following logs on #{hosts}..."
192
+ info accessory.follow_logs(timestamps: timestamps, grep: grep, grep_options: grep_options)
193
+ exec accessory.follow_logs(timestamps: timestamps, grep: grep, grep_options: grep_options)
194
+ end
195
+ else
196
+ since = options[:since]
197
+ lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
198
+
199
+ on(hosts) do
200
+ puts capture_with_info(*accessory.logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
201
+ end
202
+ end
203
+ end
204
+ end
205
+
206
+ desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
207
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
208
+ def remove(name)
209
+ confirming "This will remove all containers, images and data directories for #{name}. Are you sure?" do
210
+ with_lock do
211
+ if name == "all"
212
+ KAMAL.accessory_names.each { |accessory_name| remove_accessory(accessory_name) }
213
+ else
214
+ remove_accessory(name)
215
+ end
216
+ end
217
+ end
218
+ end
219
+
220
+ desc "remove_container [NAME]", "Remove accessory container from host", hide: true
221
+ def remove_container(name)
222
+ with_lock do
223
+ with_accessory(name) do |accessory, hosts|
224
+ on(hosts) do
225
+ execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
226
+ execute *accessory.remove_container
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ desc "remove_image [NAME]", "Remove accessory image from host", hide: true
233
+ def remove_image(name)
234
+ with_lock do
235
+ with_accessory(name) do |accessory, hosts|
236
+ on(hosts) do
237
+ execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
238
+ execute *accessory.remove_image
239
+ end
240
+ end
241
+ end
242
+ end
243
+
244
+ desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
245
+ def remove_service_directory(name)
246
+ with_lock do
247
+ with_accessory(name) do |accessory, hosts|
248
+ on(hosts) do
249
+ execute *accessory.remove_service_directory
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ desc "upgrade", "Upgrade accessories from Kamal 1.x to 2.0 (restart them in 'kamal' network)"
256
+ option :rolling, type: :boolean, default: false, desc: "Upgrade one host at a time"
257
+ option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
258
+ def upgrade(name)
259
+ confirming "This will restart all accessories" do
260
+ with_lock do
261
+ host_groups = options[:rolling] ? KAMAL.accessory_hosts : [ KAMAL.accessory_hosts ]
262
+ host_groups.each do |hosts|
263
+ host_list = Array(hosts).join(",")
264
+ KAMAL.with_specific_hosts(hosts) do
265
+ say "Upgrading #{name} accessories on #{host_list}...", :magenta
266
+ reboot name
267
+ say "Upgraded #{name} accessories on #{host_list}...", :magenta
268
+ end
269
+ end
270
+ end
271
+ end
272
+ end
273
+
274
+ private
275
+ def with_accessory(name)
276
+ if KAMAL.config.accessory(name)
277
+ accessory = KAMAL.accessory(name)
278
+ yield accessory, accessory_hosts(accessory)
279
+ else
280
+ error_on_missing_accessory(name)
281
+ end
282
+ end
283
+
284
+ def error_on_missing_accessory(name)
285
+ options = KAMAL.accessory_names.presence
286
+
287
+ error \
288
+ "No accessory by the name of '#{name}'" +
289
+ (options ? " (options: #{options.to_sentence})" : "")
290
+ end
291
+
292
+ def accessory_hosts(accessory)
293
+ KAMAL.accessory_hosts & accessory.hosts
294
+ end
295
+
296
+ def remove_accessory(name)
297
+ stop(name)
298
+ remove_container(name)
299
+ remove_image(name)
300
+ remove_service_directory(name)
301
+ end
302
+
303
+ def prepare(name)
304
+ with_accessory(name) do |accessory, hosts|
305
+ on(hosts) do
306
+ execute *KAMAL.registry.login(registry_config: accessory.registry)
307
+ execute *KAMAL.docker.create_network
308
+ rescue SSHKit::Command::Failed => e
309
+ raise unless e.message.include?("already exists")
310
+ end
311
+ end
312
+ end
313
+ end
@@ -0,0 +1,10 @@
1
+ class Kamal::Cli::Alias::Command < Thor::DynamicCommand
2
+ def run(instance, args = [])
3
+ if (_alias = KAMAL.config.aliases[name])
4
+ KAMAL.reset
5
+ Kamal::Cli::Main.start(Shellwords.split(_alias.command) + ARGV[1..-1])
6
+ else
7
+ super
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,24 @@
1
+ class Kamal::Cli::App::Assets
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
@@ -0,0 +1,126 @@
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].chomp(".")}-#{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
+ execute *app.clean_up_error_pages if KAMAL.config.error_pages_path
74
+ end
75
+
76
+ def release_barrier
77
+ if barrier.open
78
+ info "First #{KAMAL.primary_role} container is healthy on #{host}, booting any other roles"
79
+ end
80
+ end
81
+
82
+ def wait_at_barrier
83
+ info "Waiting for the first healthy #{KAMAL.primary_role} container before booting #{role} on #{host}..."
84
+ barrier.wait
85
+ info "First #{KAMAL.primary_role} container is healthy, booting #{role} on #{host}..."
86
+ rescue Kamal::Cli::Healthcheck::Error
87
+ info "First #{KAMAL.primary_role} container is unhealthy, not booting #{role} on #{host}"
88
+ raise
89
+ end
90
+
91
+ def close_barrier
92
+ if barrier.close
93
+ info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
94
+ begin
95
+ error capture_with_info(*app.logs(container_id: app.container_id_for_version(version)))
96
+ error capture_with_info(*app.container_health_log(version: version))
97
+ rescue SSHKit::Command::Failed
98
+ error "Could not fetch logs for #{version}"
99
+ end
100
+ end
101
+ end
102
+
103
+ def barrier_role?
104
+ role == KAMAL.primary_role
105
+ end
106
+
107
+ def app
108
+ @app ||= KAMAL.app(role: role, host: host)
109
+ end
110
+
111
+ def auditor
112
+ @auditor = KAMAL.auditor(role: role)
113
+ end
114
+
115
+ def audit(message)
116
+ execute *auditor.record(message), verbosity: :debug
117
+ end
118
+
119
+ def gatekeeper?
120
+ barrier && barrier_role?
121
+ end
122
+
123
+ def queuer?
124
+ barrier && !barrier_role?
125
+ end
126
+ end
@@ -0,0 +1,33 @@
1
+ class Kamal::Cli::App::ErrorPages
2
+ ERROR_PAGES_GLOB = "{4??.html,5??.html}"
3
+
4
+ attr_reader :host, :sshkit
5
+ delegate :upload!, :execute, to: :sshkit
6
+
7
+ def initialize(host, sshkit)
8
+ @host = host
9
+ @sshkit = sshkit
10
+ end
11
+
12
+ def run
13
+ if KAMAL.config.error_pages_path
14
+ with_error_pages_tmpdir do |local_error_pages_dir|
15
+ execute *KAMAL.app.create_error_pages_directory
16
+ upload! local_error_pages_dir, KAMAL.config.proxy_boot.error_pages_directory, mode: "0700", recursive: true
17
+ end
18
+ end
19
+ end
20
+
21
+ private
22
+ def with_error_pages_tmpdir
23
+ Dir.mktmpdir("kamal-error-pages") do |tmpdir|
24
+ error_pages_dir = File.join(tmpdir, KAMAL.config.version)
25
+ FileUtils.mkdir(error_pages_dir)
26
+
27
+ if (files = Dir[File.join(KAMAL.config.error_pages_path, ERROR_PAGES_GLOB)]).any?
28
+ FileUtils.cp(files, error_pages_dir)
29
+ yield error_pages_dir
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,28 @@
1
+ class Kamal::Cli::App::SslCertificates
2
+ attr_reader :host, :role, :sshkit
3
+ delegate :execute, :info, :upload!, to: :sshkit
4
+
5
+ def initialize(host, role, sshkit)
6
+ @host = host
7
+ @role = role
8
+ @sshkit = sshkit
9
+ end
10
+
11
+ def run
12
+ if role.running_proxy? && role.proxy.custom_ssl_certificate?
13
+ info "Writing SSL certificates for #{role.name} on #{host}"
14
+ execute *app.create_ssl_directory
15
+ if cert_content = role.proxy.certificate_pem_content
16
+ upload!(StringIO.new(cert_content), role.proxy.host_tls_cert, mode: "0644")
17
+ end
18
+ if key_content = role.proxy.private_key_pem_content
19
+ upload!(StringIO.new(key_content), role.proxy.host_tls_key, mode: "0644")
20
+ end
21
+ end
22
+ end
23
+
24
+ private
25
+ def app
26
+ @app ||= KAMAL.app(role: role, host: host)
27
+ end
28
+ end