kamal 1.3.1 → 1.5.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 +38 -29
- data/lib/kamal/cli/app/boot.rb +67 -0
- data/lib/kamal/cli/app/prepare_assets.rb +24 -0
- data/lib/kamal/cli/app.rb +25 -67
- data/lib/kamal/cli/base.rb +23 -8
- data/lib/kamal/cli/env.rb +3 -5
- data/lib/kamal/cli/main.rb +7 -4
- data/lib/kamal/cli/prune.rb +6 -2
- data/lib/kamal/cli/server.rb +3 -1
- data/lib/kamal/cli/templates/deploy.yml +5 -1
- data/lib/kamal/cli/templates/sample_hooks/docker-setup.sample +7 -0
- data/lib/kamal/cli/traefik.rb +16 -13
- data/lib/kamal/commander/specifics.rb +49 -0
- data/lib/kamal/commander.rb +9 -33
- data/lib/kamal/commands/accessory.rb +2 -2
- data/lib/kamal/commands/app/assets.rb +12 -12
- data/lib/kamal/commands/app/cord.rb +4 -4
- data/lib/kamal/commands/app/execution.rb +10 -8
- data/lib/kamal/commands/app/images.rb +1 -1
- data/lib/kamal/commands/app/logging.rb +2 -2
- data/lib/kamal/commands/app.rb +38 -18
- data/lib/kamal/commands/auditor.rb +1 -1
- data/lib/kamal/commands/base.rb +12 -0
- data/lib/kamal/commands/builder/base.rb +22 -5
- data/lib/kamal/commands/builder/multiarch.rb +17 -9
- data/lib/kamal/commands/builder/native/cached.rb +7 -6
- data/lib/kamal/commands/builder/native/remote.rb +9 -9
- data/lib/kamal/commands/builder/native.rb +8 -7
- data/lib/kamal/commands/docker.rb +10 -1
- data/lib/kamal/commands/healthcheck.rb +0 -1
- data/lib/kamal/commands/hook.rb +1 -1
- data/lib/kamal/commands/lock.rb +19 -9
- data/lib/kamal/commands/prune.rb +4 -4
- data/lib/kamal/commands/registry.rb +4 -1
- data/lib/kamal/commands/server.rb +1 -1
- data/lib/kamal/commands/traefik.rb +10 -16
- data/lib/kamal/configuration/accessory.rb +10 -20
- data/lib/kamal/configuration/boot.rb +1 -1
- data/lib/kamal/configuration/builder.rb +11 -3
- data/lib/kamal/configuration/env.rb +40 -0
- data/lib/kamal/configuration/role.rb +23 -40
- data/lib/kamal/configuration.rb +53 -21
- data/lib/kamal/env_file.rb +12 -15
- data/lib/kamal/sshkit_with_ext.rb +1 -0
- data/lib/kamal/utils.rb +7 -3
- data/lib/kamal/version.rb +1 -1
- data/lib/kamal.rb +1 -1
- metadata +8 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d8429a6060103f16d9aadb517e77fbb32fc6c6d2e6b9b3cf2989c8a9309e0067
|
4
|
+
data.tar.gz: e997f3c5765639c4b14aad08a81344c5af182904e3d80f4867792ed55a9fc25b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 575353b6ea9e9d429a6c6e6b89013fd36f0b371b4edbffbf3ab33f707a9637191872d7463fb670dbe45f9a36bc7a1792335fa9c0709e31d2339b1549c4da32ff
|
7
|
+
data.tar.gz: 6a71437092b0ce21cce12a6d127098e2aba2a0b18846026b6df1c9bb1d33f60d27da3ac1c3b553490a2f4daa76a7ae2e725b4f2ee7918d4cc808bc29b556e8f3
|
data/lib/kamal/cli/accessory.rb
CHANGED
@@ -5,11 +5,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
5
5
|
if name == "all"
|
6
6
|
KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
|
7
7
|
else
|
8
|
-
with_accessory(name) do |accessory|
|
8
|
+
with_accessory(name) do |accessory, hosts|
|
9
9
|
directories(name)
|
10
10
|
upload(name)
|
11
11
|
|
12
|
-
on(
|
12
|
+
on(hosts) do
|
13
13
|
execute *KAMAL.registry.login if login
|
14
14
|
execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
15
15
|
execute *accessory.run
|
@@ -22,8 +22,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
22
22
|
desc "upload [NAME]", "Upload accessory files to host", hide: true
|
23
23
|
def upload(name)
|
24
24
|
mutating do
|
25
|
-
with_accessory(name) do |accessory|
|
26
|
-
on(
|
25
|
+
with_accessory(name) do |accessory, hosts|
|
26
|
+
on(hosts) do
|
27
27
|
accessory.files.each do |(local, remote)|
|
28
28
|
accessory.ensure_local_file_present(local)
|
29
29
|
|
@@ -39,8 +39,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
39
39
|
desc "directories [NAME]", "Create accessory directories on host", hide: true
|
40
40
|
def directories(name)
|
41
41
|
mutating do
|
42
|
-
with_accessory(name) do |accessory|
|
43
|
-
on(
|
42
|
+
with_accessory(name) do |accessory, hosts|
|
43
|
+
on(hosts) do
|
44
44
|
accessory.directories.keys.each do |host_path|
|
45
45
|
execute *accessory.make_directory(host_path)
|
46
46
|
end
|
@@ -55,8 +55,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
55
55
|
if name == "all"
|
56
56
|
KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
|
57
57
|
else
|
58
|
-
with_accessory(name) do |accessory|
|
59
|
-
on(
|
58
|
+
with_accessory(name) do |accessory, hosts|
|
59
|
+
on(hosts) do
|
60
60
|
execute *KAMAL.registry.login
|
61
61
|
end
|
62
62
|
|
@@ -71,8 +71,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
71
71
|
desc "start [NAME]", "Start existing accessory container on host"
|
72
72
|
def start(name)
|
73
73
|
mutating do
|
74
|
-
with_accessory(name) do |accessory|
|
75
|
-
on(
|
74
|
+
with_accessory(name) do |accessory, hosts|
|
75
|
+
on(hosts) do
|
76
76
|
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
77
77
|
execute *accessory.start
|
78
78
|
end
|
@@ -83,8 +83,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
83
83
|
desc "stop [NAME]", "Stop existing accessory container on host"
|
84
84
|
def stop(name)
|
85
85
|
mutating do
|
86
|
-
with_accessory(name) do |accessory|
|
87
|
-
on(
|
86
|
+
with_accessory(name) do |accessory, hosts|
|
87
|
+
on(hosts) do
|
88
88
|
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
89
89
|
execute *accessory.stop, raise_on_non_zero_exit: false
|
90
90
|
end
|
@@ -107,8 +107,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
107
107
|
if name == "all"
|
108
108
|
KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
|
109
109
|
else
|
110
|
-
with_accessory(name) do |accessory|
|
111
|
-
on(
|
110
|
+
with_accessory(name) do |accessory, hosts|
|
111
|
+
on(hosts) { puts capture_with_info(*accessory.info) }
|
112
112
|
end
|
113
113
|
end
|
114
114
|
end
|
@@ -117,7 +117,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
117
117
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
118
118
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
119
119
|
def exec(name, cmd)
|
120
|
-
with_accessory(name) do |accessory|
|
120
|
+
with_accessory(name) do |accessory, hosts|
|
121
121
|
case
|
122
122
|
when options[:interactive] && options[:reuse]
|
123
123
|
say "Launching interactive command with via SSH from existing container...", :magenta
|
@@ -129,14 +129,14 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
129
129
|
|
130
130
|
when options[:reuse]
|
131
131
|
say "Launching command from existing container...", :magenta
|
132
|
-
on(
|
132
|
+
on(hosts) do
|
133
133
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
134
134
|
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
135
135
|
end
|
136
136
|
|
137
137
|
else
|
138
138
|
say "Launching command from new container...", :magenta
|
139
|
-
on(
|
139
|
+
on(hosts) do
|
140
140
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
141
141
|
capture_with_info(*accessory.execute_in_new_container(cmd))
|
142
142
|
end
|
@@ -150,12 +150,12 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
150
150
|
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
151
151
|
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
152
152
|
def logs(name)
|
153
|
-
with_accessory(name) do |accessory|
|
153
|
+
with_accessory(name) do |accessory, hosts|
|
154
154
|
grep = options[:grep]
|
155
155
|
|
156
156
|
if options[:follow]
|
157
157
|
run_locally do
|
158
|
-
info "Following logs on #{
|
158
|
+
info "Following logs on #{hosts}..."
|
159
159
|
info accessory.follow_logs(grep: grep)
|
160
160
|
exec accessory.follow_logs(grep: grep)
|
161
161
|
end
|
@@ -163,7 +163,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
163
163
|
since = options[:since]
|
164
164
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
165
165
|
|
166
|
-
on(
|
166
|
+
on(hosts) do
|
167
167
|
puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
|
168
168
|
end
|
169
169
|
end
|
@@ -177,7 +177,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
177
177
|
if name == "all"
|
178
178
|
KAMAL.accessory_names.each { |accessory_name| remove(accessory_name) }
|
179
179
|
else
|
180
|
-
|
180
|
+
confirming "This will remove all containers, images and data directories for #{name}. Are you sure?" do
|
181
181
|
with_accessory(name) do
|
182
182
|
stop(name)
|
183
183
|
remove_container(name)
|
@@ -192,8 +192,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
192
192
|
desc "remove_container [NAME]", "Remove accessory container from host", hide: true
|
193
193
|
def remove_container(name)
|
194
194
|
mutating do
|
195
|
-
with_accessory(name) do |accessory|
|
196
|
-
on(
|
195
|
+
with_accessory(name) do |accessory, hosts|
|
196
|
+
on(hosts) do
|
197
197
|
execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
|
198
198
|
execute *accessory.remove_container
|
199
199
|
end
|
@@ -204,8 +204,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
204
204
|
desc "remove_image [NAME]", "Remove accessory image from host", hide: true
|
205
205
|
def remove_image(name)
|
206
206
|
mutating do
|
207
|
-
with_accessory(name) do |accessory|
|
208
|
-
on(
|
207
|
+
with_accessory(name) do |accessory, hosts|
|
208
|
+
on(hosts) do
|
209
209
|
execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
|
210
210
|
execute *accessory.remove_image
|
211
211
|
end
|
@@ -216,8 +216,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
216
216
|
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
|
217
217
|
def remove_service_directory(name)
|
218
218
|
mutating do
|
219
|
-
with_accessory(name) do |accessory|
|
220
|
-
on(
|
219
|
+
with_accessory(name) do |accessory, hosts|
|
220
|
+
on(hosts) do
|
221
221
|
execute *accessory.remove_service_directory
|
222
222
|
end
|
223
223
|
end
|
@@ -226,8 +226,9 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
226
226
|
|
227
227
|
private
|
228
228
|
def with_accessory(name)
|
229
|
-
if
|
230
|
-
|
229
|
+
if KAMAL.config.accessory(name)
|
230
|
+
accessory = KAMAL.accessory(name)
|
231
|
+
yield accessory, accessory_hosts(accessory)
|
231
232
|
else
|
232
233
|
error_on_missing_accessory(name)
|
233
234
|
end
|
@@ -240,4 +241,12 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|
240
241
|
"No accessory by the name of '#{name}'" +
|
241
242
|
(options ? " (options: #{options.to_sentence})" : "")
|
242
243
|
end
|
244
|
+
|
245
|
+
def accessory_hosts(accessory)
|
246
|
+
if KAMAL.specific_hosts&.any?
|
247
|
+
KAMAL.specific_hosts & accessory.hosts
|
248
|
+
else
|
249
|
+
accessory.hosts
|
250
|
+
end
|
251
|
+
end
|
243
252
|
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
class Kamal::Cli::App::Boot
|
2
|
+
attr_reader :host, :role, :version, :sshkit
|
3
|
+
delegate :execute, :capture_with_info, :info, to: :sshkit
|
4
|
+
delegate :uses_cord?, :assets?, to: :role
|
5
|
+
|
6
|
+
def initialize(host, role, version, sshkit)
|
7
|
+
@host = host
|
8
|
+
@role = role
|
9
|
+
@version = version
|
10
|
+
@sshkit = sshkit
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
old_version = old_version_renamed_if_clashing
|
15
|
+
|
16
|
+
start_new_version
|
17
|
+
|
18
|
+
if old_version
|
19
|
+
stop_old_version(old_version)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
def app
|
25
|
+
@app ||= KAMAL.app(role: role)
|
26
|
+
end
|
27
|
+
|
28
|
+
def auditor
|
29
|
+
@auditor = KAMAL.auditor(role: role)
|
30
|
+
end
|
31
|
+
|
32
|
+
def audit(message)
|
33
|
+
execute *auditor.record(message), verbosity: :debug
|
34
|
+
end
|
35
|
+
|
36
|
+
def old_version_renamed_if_clashing
|
37
|
+
if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
|
38
|
+
renamed_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
39
|
+
info "Renaming container #{version} to #{renamed_version} as already deployed on #{host}"
|
40
|
+
audit("Renaming container #{version} to #{renamed_version}")
|
41
|
+
execute *app.rename_container(version: version, new_version: renamed_version)
|
42
|
+
end
|
43
|
+
|
44
|
+
capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip.presence
|
45
|
+
end
|
46
|
+
|
47
|
+
def start_new_version
|
48
|
+
audit "Booted app version #{version}"
|
49
|
+
execute *app.tie_cord(role.cord_host_file) if uses_cord?
|
50
|
+
execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
|
51
|
+
Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop_old_version(version)
|
55
|
+
if uses_cord?
|
56
|
+
cord = capture_with_info(*app.cord(version: version), raise_on_non_zero_exit: false).strip
|
57
|
+
if cord.present?
|
58
|
+
execute *app.cut_cord(cord)
|
59
|
+
Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
execute *app.stop(version: version), raise_on_non_zero_exit: false
|
64
|
+
|
65
|
+
execute *app.clean_up_assets if assets?
|
66
|
+
end
|
67
|
+
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)
|
23
|
+
end
|
24
|
+
end
|
data/lib/kamal/cli/app.rb
CHANGED
@@ -7,60 +7,23 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
7
7
|
using_version(version_or_latest) do |version|
|
8
8
|
say "Start container with version #{version} using a #{KAMAL.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
|
9
9
|
|
10
|
+
# Assets are prepared in a separate step to ensure they are on all hosts before booting
|
10
11
|
on(KAMAL.hosts) do
|
11
|
-
execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
|
12
|
-
execute *KAMAL.app.tag_current_image_as_latest
|
13
|
-
|
14
12
|
KAMAL.roles_on(host).each do |role|
|
15
|
-
|
16
|
-
role_config = KAMAL.config.role(role)
|
17
|
-
|
18
|
-
if role_config.assets?
|
19
|
-
execute *app.extract_assets
|
20
|
-
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
21
|
-
execute *app.sync_asset_volumes(old_version: old_version)
|
22
|
-
end
|
13
|
+
Kamal::Cli::App::PrepareAssets.new(host, role, self).run
|
23
14
|
end
|
24
15
|
end
|
25
16
|
|
26
17
|
on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
|
27
18
|
KAMAL.roles_on(host).each do |role|
|
28
|
-
|
29
|
-
auditor = KAMAL.auditor(role: role)
|
30
|
-
role_config = KAMAL.config.role(role)
|
31
|
-
|
32
|
-
if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
|
33
|
-
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
34
|
-
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
35
|
-
execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
|
36
|
-
execute *app.rename_container(version: version, new_version: tmp_version)
|
37
|
-
end
|
38
|
-
|
39
|
-
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
40
|
-
|
41
|
-
execute *app.tie_cord(role_config.cord_host_file) if role_config.uses_cord?
|
42
|
-
|
43
|
-
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
44
|
-
|
45
|
-
execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
|
46
|
-
|
47
|
-
Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
48
|
-
|
49
|
-
if old_version.present?
|
50
|
-
if role_config.uses_cord?
|
51
|
-
cord = capture_with_info(*app.cord(version: old_version), raise_on_non_zero_exit: false).strip
|
52
|
-
if cord.present?
|
53
|
-
execute *app.cut_cord(cord)
|
54
|
-
Kamal::Cli::Healthcheck::Poller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: old_version)) }
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
|
59
|
-
|
60
|
-
execute *app.clean_up_assets if role_config.assets?
|
61
|
-
end
|
19
|
+
Kamal::Cli::App::Boot.new(host, role, version, self).run
|
62
20
|
end
|
63
21
|
end
|
22
|
+
|
23
|
+
on(KAMAL.hosts) do |host|
|
24
|
+
execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug
|
25
|
+
execute *KAMAL.app.tag_latest_image
|
26
|
+
end
|
64
27
|
end
|
65
28
|
end
|
66
29
|
end
|
@@ -109,13 +72,15 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
109
72
|
desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
|
110
73
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
111
74
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
75
|
+
option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
|
112
76
|
def exec(cmd)
|
77
|
+
env = options[:env]
|
113
78
|
case
|
114
79
|
when options[:interactive] && options[:reuse]
|
115
80
|
say "Get current version of running container...", :magenta unless options[:version]
|
116
81
|
using_version(options[:version] || current_running_version) do |version|
|
117
82
|
say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta
|
118
|
-
run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host) }
|
83
|
+
run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host, env: env) }
|
119
84
|
end
|
120
85
|
|
121
86
|
when options[:interactive]
|
@@ -123,7 +88,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
123
88
|
using_version(version_or_latest) do |version|
|
124
89
|
say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
|
125
90
|
run_locally do
|
126
|
-
exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host)
|
91
|
+
exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host, env: env)
|
127
92
|
end
|
128
93
|
end
|
129
94
|
|
@@ -137,7 +102,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
137
102
|
|
138
103
|
roles.each do |role|
|
139
104
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
|
140
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd))
|
105
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd, env: env))
|
141
106
|
end
|
142
107
|
end
|
143
108
|
end
|
@@ -151,7 +116,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
151
116
|
|
152
117
|
roles.each do |role|
|
153
118
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
154
|
-
puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd))
|
119
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd, env: env))
|
155
120
|
end
|
156
121
|
end
|
157
122
|
end
|
@@ -175,7 +140,10 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
175
140
|
roles = KAMAL.roles_on(host)
|
176
141
|
|
177
142
|
roles.each do |role|
|
178
|
-
|
143
|
+
versions = capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false).split("\n")
|
144
|
+
versions -= [ capture_with_info(*KAMAL.app(role: role).current_running_version, raise_on_non_zero_exit: false).strip ]
|
145
|
+
|
146
|
+
versions.each do |version|
|
179
147
|
if stop
|
180
148
|
puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
|
181
149
|
execute *KAMAL.app(role: role).stop(version: version), raise_on_non_zero_exit: false
|
@@ -202,19 +170,20 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
202
170
|
# FIXME: Catch when app containers aren't running
|
203
171
|
|
204
172
|
grep = options[:grep]
|
205
|
-
|
173
|
+
since = options[:since]
|
206
174
|
if options[:follow]
|
175
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 10) # Default to 10 lines if since or grep isn't set
|
176
|
+
|
207
177
|
run_locally do
|
208
178
|
info "Following logs on #{KAMAL.primary_host}..."
|
209
179
|
|
210
|
-
KAMAL.specific_roles ||= ["web"]
|
180
|
+
KAMAL.specific_roles ||= [ "web" ]
|
211
181
|
role = KAMAL.roles_on(KAMAL.primary_host).first
|
212
182
|
|
213
|
-
info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
|
214
|
-
exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
|
183
|
+
info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep)
|
184
|
+
exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep)
|
215
185
|
end
|
216
186
|
else
|
217
|
-
since = options[:since]
|
218
187
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
219
188
|
|
220
189
|
on(KAMAL.hosts) do |host|
|
@@ -310,18 +279,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|
310
279
|
version.presence
|
311
280
|
end
|
312
281
|
|
313
|
-
def stale_versions(host:, role:)
|
314
|
-
versions = nil
|
315
|
-
on(host) do
|
316
|
-
versions = \
|
317
|
-
capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false)
|
318
|
-
.split("\n")
|
319
|
-
.drop(1)
|
320
|
-
end
|
321
|
-
versions
|
322
|
-
end
|
323
|
-
|
324
282
|
def version_or_latest
|
325
|
-
options[:version] ||
|
283
|
+
options[:version] || KAMAL.config.latest_tag
|
326
284
|
end
|
327
285
|
end
|
data/lib/kamal/cli/base.rb
CHANGED
@@ -73,7 +73,7 @@ module Kamal::Cli
|
|
73
73
|
def print_runtime
|
74
74
|
started_at = Time.now
|
75
75
|
yield
|
76
|
-
|
76
|
+
Time.now - started_at
|
77
77
|
ensure
|
78
78
|
runtime = Time.now - started_at
|
79
79
|
puts " Finished all in #{sprintf("%.1f seconds", runtime)}"
|
@@ -84,7 +84,7 @@ module Kamal::Cli
|
|
84
84
|
|
85
85
|
run_hook "pre-connect"
|
86
86
|
|
87
|
-
|
87
|
+
ensure_run_and_locks_directory
|
88
88
|
|
89
89
|
acquire_lock
|
90
90
|
|
@@ -103,6 +103,16 @@ module Kamal::Cli
|
|
103
103
|
release_lock
|
104
104
|
end
|
105
105
|
|
106
|
+
def confirming(question)
|
107
|
+
return yield if options[:confirmed]
|
108
|
+
|
109
|
+
if ask(question, limited_to: %w[ y N ], default: "N") == "y"
|
110
|
+
yield
|
111
|
+
else
|
112
|
+
say "Aborted", :red
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
106
116
|
def acquire_lock
|
107
117
|
raise_if_locked do
|
108
118
|
say "Acquiring the deploy lock...", :magenta
|
@@ -123,8 +133,9 @@ module Kamal::Cli
|
|
123
133
|
yield
|
124
134
|
rescue SSHKit::Runner::ExecuteError => e
|
125
135
|
if e.message =~ /cannot create directory/
|
136
|
+
say "Deploy lock already in place!", :red
|
126
137
|
on(KAMAL.primary_host) { puts capture_with_debug(*KAMAL.lock.status) }
|
127
|
-
raise LockError, "Deploy lock found"
|
138
|
+
raise LockError, "Deploy lock found. Run 'kamal lock help' for more information"
|
128
139
|
else
|
129
140
|
raise e
|
130
141
|
end
|
@@ -146,9 +157,9 @@ module Kamal::Cli
|
|
146
157
|
|
147
158
|
say "Running the #{hook} hook...", :magenta
|
148
159
|
run_locally do
|
149
|
-
|
150
|
-
rescue SSHKit::Command::Failed
|
151
|
-
raise HookError.new("Hook `#{hook}` failed")
|
160
|
+
execute *KAMAL.hook.run(hook, **details, **extra_details)
|
161
|
+
rescue SSHKit::Command::Failed => e
|
162
|
+
raise HookError.new("Hook `#{hook}` failed:\n#{e.message}")
|
152
163
|
end
|
153
164
|
end
|
154
165
|
end
|
@@ -175,10 +186,14 @@ module Kamal::Cli
|
|
175
186
|
instance_variable_get("@_invocations").first
|
176
187
|
end
|
177
188
|
|
178
|
-
def
|
189
|
+
def ensure_run_and_locks_directory
|
179
190
|
on(KAMAL.hosts) do
|
180
191
|
execute(*KAMAL.server.ensure_run_directory)
|
181
192
|
end
|
193
|
+
|
194
|
+
on(KAMAL.primary_host) do
|
195
|
+
execute(*KAMAL.lock.ensure_locks_directory)
|
196
|
+
end
|
182
197
|
end
|
183
|
-
|
198
|
+
end
|
184
199
|
end
|
data/lib/kamal/cli/env.rb
CHANGED
@@ -8,22 +8,21 @@ class Kamal::Cli::Env < Kamal::Cli::Base
|
|
8
8
|
execute *KAMAL.auditor.record("Pushed env files"), verbosity: :debug
|
9
9
|
|
10
10
|
KAMAL.roles_on(host).each do |role|
|
11
|
-
role_config = KAMAL.config.role(role)
|
12
11
|
execute *KAMAL.app(role: role).make_env_directory
|
13
|
-
upload!
|
12
|
+
upload! role.env.secrets_io, role.env.secrets_file, mode: 400
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
17
16
|
on(KAMAL.traefik_hosts) do
|
18
17
|
execute *KAMAL.traefik.make_env_directory
|
19
|
-
upload!
|
18
|
+
upload! KAMAL.traefik.env.secrets_io, KAMAL.traefik.env.secrets_file, mode: 400
|
20
19
|
end
|
21
20
|
|
22
21
|
on(KAMAL.accessory_hosts) do
|
23
22
|
KAMAL.accessories_on(host).each do |accessory|
|
24
23
|
accessory_config = KAMAL.config.accessory(accessory)
|
25
24
|
execute *KAMAL.accessory(accessory).make_env_directory
|
26
|
-
upload!
|
25
|
+
upload! accessory_config.env.secrets_io, accessory_config.env.secrets_file, mode: 400
|
27
26
|
end
|
28
27
|
end
|
29
28
|
end
|
@@ -36,7 +35,6 @@ class Kamal::Cli::Env < Kamal::Cli::Base
|
|
36
35
|
execute *KAMAL.auditor.record("Deleted env files"), verbosity: :debug
|
37
36
|
|
38
37
|
KAMAL.roles_on(host).each do |role|
|
39
|
-
role_config = KAMAL.config.role(role)
|
40
38
|
execute *KAMAL.app(role: role).remove_env_file
|
41
39
|
end
|
42
40
|
end
|
data/lib/kamal/cli/main.rb
CHANGED
@@ -1,15 +1,18 @@
|
|
1
1
|
class Kamal::Cli::Main < Kamal::Cli::Base
|
2
2
|
desc "setup", "Setup all accessories, push the env, and deploy app to servers"
|
3
|
+
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
3
4
|
def setup
|
4
5
|
print_runtime do
|
5
6
|
mutating do
|
7
|
+
invoke_options = deploy_options
|
8
|
+
|
6
9
|
say "Ensure Docker is installed...", :magenta
|
7
|
-
invoke "kamal:cli:server:bootstrap"
|
10
|
+
invoke "kamal:cli:server:bootstrap", [], invoke_options
|
8
11
|
|
9
12
|
say "Push env files...", :magenta
|
10
|
-
invoke "kamal:cli:env:push"
|
13
|
+
invoke "kamal:cli:env:push", [], invoke_options
|
11
14
|
|
12
|
-
invoke "kamal:cli:accessory:boot", [ "all" ]
|
15
|
+
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
|
13
16
|
deploy
|
14
17
|
end
|
15
18
|
end
|
@@ -194,7 +197,7 @@ class Kamal::Cli::Main < Kamal::Cli::Base
|
|
194
197
|
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
195
198
|
def remove
|
196
199
|
mutating do
|
197
|
-
|
200
|
+
confirming "This will remove all containers and images. Are you sure?" do
|
198
201
|
invoke "kamal:cli:traefik:remove", [], options.without(:confirmed)
|
199
202
|
invoke "kamal:cli:app:remove", [], options.without(:confirmed)
|
200
203
|
invoke "kamal:cli:accessory:remove", [ "all" ], options
|
data/lib/kamal/cli/prune.rb
CHANGED
@@ -18,12 +18,16 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
desc "containers", "Prune all stopped containers, except the last 5"
|
21
|
+
desc "containers", "Prune all stopped containers, except the last n (default 5)"
|
22
|
+
option :retain, type: :numeric, default: nil, desc: "Number of containers to retain"
|
22
23
|
def containers
|
24
|
+
retain = options.fetch(:retain, KAMAL.config.retain_containers)
|
25
|
+
raise "retain must be at least 1" if retain < 1
|
26
|
+
|
23
27
|
mutating do
|
24
28
|
on(KAMAL.hosts) do
|
25
29
|
execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
|
26
|
-
execute *KAMAL.prune.app_containers
|
30
|
+
execute *KAMAL.prune.app_containers(retain: retain)
|
27
31
|
execute *KAMAL.prune.healthcheck_containers
|
28
32
|
end
|
29
33
|
end
|
data/lib/kamal/cli/server.rb
CHANGED
@@ -17,7 +17,9 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|
17
17
|
end
|
18
18
|
|
19
19
|
if missing.any?
|
20
|
-
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and
|
20
|
+
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and either `wget` or `curl`. Install Docker manually: https://docs.docker.com/engine/install/"
|
21
21
|
end
|
22
|
+
|
23
|
+
run_hook "docker-setup"
|
22
24
|
end
|
23
25
|
end
|
@@ -63,7 +63,7 @@ registry:
|
|
63
63
|
# directories:
|
64
64
|
# - data:/data
|
65
65
|
|
66
|
-
# Configure custom arguments for Traefik
|
66
|
+
# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
|
67
67
|
# traefik:
|
68
68
|
# args:
|
69
69
|
# accesslog: true
|
@@ -77,6 +77,10 @@ registry:
|
|
77
77
|
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
78
78
|
# hitting 404 on in-flight requests. Combines all files from new and old
|
79
79
|
# version inside the asset_path.
|
80
|
+
#
|
81
|
+
# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
|
82
|
+
# See https://github.com/basecamp/kamal/issues/626 for details
|
83
|
+
#
|
80
84
|
# asset_path: /rails/public/assets
|
81
85
|
|
82
86
|
# Configure rolling deploys by setting a wait time between batches of restarts.
|