kamal 1.8.3 → 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 +4 -4
  2. data/README.md +1 -1
  3. data/lib/kamal/cli/accessory.rb +92 -38
  4. data/lib/kamal/cli/alias/command.rb +10 -0
  5. data/lib/kamal/cli/app/{prepare_assets.rb → assets.rb} +1 -1
  6. data/lib/kamal/cli/app/boot.rb +23 -16
  7. data/lib/kamal/cli/app/error_pages.rb +33 -0
  8. data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
  9. data/lib/kamal/cli/app.rb +132 -30
  10. data/lib/kamal/cli/base.rb +57 -53
  11. data/lib/kamal/cli/build.rb +81 -38
  12. data/lib/kamal/cli/healthcheck/barrier.rb +2 -0
  13. data/lib/kamal/cli/healthcheck/poller.rb +18 -39
  14. data/lib/kamal/cli/lock.rb +2 -3
  15. data/lib/kamal/cli/main.rb +60 -59
  16. data/lib/kamal/cli/proxy.rb +290 -0
  17. data/lib/kamal/cli/prune.rb +0 -1
  18. data/lib/kamal/cli/registry.rb +2 -0
  19. data/lib/kamal/cli/secrets.rb +49 -0
  20. data/lib/kamal/cli/server.rb +6 -5
  21. data/lib/kamal/cli/templates/deploy.yml +53 -53
  22. data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +2 -12
  23. data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
  24. data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
  25. data/lib/kamal/cli/templates/sample_hooks/post-proxy-reboot.sample +3 -0
  26. data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
  27. data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
  28. data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +1 -1
  29. data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +19 -6
  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 +2 -0
  33. data/lib/kamal/commander/specifics.rb +19 -6
  34. data/lib/kamal/commander.rb +39 -32
  35. data/lib/kamal/commands/accessory/proxy.rb +16 -0
  36. data/lib/kamal/commands/accessory.rb +19 -19
  37. data/lib/kamal/commands/app/assets.rb +10 -10
  38. data/lib/kamal/commands/app/containers.rb +2 -2
  39. data/lib/kamal/commands/app/error_pages.rb +9 -0
  40. data/lib/kamal/commands/app/execution.rb +7 -4
  41. data/lib/kamal/commands/app/images.rb +1 -1
  42. data/lib/kamal/commands/app/logging.rb +16 -6
  43. data/lib/kamal/commands/app/proxy.rb +32 -0
  44. data/lib/kamal/commands/app.rb +25 -24
  45. data/lib/kamal/commands/auditor.rb +12 -3
  46. data/lib/kamal/commands/base.rb +54 -8
  47. data/lib/kamal/commands/builder/base.rb +46 -16
  48. data/lib/kamal/commands/builder/clone.rb +16 -14
  49. data/lib/kamal/commands/builder/cloud.rb +22 -0
  50. data/lib/kamal/commands/builder/hybrid.rb +21 -0
  51. data/lib/kamal/commands/builder/local.rb +14 -0
  52. data/lib/kamal/commands/builder/pack.rb +46 -0
  53. data/lib/kamal/commands/builder/remote.rb +63 -0
  54. data/lib/kamal/commands/builder.rb +21 -45
  55. data/lib/kamal/commands/docker.rb +4 -0
  56. data/lib/kamal/commands/hook.rb +8 -2
  57. data/lib/kamal/commands/lock.rb +2 -6
  58. data/lib/kamal/commands/proxy.rb +127 -0
  59. data/lib/kamal/commands/prune.rb +1 -9
  60. data/lib/kamal/commands/registry.rb +9 -7
  61. data/lib/kamal/commands/server.rb +11 -1
  62. data/lib/kamal/configuration/accessory.rb +89 -12
  63. data/lib/kamal/configuration/alias.rb +15 -0
  64. data/lib/kamal/configuration/builder.rb +73 -15
  65. data/lib/kamal/configuration/docs/accessory.yml +53 -15
  66. data/lib/kamal/configuration/docs/alias.yml +26 -0
  67. data/lib/kamal/configuration/docs/boot.yml +3 -3
  68. data/lib/kamal/configuration/docs/builder.yml +63 -38
  69. data/lib/kamal/configuration/docs/configuration.yml +62 -46
  70. data/lib/kamal/configuration/docs/env.yml +61 -17
  71. data/lib/kamal/configuration/docs/logging.yml +3 -3
  72. data/lib/kamal/configuration/docs/proxy.yml +168 -0
  73. data/lib/kamal/configuration/docs/registry.yml +20 -13
  74. data/lib/kamal/configuration/docs/role.yml +14 -13
  75. data/lib/kamal/configuration/docs/servers.yml +2 -2
  76. data/lib/kamal/configuration/docs/ssh.yml +23 -19
  77. data/lib/kamal/configuration/docs/sshkit.yml +4 -4
  78. data/lib/kamal/configuration/env/tag.rb +4 -3
  79. data/lib/kamal/configuration/env.rb +19 -17
  80. data/lib/kamal/configuration/proxy/boot.rb +129 -0
  81. data/lib/kamal/configuration/proxy.rb +124 -0
  82. data/lib/kamal/configuration/registry.rb +7 -6
  83. data/lib/kamal/configuration/role.rb +69 -98
  84. data/lib/kamal/configuration/servers.rb +8 -1
  85. data/lib/kamal/configuration/validator/accessory.rb +6 -2
  86. data/lib/kamal/configuration/validator/alias.rb +15 -0
  87. data/lib/kamal/configuration/validator/builder.rb +6 -0
  88. data/lib/kamal/configuration/validator/proxy.rb +25 -0
  89. data/lib/kamal/configuration/validator/role.rb +3 -1
  90. data/lib/kamal/configuration/validator/servers.rb +1 -1
  91. data/lib/kamal/configuration/validator.rb +62 -24
  92. data/lib/kamal/configuration.rb +96 -50
  93. data/lib/kamal/docker.rb +30 -0
  94. data/lib/kamal/env_file.rb +7 -1
  95. data/lib/kamal/git.rb +10 -0
  96. data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
  97. data/lib/kamal/secrets/adapters/base.rb +33 -0
  98. data/lib/kamal/secrets/adapters/bitwarden.rb +81 -0
  99. data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
  100. data/lib/kamal/secrets/adapters/doppler.rb +57 -0
  101. data/lib/kamal/secrets/adapters/enpass.rb +71 -0
  102. data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
  103. data/lib/kamal/secrets/adapters/last_pass.rb +40 -0
  104. data/lib/kamal/secrets/adapters/one_password.rb +104 -0
  105. data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
  106. data/lib/kamal/secrets/adapters/test.rb +14 -0
  107. data/lib/kamal/secrets/adapters.rb +16 -0
  108. data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +33 -0
  109. data/lib/kamal/secrets.rb +42 -0
  110. data/lib/kamal/sshkit_with_ext.rb +1 -0
  111. data/lib/kamal/utils.rb +30 -0
  112. data/lib/kamal/version.rb +1 -1
  113. data/lib/kamal.rb +3 -1
  114. metadata +63 -36
  115. data/lib/kamal/cli/env.rb +0 -54
  116. data/lib/kamal/cli/templates/sample_hooks/post-traefik-reboot.sample +0 -3
  117. data/lib/kamal/cli/templates/sample_hooks/pre-traefik-reboot.sample +0 -3
  118. data/lib/kamal/cli/templates/template.env +0 -2
  119. data/lib/kamal/cli/traefik.rb +0 -122
  120. data/lib/kamal/commands/app/cord.rb +0 -22
  121. data/lib/kamal/commands/builder/multiarch/remote.rb +0 -65
  122. data/lib/kamal/commands/builder/multiarch.rb +0 -41
  123. data/lib/kamal/commands/builder/native/cached.rb +0 -25
  124. data/lib/kamal/commands/builder/native/remote.rb +0 -67
  125. data/lib/kamal/commands/builder/native.rb +0 -20
  126. data/lib/kamal/commands/traefik.rb +0 -85
  127. data/lib/kamal/configuration/docs/healthcheck.yml +0 -59
  128. data/lib/kamal/configuration/docs/traefik.yml +0 -62
  129. data/lib/kamal/configuration/healthcheck.rb +0 -63
  130. data/lib/kamal/configuration/traefik.rb +0 -60
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67aa8d9022e0dd44e1be0a0ab8c26f20dd458fb469cf5e9d461dd58871712d8d
4
- data.tar.gz: 286d81788711c43f783e7a4bebc861bf89eeb948f7bebb622686e9947151296d
3
+ metadata.gz: 4e1cf57d731a8b129a8ccbb86faddd3e813bc4d17895e6e538fe904f5bb65d27
4
+ data.tar.gz: 2d7d81b3a34f42fb427bfed18f9cf0ed1955d38e4b4783c1407bc1db6de5cef6
5
5
  SHA512:
6
- metadata.gz: dad7512c1f60ab760bb4cbe5183f8af92b8388697d7778fef2def69bed50be1e90befa09e765e30ad60af58548db69e8184df82053c6f9c1c28e3fa1549373de
7
- data.tar.gz: 75b5950ba4d744e9c7040c0531efcc2c781d13b1910d14226f6241424a6aba87aa0b24dcc249e13248f9c6da64fe2248fadffffbda630033a71ba78a2b3dd4c6
6
+ metadata.gz: f3144c40082cfa24c78e2a1ebf2f433e491be3f5966e45cfc5488c3a11f5a3f513f097f7818cbc9e34d20b79986e64212055a87030fd4aa386a1d82bbb7d0efe
7
+ data.tar.gz: c6b796497a6f7a68815d340664b34fb9943f55ea7121122994aa3fdadaa5b12a18fb4078ff194cffb6060917a74611fdf31017afa75f2515d94ec259e0809251
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Kamal: Deploy web apps anywhere
2
2
 
3
- From bare metal to cloud VMs, deploy web apps anywhere with zero downtime. Kamal has the dynamic reverse-proxy Traefik hold requests while a new app container is started and the old one is stopped. Works seamlessly across multiple hosts, 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.
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
4
 
5
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
6
 
@@ -1,18 +1,39 @@
1
+ require "active_support/core_ext/array/conversions"
2
+ require "concurrent/array"
3
+
1
4
  class Kamal::Cli::Accessory < Kamal::Cli::Base
2
5
  desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
3
- def boot(name, login: true)
6
+ def boot(name, prepare: true)
4
7
  with_lock do
5
8
  if name == "all"
6
9
  KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
7
10
  else
11
+ prepare(name) if prepare
12
+
8
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
+
9
24
  directories(name)
10
25
  upload(name)
11
26
 
12
- on(hosts) do
13
- execute *KAMAL.registry.login if login
27
+ on(hosts) do |host|
14
28
  execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
15
- execute *accessory.run
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
16
37
  end
17
38
  end
18
39
  end
@@ -55,15 +76,10 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
55
76
  if name == "all"
56
77
  KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
57
78
  else
58
- with_accessory(name) do |accessory, hosts|
59
- on(hosts) do
60
- execute *KAMAL.registry.login
61
- end
62
-
63
- stop(name)
64
- remove_container(name)
65
- boot(name, login: false)
66
- end
79
+ prepare(name)
80
+ stop(name)
81
+ remove_container(name)
82
+ boot(name, prepare: false)
67
83
  end
68
84
  end
69
85
  end
@@ -75,6 +91,10 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
75
91
  on(hosts) do
76
92
  execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
77
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
78
98
  end
79
99
  end
80
100
  end
@@ -87,6 +107,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
87
107
  on(hosts) do
88
108
  execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
89
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
90
115
  end
91
116
  end
92
117
  end
@@ -95,10 +120,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
95
120
  desc "restart [NAME]", "Restart existing accessory container on host"
96
121
  def restart(name)
97
122
  with_lock do
98
- with_accessory(name) do
99
- stop(name)
100
- start(name)
101
- end
123
+ stop(name)
124
+ start(name)
102
125
  end
103
126
  end
104
127
 
@@ -114,32 +137,37 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
114
137
  end
115
138
  end
116
139
 
117
- desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
140
+ desc "exec [NAME] [CMD...]", "Execute a custom command on servers within the accessory container (use --help to show options)"
118
141
  option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
119
142
  option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
120
- def exec(name, cmd)
143
+ def exec(name, *cmd)
144
+ pre_connect_if_required
145
+
146
+ cmd = Kamal::Utils.join_commands(cmd)
121
147
  with_accessory(name) do |accessory, hosts|
122
148
  case
123
149
  when options[:interactive] && options[:reuse]
124
- say "Launching interactive command with via SSH from existing container...", :magenta
150
+ say "Launching interactive command via SSH from existing container...", :magenta
125
151
  run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
126
152
 
127
153
  when options[:interactive]
128
154
  say "Launching interactive command via SSH from new container...", :magenta
155
+ on(accessory.hosts.first) { execute *KAMAL.registry.login }
129
156
  run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
130
157
 
131
158
  when options[:reuse]
132
159
  say "Launching command from existing container...", :magenta
133
- on(hosts) do
160
+ on(hosts) do |host|
134
161
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
135
- capture_with_info(*accessory.execute_in_existing_container(cmd))
162
+ puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
136
163
  end
137
164
 
138
165
  else
139
166
  say "Launching command from new container...", :magenta
140
- on(hosts) do
167
+ on(hosts) do |host|
168
+ execute *KAMAL.registry.login
141
169
  execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
142
- capture_with_info(*accessory.execute_in_new_container(cmd))
170
+ puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
143
171
  end
144
172
  end
145
173
  end
@@ -149,25 +177,27 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
149
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)"
150
178
  option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
151
179
  option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
152
- option :grep_options, aliases: "-o", desc: "Additional options supplied to grep"
180
+ option :grep_options, desc: "Additional options supplied to grep"
153
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"
154
183
  def logs(name)
155
184
  with_accessory(name) do |accessory, hosts|
156
185
  grep = options[:grep]
157
186
  grep_options = options[:grep_options]
187
+ timestamps = !options[:skip_timestamps]
158
188
 
159
189
  if options[:follow]
160
190
  run_locally do
161
191
  info "Following logs on #{hosts}..."
162
- info accessory.follow_logs(grep: grep, grep_options: grep_options)
163
- exec accessory.follow_logs(grep: grep, grep_options: grep_options)
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)
164
194
  end
165
195
  else
166
196
  since = options[:since]
167
197
  lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
168
198
 
169
199
  on(hosts) do
170
- puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep, grep_options: grep_options))
200
+ puts capture_with_info(*accessory.logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
171
201
  end
172
202
  end
173
203
  end
@@ -222,6 +252,25 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
222
252
  end
223
253
  end
224
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
+
225
274
  private
226
275
  def with_accessory(name)
227
276
  if KAMAL.config.accessory(name)
@@ -241,19 +290,24 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
241
290
  end
242
291
 
243
292
  def accessory_hosts(accessory)
244
- if KAMAL.specific_hosts&.any?
245
- KAMAL.specific_hosts & accessory.hosts
246
- else
247
- accessory.hosts
248
- end
293
+ KAMAL.accessory_hosts & accessory.hosts
249
294
  end
250
295
 
251
296
  def remove_accessory(name)
252
- with_accessory(name) do
253
- stop(name)
254
- remove_container(name)
255
- remove_image(name)
256
- remove_service_directory(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
257
311
  end
258
312
  end
259
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
@@ -1,4 +1,4 @@
1
- class Kamal::Cli::App::PrepareAssets
1
+ class Kamal::Cli::App::Assets
2
2
  attr_reader :host, :role, :sshkit
3
3
  delegate :execute, :capture_with_info, :info, to: :sshkit
4
4
  delegate :assets?, to: :role
@@ -1,7 +1,7 @@
1
1
  class Kamal::Cli::App::Boot
2
2
  attr_reader :host, :role, :version, :barrier, :sshkit
3
- delegate :execute, :capture_with_info, :capture_with_pretty_json, :info, :error, to: :sshkit
4
- delegate :uses_cord?, :assets?, :running_traefik?, to: :role
3
+ delegate :execute, :capture_with_info, :capture_with_pretty_json, :info, :error, :upload!, to: :sshkit
4
+ delegate :assets?, :running_proxy?, to: :role
5
5
 
6
6
  def initialize(host, role, sshkit, version, barrier)
7
7
  @host = host
@@ -45,11 +45,22 @@ class Kamal::Cli::App::Boot
45
45
 
46
46
  def start_new_version
47
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"
48
52
 
49
- execute *app.tie_cord(role.cord_host_file) if uses_cord?
50
- hostname = "#{host.to_s[0...51].gsub(/\.+$/, '')}-#{SecureRandom.hex(6)}"
51
53
  execute *app.run(hostname: hostname)
52
- Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
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
53
64
  end
54
65
 
55
66
  def stop_new_version
@@ -57,17 +68,9 @@ class Kamal::Cli::App::Boot
57
68
  end
58
69
 
59
70
  def stop_old_version(version)
60
- if uses_cord?
61
- cord = capture_with_info(*app.cord(version: version), raise_on_non_zero_exit: false).strip
62
- if cord.present?
63
- execute *app.cut_cord(cord)
64
- Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
65
- end
66
- end
67
-
68
71
  execute *app.stop(version: version), raise_on_non_zero_exit: false
69
-
70
72
  execute *app.clean_up_assets if assets?
73
+ execute *app.clean_up_error_pages if KAMAL.config.error_pages_path
71
74
  end
72
75
 
73
76
  def release_barrier
@@ -88,8 +91,12 @@ class Kamal::Cli::App::Boot
88
91
  def close_barrier
89
92
  if barrier.close
90
93
  info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
91
- error capture_with_info(*app.logs(version: version))
92
- error capture_with_info(*app.container_health_log(version: version))
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
93
100
  end
94
101
  end
95
102
 
@@ -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