kamal 1.3.1 → 1.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|