kamal 2.3.0 → 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.
- checksums.yaml +4 -4
- data/lib/kamal/cli/accessory.rb +42 -16
- data/lib/kamal/cli/alias/command.rb +1 -0
- data/lib/kamal/cli/app/{prepare_assets.rb → assets.rb} +1 -1
- data/lib/kamal/cli/app/boot.rb +3 -2
- data/lib/kamal/cli/app/error_pages.rb +33 -0
- data/lib/kamal/cli/app/ssl_certificates.rb +28 -0
- data/lib/kamal/cli/app.rb +94 -29
- data/lib/kamal/cli/base.rb +29 -4
- data/lib/kamal/cli/build.rb +60 -18
- data/lib/kamal/cli/main.rb +8 -10
- data/lib/kamal/cli/proxy.rb +58 -25
- data/lib/kamal/cli/registry.rb +2 -0
- data/lib/kamal/cli/secrets.rb +9 -3
- data/lib/kamal/cli/server.rb +4 -2
- data/lib/kamal/cli/templates/deploy.yml +6 -3
- data/lib/kamal/cli/templates/sample_hooks/post-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-app-boot.sample +3 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +1 -1
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +19 -6
- data/lib/kamal/cli.rb +1 -0
- data/lib/kamal/commander/specifics.rb +9 -1
- data/lib/kamal/commander.rb +18 -27
- data/lib/kamal/commands/accessory/proxy.rb +16 -0
- data/lib/kamal/commands/accessory.rb +9 -9
- data/lib/kamal/commands/app/assets.rb +4 -4
- data/lib/kamal/commands/app/containers.rb +2 -2
- data/lib/kamal/commands/app/error_pages.rb +9 -0
- data/lib/kamal/commands/app/execution.rb +6 -4
- data/lib/kamal/commands/app/images.rb +1 -1
- data/lib/kamal/commands/app/logging.rb +14 -4
- data/lib/kamal/commands/app/proxy.rb +17 -1
- data/lib/kamal/commands/app.rb +19 -10
- data/lib/kamal/commands/auditor.rb +11 -5
- data/lib/kamal/commands/base.rb +37 -1
- data/lib/kamal/commands/builder/base.rb +20 -7
- data/lib/kamal/commands/builder/cloud.rb +22 -0
- data/lib/kamal/commands/builder/pack.rb +46 -0
- data/lib/kamal/commands/builder.rb +11 -19
- data/lib/kamal/commands/proxy.rb +55 -15
- data/lib/kamal/commands/registry.rb +9 -7
- data/lib/kamal/configuration/accessory.rb +66 -11
- data/lib/kamal/configuration/builder.rb +20 -0
- data/lib/kamal/configuration/docs/accessory.yml +32 -4
- data/lib/kamal/configuration/docs/alias.yml +2 -2
- data/lib/kamal/configuration/docs/builder.yml +22 -0
- data/lib/kamal/configuration/docs/configuration.yml +6 -0
- data/lib/kamal/configuration/docs/env.yml +31 -0
- data/lib/kamal/configuration/docs/proxy.yml +78 -15
- data/lib/kamal/configuration/docs/registry.yml +4 -0
- data/lib/kamal/configuration/env.rb +13 -4
- data/lib/kamal/configuration/proxy/boot.rb +129 -0
- data/lib/kamal/configuration/proxy.rb +67 -5
- data/lib/kamal/configuration/registry.rb +6 -6
- data/lib/kamal/configuration/role.rb +11 -9
- data/lib/kamal/configuration/servers.rb +8 -1
- data/lib/kamal/configuration/validator/accessory.rb +6 -2
- data/lib/kamal/configuration/validator/builder.rb +2 -0
- data/lib/kamal/configuration/validator/proxy.rb +10 -0
- data/lib/kamal/configuration/validator/role.rb +3 -1
- data/lib/kamal/configuration/validator/servers.rb +1 -1
- data/lib/kamal/configuration/validator.rb +21 -1
- data/lib/kamal/configuration.rb +36 -57
- data/lib/kamal/docker.rb +30 -0
- data/lib/kamal/git.rb +10 -0
- data/lib/kamal/secrets/adapters/aws_secrets_manager.rb +51 -0
- data/lib/kamal/secrets/adapters/base.rb +13 -3
- data/lib/kamal/secrets/adapters/bitwarden.rb +2 -2
- data/lib/kamal/secrets/adapters/bitwarden_secrets_manager.rb +66 -0
- data/lib/kamal/secrets/adapters/doppler.rb +57 -0
- data/lib/kamal/secrets/adapters/enpass.rb +71 -0
- data/lib/kamal/secrets/adapters/gcp_secret_manager.rb +112 -0
- data/lib/kamal/secrets/adapters/last_pass.rb +3 -2
- data/lib/kamal/secrets/adapters/one_password.rb +47 -13
- data/lib/kamal/secrets/adapters/passbolt.rb +130 -0
- data/lib/kamal/secrets/adapters/test.rb +2 -2
- data/lib/kamal/secrets/adapters.rb +2 -0
- data/lib/kamal/secrets/dotenv/inline_command_substitution.rb +2 -1
- data/lib/kamal/secrets.rb +1 -1
- data/lib/kamal/version.rb +1 -1
- metadata +22 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4e1cf57d731a8b129a8ccbb86faddd3e813bc4d17895e6e538fe904f5bb65d27
|
4
|
+
data.tar.gz: 2d7d81b3a34f42fb427bfed18f9cf0ed1955d38e4b4783c1407bc1db6de5cef6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f3144c40082cfa24c78e2a1ebf2f433e491be3f5966e45cfc5488c3a11f5a3f513f097f7818cbc9e34d20b79986e64212055a87030fd4aa386a1d82bbb7d0efe
|
7
|
+
data.tar.gz: c6b796497a6f7a68815d340664b34fb9943f55ea7121122994aa3fdadaa5b12a18fb4078ff194cffb6060917a74611fdf31017afa75f2515d94ec259e0809251
|
data/lib/kamal/cli/accessory.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require "active_support/core_ext/array/conversions"
|
2
|
+
require "concurrent/array"
|
2
3
|
|
3
4
|
class Kamal::Cli::Accessory < Kamal::Cli::Base
|
4
5
|
desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
|
@@ -10,14 +11,29 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
10
11
|
prepare(name) if prepare
|
11
12
|
|
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
|
+
|
13
24
|
directories(name)
|
14
25
|
upload(name)
|
15
26
|
|
16
|
-
on(hosts) do
|
27
|
+
on(hosts) do |host|
|
17
28
|
execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
18
29
|
execute *accessory.ensure_env_directory
|
19
30
|
upload! accessory.secrets_io, accessory.secrets_path, mode: "0600"
|
20
|
-
execute *accessory.run
|
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
|
21
37
|
end
|
22
38
|
end
|
23
39
|
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
|
@@ -112,32 +137,37 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
112
137
|
end
|
113
138
|
end
|
114
139
|
|
115
|
-
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)"
|
116
141
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
117
142
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
118
|
-
def exec(name, cmd)
|
143
|
+
def exec(name, *cmd)
|
144
|
+
pre_connect_if_required
|
145
|
+
|
146
|
+
cmd = Kamal::Utils.join_commands(cmd)
|
119
147
|
with_accessory(name) do |accessory, hosts|
|
120
148
|
case
|
121
149
|
when options[:interactive] && options[:reuse]
|
122
|
-
say "Launching interactive command
|
150
|
+
say "Launching interactive command via SSH from existing container...", :magenta
|
123
151
|
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
124
152
|
|
125
153
|
when options[:interactive]
|
126
154
|
say "Launching interactive command via SSH from new container...", :magenta
|
155
|
+
on(accessory.hosts.first) { execute *KAMAL.registry.login }
|
127
156
|
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
128
157
|
|
129
158
|
when options[:reuse]
|
130
159
|
say "Launching command from existing container...", :magenta
|
131
|
-
on(hosts) do
|
160
|
+
on(hosts) do |host|
|
132
161
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
133
|
-
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
162
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_existing_container(cmd))
|
134
163
|
end
|
135
164
|
|
136
165
|
else
|
137
166
|
say "Launching command from new container...", :magenta
|
138
|
-
on(hosts) do
|
167
|
+
on(hosts) do |host|
|
168
|
+
execute *KAMAL.registry.login
|
139
169
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
140
|
-
capture_with_info(*accessory.execute_in_new_container(cmd))
|
170
|
+
puts_by_host host, capture_with_info(*accessory.execute_in_new_container(cmd))
|
141
171
|
end
|
142
172
|
end
|
143
173
|
end
|
@@ -147,7 +177,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
147
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)"
|
148
178
|
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
149
179
|
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
150
|
-
option :grep_options,
|
180
|
+
option :grep_options, desc: "Additional options supplied to grep"
|
151
181
|
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
152
182
|
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
153
183
|
def logs(name)
|
@@ -260,11 +290,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
260
290
|
end
|
261
291
|
|
262
292
|
def accessory_hosts(accessory)
|
263
|
-
|
264
|
-
KAMAL.specific_hosts & accessory.hosts
|
265
|
-
else
|
266
|
-
accessory.hosts
|
267
|
-
end
|
293
|
+
KAMAL.accessory_hosts & accessory.hosts
|
268
294
|
end
|
269
295
|
|
270
296
|
def remove_accessory(name)
|
@@ -277,7 +303,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
277
303
|
def prepare(name)
|
278
304
|
with_accessory(name) do |accessory, hosts|
|
279
305
|
on(hosts) do
|
280
|
-
execute *KAMAL.registry.login
|
306
|
+
execute *KAMAL.registry.login(registry_config: accessory.registry)
|
281
307
|
execute *KAMAL.docker.create_network
|
282
308
|
rescue SSHKit::Command::Failed => e
|
283
309
|
raise unless e.message.include?("already exists")
|
data/lib/kamal/cli/app/boot.rb
CHANGED
@@ -45,7 +45,7 @@ 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].
|
48
|
+
hostname = "#{host.to_s[0...51].chomp(".")}-#{SecureRandom.hex(6)}"
|
49
49
|
|
50
50
|
execute *app.ensure_env_directory
|
51
51
|
upload! role.secrets_io(host), role.secrets_path, mode: "0600"
|
@@ -70,6 +70,7 @@ class Kamal::Cli::App::Boot
|
|
70
70
|
def stop_old_version(version)
|
71
71
|
execute *app.stop(version: version), raise_on_non_zero_exit: false
|
72
72
|
execute *app.clean_up_assets if assets?
|
73
|
+
execute *app.clean_up_error_pages if KAMAL.config.error_pages_path
|
73
74
|
end
|
74
75
|
|
75
76
|
def release_barrier
|
@@ -91,7 +92,7 @@ class Kamal::Cli::App::Boot
|
|
91
92
|
if barrier.close
|
92
93
|
info "First #{KAMAL.primary_role} container is unhealthy on #{host}, not booting any other roles"
|
93
94
|
begin
|
94
|
-
error capture_with_info(*app.logs(
|
95
|
+
error capture_with_info(*app.logs(container_id: app.container_id_for_version(version)))
|
95
96
|
error capture_with_info(*app.container_health_log(version: version))
|
96
97
|
rescue SSHKit::Command::Failed
|
97
98
|
error "Could not fetch logs for #{version}"
|
@@ -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
|
data/lib/kamal/cli/app.rb
CHANGED
@@ -7,23 +7,34 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
7
7
|
say "Start container with version #{version} (or reboot if already running)...", :magenta
|
8
8
|
|
9
9
|
# Assets are prepared in a separate step to ensure they are on all hosts before booting
|
10
|
-
on(KAMAL.
|
10
|
+
on(KAMAL.app_hosts) do
|
11
|
+
Kamal::Cli::App::ErrorPages.new(host, self).run
|
12
|
+
|
11
13
|
KAMAL.roles_on(host).each do |role|
|
12
|
-
Kamal::Cli::App::
|
14
|
+
Kamal::Cli::App::Assets.new(host, role, self).run
|
15
|
+
Kamal::Cli::App::SslCertificates.new(host, role, self).run
|
13
16
|
end
|
14
17
|
end
|
15
18
|
|
16
19
|
# Primary hosts and roles are returned first, so they can open the barrier
|
17
20
|
barrier = Kamal::Cli::Healthcheck::Barrier.new
|
18
21
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
+
host_boot_groups.each do |hosts|
|
23
|
+
host_list = Array(hosts).join(",")
|
24
|
+
run_hook "pre-app-boot", hosts: host_list
|
25
|
+
|
26
|
+
on(hosts) do |host|
|
27
|
+
KAMAL.roles_on(host).each do |role|
|
28
|
+
Kamal::Cli::App::Boot.new(host, role, self, version, barrier).run
|
29
|
+
end
|
22
30
|
end
|
31
|
+
|
32
|
+
run_hook "post-app-boot", hosts: host_list
|
33
|
+
sleep KAMAL.config.boot.wait if KAMAL.config.boot.wait
|
23
34
|
end
|
24
35
|
|
25
36
|
# Tag once the app booted on all hosts
|
26
|
-
on(KAMAL.
|
37
|
+
on(KAMAL.app_hosts) do |host|
|
27
38
|
execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
|
28
39
|
execute *KAMAL.app.tag_latest_image
|
29
40
|
end
|
@@ -34,7 +45,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
34
45
|
desc "start", "Start existing app container on servers"
|
35
46
|
def start
|
36
47
|
with_lock do
|
37
|
-
on(KAMAL.
|
48
|
+
on(KAMAL.app_hosts) do |host|
|
38
49
|
roles = KAMAL.roles_on(host)
|
39
50
|
|
40
51
|
roles.each do |role|
|
@@ -57,7 +68,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
57
68
|
desc "stop", "Stop app container on servers"
|
58
69
|
def stop
|
59
70
|
with_lock do
|
60
|
-
on(KAMAL.
|
71
|
+
on(KAMAL.app_hosts) do |host|
|
61
72
|
roles = KAMAL.roles_on(host)
|
62
73
|
|
63
74
|
roles.each do |role|
|
@@ -81,7 +92,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
81
92
|
# FIXME: Drop in favor of just containers?
|
82
93
|
desc "details", "Show details about app containers"
|
83
94
|
def details
|
84
|
-
on(KAMAL.
|
95
|
+
on(KAMAL.app_hosts) do |host|
|
85
96
|
roles = KAMAL.roles_on(host)
|
86
97
|
|
87
98
|
roles.each do |role|
|
@@ -94,9 +105,21 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
94
105
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
95
106
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
96
107
|
option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
|
108
|
+
option :detach, type: :boolean, default: false, desc: "Execute command in a detached container"
|
97
109
|
def exec(*cmd)
|
110
|
+
pre_connect_if_required
|
111
|
+
|
112
|
+
if (incompatible_options = [ :interactive, :reuse ].select { |key| options[:detach] && options[key] }.presence)
|
113
|
+
raise ArgumentError, "Detach is not compatible with #{incompatible_options.join(" or ")}"
|
114
|
+
end
|
115
|
+
|
116
|
+
if cmd.empty?
|
117
|
+
raise ArgumentError, "No command provided. You must specify a command to execute."
|
118
|
+
end
|
119
|
+
|
98
120
|
cmd = Kamal::Utils.join_commands(cmd)
|
99
121
|
env = options[:env]
|
122
|
+
detach = options[:detach]
|
100
123
|
case
|
101
124
|
when options[:interactive] && options[:reuse]
|
102
125
|
say "Get current version of running container...", :magenta unless options[:version]
|
@@ -109,6 +132,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
109
132
|
say "Get most recent version available as an image...", :magenta unless options[:version]
|
110
133
|
using_version(version_or_latest) do |version|
|
111
134
|
say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
|
135
|
+
on(KAMAL.primary_host) { execute *KAMAL.registry.login }
|
112
136
|
run_locally do
|
113
137
|
exec KAMAL.app(role: KAMAL.primary_role, host: KAMAL.primary_host).execute_in_new_container_over_ssh(cmd, env: env)
|
114
138
|
end
|
@@ -119,7 +143,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
119
143
|
using_version(options[:version] || current_running_version) do |version|
|
120
144
|
say "Launching command with version #{version} from existing container...", :magenta
|
121
145
|
|
122
|
-
on(KAMAL.
|
146
|
+
on(KAMAL.app_hosts) do |host|
|
123
147
|
roles = KAMAL.roles_on(host)
|
124
148
|
|
125
149
|
roles.each do |role|
|
@@ -133,12 +157,14 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
133
157
|
say "Get most recent version available as an image...", :magenta unless options[:version]
|
134
158
|
using_version(version_or_latest) do |version|
|
135
159
|
say "Launching command with version #{version} from new container...", :magenta
|
136
|
-
on(KAMAL.
|
160
|
+
on(KAMAL.app_hosts) do |host|
|
161
|
+
execute *KAMAL.registry.login
|
162
|
+
|
137
163
|
roles = KAMAL.roles_on(host)
|
138
164
|
|
139
165
|
roles.each do |role|
|
140
166
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
141
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env))
|
167
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach))
|
142
168
|
end
|
143
169
|
end
|
144
170
|
end
|
@@ -147,7 +173,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
147
173
|
|
148
174
|
desc "containers", "Show app containers on servers"
|
149
175
|
def containers
|
150
|
-
on(KAMAL.
|
176
|
+
on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
|
151
177
|
end
|
152
178
|
|
153
179
|
desc "stale_containers", "Detect app stale containers"
|
@@ -156,7 +182,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
156
182
|
stop = options[:stop]
|
157
183
|
|
158
184
|
with_lock_if_stopping do
|
159
|
-
on(KAMAL.
|
185
|
+
on(KAMAL.app_hosts) do |host|
|
160
186
|
roles = KAMAL.roles_on(host)
|
161
187
|
|
162
188
|
roles.each do |role|
|
@@ -179,22 +205,24 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
179
205
|
|
180
206
|
desc "images", "Show app images on servers"
|
181
207
|
def images
|
182
|
-
on(KAMAL.
|
208
|
+
on(KAMAL.app_hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
|
183
209
|
end
|
184
210
|
|
185
211
|
desc "logs", "Show log lines from app on servers (use --help to show options)"
|
186
212
|
option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
187
213
|
option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
|
188
214
|
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
189
|
-
option :grep_options,
|
215
|
+
option :grep_options, desc: "Additional options supplied to grep"
|
190
216
|
option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
|
191
217
|
option :skip_timestamps, type: :boolean, aliases: "-T", desc: "Skip appending timestamps to logging output"
|
218
|
+
option :container_id, desc: "Docker container ID to fetch logs"
|
192
219
|
def logs
|
193
220
|
# FIXME: Catch when app containers aren't running
|
194
221
|
|
195
222
|
grep = options[:grep]
|
196
223
|
grep_options = options[:grep_options]
|
197
224
|
since = options[:since]
|
225
|
+
container_id = options[:container_id]
|
198
226
|
timestamps = !options[:skip_timestamps]
|
199
227
|
|
200
228
|
if options[:follow]
|
@@ -207,18 +235,18 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
207
235
|
role = KAMAL.roles_on(KAMAL.primary_host).first
|
208
236
|
|
209
237
|
app = KAMAL.app(role: role, host: host)
|
210
|
-
info app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
211
|
-
exec app.follow_logs(host: KAMAL.primary_host, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
238
|
+
info app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
239
|
+
exec app.follow_logs(host: KAMAL.primary_host, container_id: container_id, timestamps: timestamps, lines: lines, grep: grep, grep_options: grep_options)
|
212
240
|
end
|
213
241
|
else
|
214
242
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
215
243
|
|
216
|
-
on(KAMAL.
|
244
|
+
on(KAMAL.app_hosts) do |host|
|
217
245
|
roles = KAMAL.roles_on(host)
|
218
246
|
|
219
247
|
roles.each do |role|
|
220
248
|
begin
|
221
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
|
249
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).logs(container_id: container_id, timestamps: timestamps, since: since, lines: lines, grep: grep, grep_options: grep_options))
|
222
250
|
rescue SSHKit::Command::Failed
|
223
251
|
puts_by_host host, "Nothing found"
|
224
252
|
end
|
@@ -233,14 +261,44 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
233
261
|
stop
|
234
262
|
remove_containers
|
235
263
|
remove_images
|
236
|
-
|
264
|
+
remove_app_directories
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
desc "live", "Set the app to live mode"
|
269
|
+
def live
|
270
|
+
with_lock do
|
271
|
+
on(KAMAL.proxy_hosts) do |host|
|
272
|
+
roles = KAMAL.roles_on(host)
|
273
|
+
|
274
|
+
roles.each do |role|
|
275
|
+
execute *KAMAL.app(role: role, host: host).live if role.running_proxy?
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
desc "maintenance", "Set the app to maintenance mode"
|
282
|
+
option :drain_timeout, type: :numeric, desc: "How long to allow in-flight requests to complete (defaults to drain_timeout from config)"
|
283
|
+
option :message, type: :string, desc: "Message to display to clients while stopped"
|
284
|
+
def maintenance
|
285
|
+
maintenance_options = { drain_timeout: options[:drain_timeout] || KAMAL.config.drain_timeout, message: options[:message] }
|
286
|
+
|
287
|
+
with_lock do
|
288
|
+
on(KAMAL.proxy_hosts) do |host|
|
289
|
+
roles = KAMAL.roles_on(host)
|
290
|
+
|
291
|
+
roles.each do |role|
|
292
|
+
execute *KAMAL.app(role: role, host: host).maintenance(**maintenance_options) if role.running_proxy?
|
293
|
+
end
|
294
|
+
end
|
237
295
|
end
|
238
296
|
end
|
239
297
|
|
240
298
|
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
|
241
299
|
def remove_container(version)
|
242
300
|
with_lock do
|
243
|
-
on(KAMAL.
|
301
|
+
on(KAMAL.app_hosts) do |host|
|
244
302
|
roles = KAMAL.roles_on(host)
|
245
303
|
|
246
304
|
roles.each do |role|
|
@@ -254,7 +312,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
254
312
|
desc "remove_containers", "Remove all app containers from servers", hide: true
|
255
313
|
def remove_containers
|
256
314
|
with_lock do
|
257
|
-
on(KAMAL.
|
315
|
+
on(KAMAL.app_hosts) do |host|
|
258
316
|
roles = KAMAL.roles_on(host)
|
259
317
|
|
260
318
|
roles.each do |role|
|
@@ -268,30 +326,33 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
268
326
|
desc "remove_images", "Remove all app images from servers", hide: true
|
269
327
|
def remove_images
|
270
328
|
with_lock do
|
271
|
-
on(KAMAL.
|
329
|
+
on(KAMAL.app_hosts) do
|
272
330
|
execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
|
273
331
|
execute *KAMAL.app.remove_images
|
274
332
|
end
|
275
333
|
end
|
276
334
|
end
|
277
335
|
|
278
|
-
desc "
|
279
|
-
def
|
336
|
+
desc "remove_app_directories", "Remove the app directories from servers", hide: true
|
337
|
+
def remove_app_directories
|
280
338
|
with_lock do
|
281
|
-
on(KAMAL.
|
339
|
+
on(KAMAL.app_hosts) do |host|
|
282
340
|
roles = KAMAL.roles_on(host)
|
283
341
|
|
284
342
|
roles.each do |role|
|
285
|
-
execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}
|
343
|
+
execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}", role: role), verbosity: :debug
|
286
344
|
execute *KAMAL.server.remove_app_directory, raise_on_non_zero_exit: false
|
287
345
|
end
|
346
|
+
|
347
|
+
execute *KAMAL.auditor.record("Removed #{KAMAL.config.app_directory}"), verbosity: :debug
|
348
|
+
execute *KAMAL.app.remove_proxy_app_directory, raise_on_non_zero_exit: false
|
288
349
|
end
|
289
350
|
end
|
290
351
|
end
|
291
352
|
|
292
353
|
desc "version", "Show app version currently running on servers"
|
293
354
|
def version
|
294
|
-
on(KAMAL.
|
355
|
+
on(KAMAL.app_hosts) do |host|
|
295
356
|
role = KAMAL.roles_on(host).first
|
296
357
|
puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).current_running_version).strip
|
297
358
|
end
|
@@ -332,4 +393,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
332
393
|
yield
|
333
394
|
end
|
334
395
|
end
|
396
|
+
|
397
|
+
def host_boot_groups
|
398
|
+
KAMAL.config.boot.limit ? KAMAL.app_hosts.each_slice(KAMAL.config.boot.limit).to_a : [ KAMAL.app_hosts ]
|
399
|
+
end
|
335
400
|
end
|
data/lib/kamal/cli/base.rb
CHANGED
@@ -5,7 +5,7 @@ module Kamal::Cli
|
|
5
5
|
class Base < Thor
|
6
6
|
include SSHKit::DSL
|
7
7
|
|
8
|
-
def self.exit_on_failure?()
|
8
|
+
def self.exit_on_failure?() true end
|
9
9
|
def self.dynamic_command_class() Kamal::Cli::Alias::Command end
|
10
10
|
|
11
11
|
class_option :verbose, type: :boolean, aliases: "-v", desc: "Detailed logging"
|
@@ -30,6 +30,7 @@ module Kamal::Cli
|
|
30
30
|
else
|
31
31
|
super
|
32
32
|
end
|
33
|
+
|
33
34
|
initialize_commander unless KAMAL.configured?
|
34
35
|
end
|
35
36
|
|
@@ -132,7 +133,13 @@ module Kamal::Cli
|
|
132
133
|
|
133
134
|
def run_hook(hook, **extra_details)
|
134
135
|
if !options[:skip_hooks] && KAMAL.hook.hook_exists?(hook)
|
135
|
-
details = {
|
136
|
+
details = {
|
137
|
+
hosts: KAMAL.hosts.join(","),
|
138
|
+
roles: KAMAL.specific_roles&.join(","),
|
139
|
+
lock: KAMAL.holding_lock?.to_s,
|
140
|
+
command: command,
|
141
|
+
subcommand: subcommand
|
142
|
+
}.compact
|
136
143
|
|
137
144
|
say "Running the #{hook} hook...", :magenta
|
138
145
|
with_env KAMAL.hook.env(**details, **extra_details) do
|
@@ -146,12 +153,16 @@ module Kamal::Cli
|
|
146
153
|
end
|
147
154
|
|
148
155
|
def on(*args, &block)
|
156
|
+
pre_connect_if_required
|
157
|
+
|
158
|
+
super
|
159
|
+
end
|
160
|
+
|
161
|
+
def pre_connect_if_required
|
149
162
|
if !KAMAL.connected?
|
150
163
|
run_hook "pre-connect"
|
151
164
|
KAMAL.connected = true
|
152
165
|
end
|
153
|
-
|
154
|
-
super
|
155
166
|
end
|
156
167
|
|
157
168
|
def command
|
@@ -194,5 +205,19 @@ module Kamal::Cli
|
|
194
205
|
ENV.clear
|
195
206
|
ENV.update(current_env)
|
196
207
|
end
|
208
|
+
|
209
|
+
def ensure_docker_installed
|
210
|
+
run_locally do
|
211
|
+
begin
|
212
|
+
execute *KAMAL.builder.ensure_docker_installed
|
213
|
+
rescue SSHKit::Command::Failed => e
|
214
|
+
error = e.message =~ /command not found/ ?
|
215
|
+
"Docker is not installed locally" :
|
216
|
+
"Docker buildx plugin is not installed locally"
|
217
|
+
|
218
|
+
raise DependencyError, error
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
197
222
|
end
|
198
223
|
end
|