kamal 0.16.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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +1021 -0
- data/bin/kamal +18 -0
- data/lib/kamal/cli/accessory.rb +239 -0
- data/lib/kamal/cli/app.rb +296 -0
- data/lib/kamal/cli/base.rb +171 -0
- data/lib/kamal/cli/build.rb +106 -0
- data/lib/kamal/cli/healthcheck.rb +20 -0
- data/lib/kamal/cli/lock.rb +37 -0
- data/lib/kamal/cli/main.rb +249 -0
- data/lib/kamal/cli/prune.rb +30 -0
- data/lib/kamal/cli/registry.rb +18 -0
- data/lib/kamal/cli/server.rb +21 -0
- data/lib/kamal/cli/templates/deploy.yml +74 -0
- data/lib/kamal/cli/templates/sample_hooks/post-deploy.sample +14 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-build.sample +51 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-connect.sample +47 -0
- data/lib/kamal/cli/templates/sample_hooks/pre-deploy.sample +109 -0
- data/lib/kamal/cli/templates/template.env +2 -0
- data/lib/kamal/cli/traefik.rb +111 -0
- data/lib/kamal/cli.rb +7 -0
- data/lib/kamal/commander.rb +154 -0
- data/lib/kamal/commands/accessory.rb +113 -0
- data/lib/kamal/commands/app.rb +175 -0
- data/lib/kamal/commands/auditor.rb +28 -0
- data/lib/kamal/commands/base.rb +65 -0
- data/lib/kamal/commands/builder/base.rb +60 -0
- data/lib/kamal/commands/builder/multiarch/remote.rb +51 -0
- data/lib/kamal/commands/builder/multiarch.rb +29 -0
- data/lib/kamal/commands/builder/native/cached.rb +16 -0
- data/lib/kamal/commands/builder/native/remote.rb +59 -0
- data/lib/kamal/commands/builder/native.rb +20 -0
- data/lib/kamal/commands/builder.rb +62 -0
- data/lib/kamal/commands/docker.rb +21 -0
- data/lib/kamal/commands/healthcheck.rb +57 -0
- data/lib/kamal/commands/hook.rb +14 -0
- data/lib/kamal/commands/lock.rb +63 -0
- data/lib/kamal/commands/prune.rb +38 -0
- data/lib/kamal/commands/registry.rb +20 -0
- data/lib/kamal/commands/traefik.rb +104 -0
- data/lib/kamal/commands.rb +2 -0
- data/lib/kamal/configuration/accessory.rb +169 -0
- data/lib/kamal/configuration/boot.rb +20 -0
- data/lib/kamal/configuration/builder.rb +114 -0
- data/lib/kamal/configuration/role.rb +155 -0
- data/lib/kamal/configuration/ssh.rb +38 -0
- data/lib/kamal/configuration/sshkit.rb +20 -0
- data/lib/kamal/configuration.rb +251 -0
- data/lib/kamal/sshkit_with_ext.rb +104 -0
- data/lib/kamal/tags.rb +39 -0
- data/lib/kamal/utils/healthcheck_poller.rb +39 -0
- data/lib/kamal/utils/sensitive.rb +19 -0
- data/lib/kamal/utils.rb +100 -0
- data/lib/kamal/version.rb +3 -0
- data/lib/kamal.rb +10 -0
- metadata +266 -0
data/bin/kamal
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# Prevent failures from being reported twice.
|
4
|
+
Thread.report_on_exception = false
|
5
|
+
|
6
|
+
require "kamal"
|
7
|
+
|
8
|
+
begin
|
9
|
+
Kamal::Cli::Main.start(ARGV)
|
10
|
+
rescue SSHKit::Runner::ExecuteError => e
|
11
|
+
puts " \e[31mERROR (#{e.cause.class}): #{e.message}\e[0m"
|
12
|
+
puts e.cause.backtrace if ENV["VERBOSE"]
|
13
|
+
exit 1
|
14
|
+
rescue => e
|
15
|
+
puts " \e[31mERROR (#{e.class}): #{e.message}\e[0m"
|
16
|
+
puts e.backtrace if ENV["VERBOSE"]
|
17
|
+
exit 1
|
18
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
class Kamal::Cli::Accessory < Kamal::Cli::Base
|
2
|
+
desc "boot [NAME]", "Boot new accessory service on host (use NAME=all to boot all accessories)"
|
3
|
+
def boot(name, login: true)
|
4
|
+
mutating do
|
5
|
+
if name == "all"
|
6
|
+
KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
|
7
|
+
else
|
8
|
+
with_accessory(name) do |accessory|
|
9
|
+
directories(name)
|
10
|
+
upload(name)
|
11
|
+
|
12
|
+
on(accessory.hosts) do
|
13
|
+
execute *KAMAL.registry.login if login
|
14
|
+
execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
15
|
+
execute *accessory.run
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
desc "upload [NAME]", "Upload accessory files to host", hide: true
|
23
|
+
def upload(name)
|
24
|
+
mutating do
|
25
|
+
with_accessory(name) do |accessory|
|
26
|
+
on(accessory.hosts) do
|
27
|
+
accessory.files.each do |(local, remote)|
|
28
|
+
accessory.ensure_local_file_present(local)
|
29
|
+
|
30
|
+
execute *accessory.make_directory_for(remote)
|
31
|
+
upload! local, remote
|
32
|
+
execute :chmod, "755", remote
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
desc "directories [NAME]", "Create accessory directories on host", hide: true
|
40
|
+
def directories(name)
|
41
|
+
mutating do
|
42
|
+
with_accessory(name) do |accessory|
|
43
|
+
on(accessory.hosts) do
|
44
|
+
accessory.directories.keys.each do |host_path|
|
45
|
+
execute *accessory.make_directory(host_path)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc "reboot [NAME]", "Reboot existing accessory on host (stop container, remove container, start new container)"
|
53
|
+
def reboot(name)
|
54
|
+
mutating do
|
55
|
+
with_accessory(name) do |accessory|
|
56
|
+
on(accessory.hosts) do
|
57
|
+
execute *KAMAL.registry.login
|
58
|
+
end
|
59
|
+
|
60
|
+
stop(name)
|
61
|
+
remove_container(name)
|
62
|
+
boot(name, login: false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
desc "start [NAME]", "Start existing accessory container on host"
|
68
|
+
def start(name)
|
69
|
+
mutating do
|
70
|
+
with_accessory(name) do |accessory|
|
71
|
+
on(accessory.hosts) do
|
72
|
+
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
73
|
+
execute *accessory.start
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
desc "stop [NAME]", "Stop existing accessory container on host"
|
80
|
+
def stop(name)
|
81
|
+
mutating do
|
82
|
+
with_accessory(name) do |accessory|
|
83
|
+
on(accessory.hosts) do
|
84
|
+
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
85
|
+
execute *accessory.stop, raise_on_non_zero_exit: false
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
desc "restart [NAME]", "Restart existing accessory container on host"
|
92
|
+
def restart(name)
|
93
|
+
mutating do
|
94
|
+
with_accessory(name) do
|
95
|
+
stop(name)
|
96
|
+
start(name)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
desc "details [NAME]", "Show details about accessory on host (use NAME=all to show all accessories)"
|
102
|
+
def details(name)
|
103
|
+
if name == "all"
|
104
|
+
KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
|
105
|
+
else
|
106
|
+
with_accessory(name) do |accessory|
|
107
|
+
on(accessory.hosts) { puts capture_with_info(*accessory.info) }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
desc "exec [NAME] [CMD]", "Execute a custom command on servers (use --help to show options)"
|
113
|
+
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
114
|
+
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
115
|
+
def exec(name, cmd)
|
116
|
+
with_accessory(name) do |accessory|
|
117
|
+
case
|
118
|
+
when options[:interactive] && options[:reuse]
|
119
|
+
say "Launching interactive command with via SSH from existing container...", :magenta
|
120
|
+
run_locally { exec accessory.execute_in_existing_container_over_ssh(cmd) }
|
121
|
+
|
122
|
+
when options[:interactive]
|
123
|
+
say "Launching interactive command via SSH from new container...", :magenta
|
124
|
+
run_locally { exec accessory.execute_in_new_container_over_ssh(cmd) }
|
125
|
+
|
126
|
+
when options[:reuse]
|
127
|
+
say "Launching command from existing container...", :magenta
|
128
|
+
on(accessory.hosts) do
|
129
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
130
|
+
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
131
|
+
end
|
132
|
+
|
133
|
+
else
|
134
|
+
say "Launching command from new container...", :magenta
|
135
|
+
on(accessory.hosts) do
|
136
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
137
|
+
capture_with_info(*accessory.execute_in_new_container(cmd))
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
desc "logs [NAME]", "Show log lines from accessory on host (use --help to show options)"
|
144
|
+
option :since, aliases: "-s", desc: "Show logs since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
145
|
+
option :lines, type: :numeric, aliases: "-n", desc: "Number of log lines to pull from each server"
|
146
|
+
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
147
|
+
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
148
|
+
def logs(name)
|
149
|
+
with_accessory(name) do |accessory|
|
150
|
+
grep = options[:grep]
|
151
|
+
|
152
|
+
if options[:follow]
|
153
|
+
run_locally do
|
154
|
+
info "Following logs on #{accessory.hosts}..."
|
155
|
+
info accessory.follow_logs(grep: grep)
|
156
|
+
exec accessory.follow_logs(grep: grep)
|
157
|
+
end
|
158
|
+
else
|
159
|
+
since = options[:since]
|
160
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
161
|
+
|
162
|
+
on(accessory.hosts) do
|
163
|
+
puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
desc "remove [NAME]", "Remove accessory container, image and data directory from host (use NAME=all to remove all accessories)"
|
170
|
+
option :confirmed, aliases: "-y", type: :boolean, default: false, desc: "Proceed without confirmation question"
|
171
|
+
def remove(name)
|
172
|
+
mutating do
|
173
|
+
if name == "all"
|
174
|
+
KAMAL.accessory_names.each { |accessory_name| remove(accessory_name) }
|
175
|
+
else
|
176
|
+
if options[:confirmed] || ask("This will remove all containers, images and data directories for #{name}. Are you sure?", limited_to: %w( y N ), default: "N") == "y"
|
177
|
+
with_accessory(name) do
|
178
|
+
stop(name)
|
179
|
+
remove_container(name)
|
180
|
+
remove_image(name)
|
181
|
+
remove_service_directory(name)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
desc "remove_container [NAME]", "Remove accessory container from host", hide: true
|
189
|
+
def remove_container(name)
|
190
|
+
mutating do
|
191
|
+
with_accessory(name) do |accessory|
|
192
|
+
on(accessory.hosts) do
|
193
|
+
execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
|
194
|
+
execute *accessory.remove_container
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
desc "remove_image [NAME]", "Remove accessory image from host", hide: true
|
201
|
+
def remove_image(name)
|
202
|
+
mutating do
|
203
|
+
with_accessory(name) do |accessory|
|
204
|
+
on(accessory.hosts) do
|
205
|
+
execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
|
206
|
+
execute *accessory.remove_image
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
|
213
|
+
def remove_service_directory(name)
|
214
|
+
mutating do
|
215
|
+
with_accessory(name) do |accessory|
|
216
|
+
on(accessory.hosts) do
|
217
|
+
execute *accessory.remove_service_directory
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
private
|
224
|
+
def with_accessory(name)
|
225
|
+
if accessory = KAMAL.accessory(name)
|
226
|
+
yield accessory
|
227
|
+
else
|
228
|
+
error_on_missing_accessory(name)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
def error_on_missing_accessory(name)
|
233
|
+
options = KAMAL.accessory_names.presence
|
234
|
+
|
235
|
+
error \
|
236
|
+
"No accessory by the name of '#{name}'" +
|
237
|
+
(options ? " (options: #{options.to_sentence})" : "")
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,296 @@
|
|
1
|
+
class Kamal::Cli::App < Kamal::Cli::Base
|
2
|
+
desc "boot", "Boot app on servers (or reboot app if already running)"
|
3
|
+
def boot
|
4
|
+
mutating do
|
5
|
+
hold_lock_on_error do
|
6
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
7
|
+
using_version(version_or_latest) do |version|
|
8
|
+
say "Start container with version #{version} using a #{KAMAL.config.readiness_delay}s readiness delay (or reboot if already running)...", :magenta
|
9
|
+
|
10
|
+
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_as_latest
|
13
|
+
end
|
14
|
+
|
15
|
+
on(KAMAL.hosts, **KAMAL.boot_strategy) do |host|
|
16
|
+
roles = KAMAL.roles_on(host)
|
17
|
+
|
18
|
+
roles.each do |role|
|
19
|
+
app = KAMAL.app(role: role)
|
20
|
+
auditor = KAMAL.auditor(role: role)
|
21
|
+
|
22
|
+
if capture_with_info(*app.container_id_for_version(version, only_running: true), raise_on_non_zero_exit: false).present?
|
23
|
+
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
24
|
+
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
25
|
+
execute *auditor.record("Renaming container #{version} to #{tmp_version}"), verbosity: :debug
|
26
|
+
execute *app.rename_container(version: version, new_version: tmp_version)
|
27
|
+
end
|
28
|
+
|
29
|
+
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
30
|
+
|
31
|
+
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
32
|
+
execute *app.start_or_run(hostname: "#{host}-#{SecureRandom.hex(6)}")
|
33
|
+
|
34
|
+
Kamal::Utils::HealthcheckPoller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
35
|
+
|
36
|
+
execute *app.stop(version: old_version), raise_on_non_zero_exit: false if old_version.present?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
desc "start", "Start existing app container on servers"
|
45
|
+
def start
|
46
|
+
mutating do
|
47
|
+
on(KAMAL.hosts) do |host|
|
48
|
+
roles = KAMAL.roles_on(host)
|
49
|
+
|
50
|
+
roles.each do |role|
|
51
|
+
execute *KAMAL.auditor.record("Started app version #{KAMAL.config.version}"), verbosity: :debug
|
52
|
+
execute *KAMAL.app(role: role).start, raise_on_non_zero_exit: false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
desc "stop", "Stop app container on servers"
|
59
|
+
def stop
|
60
|
+
mutating do
|
61
|
+
on(KAMAL.hosts) do |host|
|
62
|
+
roles = KAMAL.roles_on(host)
|
63
|
+
|
64
|
+
roles.each do |role|
|
65
|
+
execute *KAMAL.auditor.record("Stopped app", role: role), verbosity: :debug
|
66
|
+
execute *KAMAL.app(role: role).stop, raise_on_non_zero_exit: false
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# FIXME: Drop in favor of just containers?
|
73
|
+
desc "details", "Show details about app containers"
|
74
|
+
def details
|
75
|
+
on(KAMAL.hosts) do |host|
|
76
|
+
roles = KAMAL.roles_on(host)
|
77
|
+
|
78
|
+
roles.each do |role|
|
79
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role).info)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)"
|
85
|
+
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
86
|
+
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
87
|
+
def exec(cmd)
|
88
|
+
case
|
89
|
+
when options[:interactive] && options[:reuse]
|
90
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
91
|
+
using_version(options[:version] || current_running_version) do |version|
|
92
|
+
say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta
|
93
|
+
run_locally { exec KAMAL.app(role: "web").execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host) }
|
94
|
+
end
|
95
|
+
|
96
|
+
when options[:interactive]
|
97
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
98
|
+
using_version(version_or_latest) do |version|
|
99
|
+
say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta
|
100
|
+
run_locally { exec KAMAL.app(role: KAMAL.primary_host.roles.first).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host) }
|
101
|
+
end
|
102
|
+
|
103
|
+
when options[:reuse]
|
104
|
+
say "Get current version of running container...", :magenta unless options[:version]
|
105
|
+
using_version(options[:version] || current_running_version) do |version|
|
106
|
+
say "Launching command with version #{version} from existing container...", :magenta
|
107
|
+
|
108
|
+
on(KAMAL.hosts) do |host|
|
109
|
+
roles = KAMAL.roles_on(host)
|
110
|
+
|
111
|
+
roles.each do |role|
|
112
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug
|
113
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
else
|
119
|
+
say "Get most recent version available as an image...", :magenta unless options[:version]
|
120
|
+
using_version(version_or_latest) do |version|
|
121
|
+
say "Launching command with version #{version} from new container...", :magenta
|
122
|
+
on(KAMAL.hosts) do |host|
|
123
|
+
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug
|
124
|
+
puts_by_host host, capture_with_info(*KAMAL.app.execute_in_new_container(cmd))
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
desc "containers", "Show app containers on servers"
|
131
|
+
def containers
|
132
|
+
on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_containers) }
|
133
|
+
end
|
134
|
+
|
135
|
+
desc "stale_containers", "Detect app stale containers"
|
136
|
+
option :stop, aliases: "-s", type: :boolean, default: false, desc: "Stop the stale containers found"
|
137
|
+
def stale_containers
|
138
|
+
mutating do
|
139
|
+
stop = options[:stop]
|
140
|
+
|
141
|
+
cli = self
|
142
|
+
|
143
|
+
on(KAMAL.hosts) do |host|
|
144
|
+
roles = KAMAL.roles_on(host)
|
145
|
+
|
146
|
+
roles.each do |role|
|
147
|
+
cli.send(:stale_versions, host: host, role: role).each do |version|
|
148
|
+
if stop
|
149
|
+
puts_by_host host, "Stopping stale container for role #{role} with version #{version}"
|
150
|
+
execute *KAMAL.app(role: role).stop(version: version), raise_on_non_zero_exit: false
|
151
|
+
else
|
152
|
+
puts_by_host host, "Detected stale container for role #{role} with version #{version} (use `kamal app stale_containers --stop` to stop)"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
desc "images", "Show app images on servers"
|
161
|
+
def images
|
162
|
+
on(KAMAL.hosts) { |host| puts_by_host host, capture_with_info(*KAMAL.app.list_images) }
|
163
|
+
end
|
164
|
+
|
165
|
+
desc "logs", "Show log lines from app on servers (use --help to show options)"
|
166
|
+
option :since, aliases: "-s", desc: "Show lines since timestamp (e.g. 2013-01-02T13:23:37Z) or relative (e.g. 42m for 42 minutes)"
|
167
|
+
option :lines, type: :numeric, aliases: "-n", desc: "Number of lines to show from each server"
|
168
|
+
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
169
|
+
option :follow, aliases: "-f", desc: "Follow log on primary server (or specific host set by --hosts)"
|
170
|
+
def logs
|
171
|
+
# FIXME: Catch when app containers aren't running
|
172
|
+
|
173
|
+
grep = options[:grep]
|
174
|
+
|
175
|
+
if options[:follow]
|
176
|
+
run_locally do
|
177
|
+
info "Following logs on #{KAMAL.primary_host}..."
|
178
|
+
|
179
|
+
KAMAL.specific_roles ||= ["web"]
|
180
|
+
role = KAMAL.roles_on(KAMAL.primary_host).first
|
181
|
+
|
182
|
+
info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
|
183
|
+
exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
|
184
|
+
end
|
185
|
+
else
|
186
|
+
since = options[:since]
|
187
|
+
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
188
|
+
|
189
|
+
on(KAMAL.hosts) do |host|
|
190
|
+
roles = KAMAL.roles_on(host)
|
191
|
+
|
192
|
+
roles.each do |role|
|
193
|
+
begin
|
194
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role).logs(since: since, lines: lines, grep: grep))
|
195
|
+
rescue SSHKit::Command::Failed
|
196
|
+
puts_by_host host, "Nothing found"
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
desc "remove", "Remove app containers and images from servers"
|
204
|
+
def remove
|
205
|
+
mutating do
|
206
|
+
stop
|
207
|
+
remove_containers
|
208
|
+
remove_images
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
desc "remove_container [VERSION]", "Remove app container with given version from servers", hide: true
|
213
|
+
def remove_container(version)
|
214
|
+
mutating do
|
215
|
+
on(KAMAL.hosts) do |host|
|
216
|
+
roles = KAMAL.roles_on(host)
|
217
|
+
|
218
|
+
roles.each do |role|
|
219
|
+
execute *KAMAL.auditor.record("Removed app container with version #{version}", role: role), verbosity: :debug
|
220
|
+
execute *KAMAL.app(role: role).remove_container(version: version)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
desc "remove_containers", "Remove all app containers from servers", hide: true
|
227
|
+
def remove_containers
|
228
|
+
mutating do
|
229
|
+
on(KAMAL.hosts) do |host|
|
230
|
+
roles = KAMAL.roles_on(host)
|
231
|
+
|
232
|
+
roles.each do |role|
|
233
|
+
execute *KAMAL.auditor.record("Removed all app containers", role: role), verbosity: :debug
|
234
|
+
execute *KAMAL.app(role: role).remove_containers
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
|
240
|
+
desc "remove_images", "Remove all app images from servers", hide: true
|
241
|
+
def remove_images
|
242
|
+
mutating do
|
243
|
+
on(KAMAL.hosts) do
|
244
|
+
execute *KAMAL.auditor.record("Removed all app images"), verbosity: :debug
|
245
|
+
execute *KAMAL.app.remove_images
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
desc "version", "Show app version currently running on servers"
|
251
|
+
def version
|
252
|
+
on(KAMAL.hosts) do |host|
|
253
|
+
role = KAMAL.roles_on(host).first
|
254
|
+
puts_by_host host, capture_with_info(*KAMAL.app(role: role).current_running_version).strip
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
private
|
259
|
+
def using_version(new_version)
|
260
|
+
if new_version
|
261
|
+
begin
|
262
|
+
old_version = KAMAL.config.version
|
263
|
+
KAMAL.config.version = new_version
|
264
|
+
yield new_version
|
265
|
+
ensure
|
266
|
+
KAMAL.config.version = old_version
|
267
|
+
end
|
268
|
+
else
|
269
|
+
yield KAMAL.config.version
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
def current_running_version(host: KAMAL.primary_host)
|
274
|
+
version = nil
|
275
|
+
on(host) do
|
276
|
+
role = KAMAL.roles_on(host).first
|
277
|
+
version = capture_with_info(*KAMAL.app(role: role).current_running_version).strip
|
278
|
+
end
|
279
|
+
version.presence
|
280
|
+
end
|
281
|
+
|
282
|
+
def stale_versions(host:, role:)
|
283
|
+
versions = nil
|
284
|
+
on(host) do
|
285
|
+
versions = \
|
286
|
+
capture_with_info(*KAMAL.app(role: role).list_versions, raise_on_non_zero_exit: false)
|
287
|
+
.split("\n")
|
288
|
+
.drop(1)
|
289
|
+
end
|
290
|
+
versions
|
291
|
+
end
|
292
|
+
|
293
|
+
def version_or_latest
|
294
|
+
options[:version] || "latest"
|
295
|
+
end
|
296
|
+
end
|