Compare commits
61 Commits
concurrent
...
v1.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c985fa33d1 | ||
|
|
e8b9f8907f | ||
|
|
4966d52919 | ||
|
|
52bb40add0 | ||
|
|
73a9276cdd | ||
|
|
8c0784ed4a | ||
|
|
089a2d3bba | ||
|
|
bd76d23916 | ||
|
|
fa37fcd10c | ||
|
|
f5dc0858b0 | ||
|
|
9dddb140b1 | ||
|
|
26b1d57c90 | ||
|
|
b94199415f | ||
|
|
f69c45b7ea | ||
|
|
32a2ae5b2c | ||
|
|
37544a6383 | ||
|
|
a1bc6d61af | ||
|
|
5c32be10f1 | ||
|
|
dc5af03593 | ||
|
|
1abd029ea0 | ||
|
|
c4d0d3e5eb | ||
|
|
46e7cf8e78 | ||
|
|
c7cfc074b6 | ||
|
|
c10f43e365 | ||
|
|
8e2184d65e | ||
|
|
2be397b679 | ||
|
|
cc8c508556 | ||
|
|
3b16e047c5 | ||
|
|
6563393d9a | ||
|
|
91f350fcce | ||
|
|
e4e9664049 | ||
|
|
1acef5221f | ||
|
|
788a57e85e | ||
|
|
f9a934a01f | ||
|
|
f286fdc374 | ||
|
|
828cca322b | ||
|
|
cb030e8751 | ||
|
|
6892abb4be | ||
|
|
bcfd0ca88a | ||
|
|
2e8071a5b3 | ||
|
|
200e2686fd | ||
|
|
db94789dc1 | ||
|
|
2bffc3bc74 | ||
|
|
064ace0598 | ||
|
|
a02af74dda | ||
|
|
5ef384d666 | ||
|
|
b94dfe193b | ||
|
|
bc6c027315 | ||
|
|
1c2a45817a | ||
|
|
b411356409 | ||
|
|
77e72e34ce | ||
|
|
ad04bb7556 | ||
|
|
1ec69d3764 | ||
|
|
2d1a0dc9ba | ||
|
|
c984db152f | ||
|
|
0d709a3fdb | ||
|
|
414d29ae4e | ||
|
|
f8d8319c2f | ||
|
|
f6a9d54902 | ||
|
|
b2fd5744fb | ||
|
|
cbd99306eb |
@@ -1,7 +1,7 @@
|
|||||||
PATH
|
PATH
|
||||||
remote: .
|
remote: .
|
||||||
specs:
|
specs:
|
||||||
kamal (1.3.1)
|
kamal (1.4.0)
|
||||||
activesupport (>= 7.0)
|
activesupport (>= 7.0)
|
||||||
base64 (~> 0.2)
|
base64 (~> 0.2)
|
||||||
bcrypt_pbkdf (~> 1.0)
|
bcrypt_pbkdf (~> 1.0)
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
if name == "all"
|
if name == "all"
|
||||||
KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
|
KAMAL.accessory_names.each { |accessory_name| boot(accessory_name) }
|
||||||
else
|
else
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
directories(name)
|
directories(name)
|
||||||
upload(name)
|
upload(name)
|
||||||
|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.registry.login if login
|
execute *KAMAL.registry.login if login
|
||||||
execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Booted #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.run
|
execute *accessory.run
|
||||||
@@ -22,8 +22,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
desc "upload [NAME]", "Upload accessory files to host", hide: true
|
desc "upload [NAME]", "Upload accessory files to host", hide: true
|
||||||
def upload(name)
|
def upload(name)
|
||||||
mutating do
|
mutating do
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
accessory.files.each do |(local, remote)|
|
accessory.files.each do |(local, remote)|
|
||||||
accessory.ensure_local_file_present(local)
|
accessory.ensure_local_file_present(local)
|
||||||
|
|
||||||
@@ -39,8 +39,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
desc "directories [NAME]", "Create accessory directories on host", hide: true
|
desc "directories [NAME]", "Create accessory directories on host", hide: true
|
||||||
def directories(name)
|
def directories(name)
|
||||||
mutating do
|
mutating do
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
accessory.directories.keys.each do |host_path|
|
accessory.directories.keys.each do |host_path|
|
||||||
execute *accessory.make_directory(host_path)
|
execute *accessory.make_directory(host_path)
|
||||||
end
|
end
|
||||||
@@ -55,8 +55,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
if name == "all"
|
if name == "all"
|
||||||
KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
|
KAMAL.accessory_names.each { |accessory_name| reboot(accessory_name) }
|
||||||
else
|
else
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.registry.login
|
execute *KAMAL.registry.login
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -71,8 +71,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
desc "start [NAME]", "Start existing accessory container on host"
|
desc "start [NAME]", "Start existing accessory container on host"
|
||||||
def start(name)
|
def start(name)
|
||||||
mutating do
|
mutating do
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Started #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.start
|
execute *accessory.start
|
||||||
end
|
end
|
||||||
@@ -83,8 +83,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
desc "stop [NAME]", "Stop existing accessory container on host"
|
desc "stop [NAME]", "Stop existing accessory container on host"
|
||||||
def stop(name)
|
def stop(name)
|
||||||
mutating do
|
mutating do
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Stopped #{name} accessory"), verbosity: :debug
|
||||||
execute *accessory.stop, raise_on_non_zero_exit: false
|
execute *accessory.stop, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
@@ -107,8 +107,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
if name == "all"
|
if name == "all"
|
||||||
KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
|
KAMAL.accessory_names.each { |accessory_name| details(accessory_name) }
|
||||||
else
|
else
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) { puts capture_with_info(*accessory.info) }
|
on(hosts) { puts capture_with_info(*accessory.info) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -117,7 +117,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
option :interactive, aliases: "-i", type: :boolean, default: false, desc: "Execute command over ssh for an interactive shell (use for console/bash)"
|
||||||
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
option :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one"
|
||||||
def exec(name, cmd)
|
def exec(name, cmd)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
case
|
case
|
||||||
when options[:interactive] && options[:reuse]
|
when options[:interactive] && options[:reuse]
|
||||||
say "Launching interactive command with via SSH from existing container...", :magenta
|
say "Launching interactive command with via SSH from existing container...", :magenta
|
||||||
@@ -129,14 +129,14 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
|
|
||||||
when options[:reuse]
|
when options[:reuse]
|
||||||
say "Launching command from existing container...", :magenta
|
say "Launching command from existing container...", :magenta
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
||||||
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
capture_with_info(*accessory.execute_in_existing_container(cmd))
|
||||||
end
|
end
|
||||||
|
|
||||||
else
|
else
|
||||||
say "Launching command from new container...", :magenta
|
say "Launching command from new container...", :magenta
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on #{name} accessory"), verbosity: :debug
|
||||||
capture_with_info(*accessory.execute_in_new_container(cmd))
|
capture_with_info(*accessory.execute_in_new_container(cmd))
|
||||||
end
|
end
|
||||||
@@ -150,12 +150,12 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
option :grep, aliases: "-g", desc: "Show lines with grep match only (use this to fetch specific requests by id)"
|
||||||
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
option :follow, aliases: "-f", desc: "Follow logs on primary server (or specific host set by --hosts)"
|
||||||
def logs(name)
|
def logs(name)
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
grep = options[:grep]
|
grep = options[:grep]
|
||||||
|
|
||||||
if options[:follow]
|
if options[:follow]
|
||||||
run_locally do
|
run_locally do
|
||||||
info "Following logs on #{accessory.hosts}..."
|
info "Following logs on #{hosts}..."
|
||||||
info accessory.follow_logs(grep: grep)
|
info accessory.follow_logs(grep: grep)
|
||||||
exec accessory.follow_logs(grep: grep)
|
exec accessory.follow_logs(grep: grep)
|
||||||
end
|
end
|
||||||
@@ -163,7 +163,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
since = options[:since]
|
since = options[:since]
|
||||||
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
||||||
|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
|
puts capture_with_info(*accessory.logs(since: since, lines: lines, grep: grep))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -192,8 +192,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
desc "remove_container [NAME]", "Remove accessory container from host", hide: true
|
desc "remove_container [NAME]", "Remove accessory container from host", hide: true
|
||||||
def remove_container(name)
|
def remove_container(name)
|
||||||
mutating do
|
mutating do
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
|
execute *KAMAL.auditor.record("Remove #{name} accessory container"), verbosity: :debug
|
||||||
execute *accessory.remove_container
|
execute *accessory.remove_container
|
||||||
end
|
end
|
||||||
@@ -204,8 +204,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
desc "remove_image [NAME]", "Remove accessory image from host", hide: true
|
desc "remove_image [NAME]", "Remove accessory image from host", hide: true
|
||||||
def remove_image(name)
|
def remove_image(name)
|
||||||
mutating do
|
mutating do
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
|
execute *KAMAL.auditor.record("Removed #{name} accessory image"), verbosity: :debug
|
||||||
execute *accessory.remove_image
|
execute *accessory.remove_image
|
||||||
end
|
end
|
||||||
@@ -216,8 +216,8 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
|
desc "remove_service_directory [NAME]", "Remove accessory directory used for uploaded files and data directories from host", hide: true
|
||||||
def remove_service_directory(name)
|
def remove_service_directory(name)
|
||||||
mutating do
|
mutating do
|
||||||
with_accessory(name) do |accessory|
|
with_accessory(name) do |accessory, hosts|
|
||||||
on(accessory.hosts) do
|
on(hosts) do
|
||||||
execute *accessory.remove_service_directory
|
execute *accessory.remove_service_directory
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -227,7 +227,7 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
private
|
private
|
||||||
def with_accessory(name)
|
def with_accessory(name)
|
||||||
if accessory = KAMAL.accessory(name)
|
if accessory = KAMAL.accessory(name)
|
||||||
yield accessory
|
yield accessory, accessory_hosts(accessory)
|
||||||
else
|
else
|
||||||
error_on_missing_accessory(name)
|
error_on_missing_accessory(name)
|
||||||
end
|
end
|
||||||
@@ -240,4 +240,12 @@ class Kamal::Cli::Accessory < Kamal::Cli::Base
|
|||||||
"No accessory by the name of '#{name}'" +
|
"No accessory by the name of '#{name}'" +
|
||||||
(options ? " (options: #{options.to_sentence})" : "")
|
(options ? " (options: #{options.to_sentence})" : "")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accessory_hosts(accessory)
|
||||||
|
if KAMAL.specific_hosts&.any?
|
||||||
|
KAMAL.specific_hosts & accessory.hosts
|
||||||
|
else
|
||||||
|
accessory.hosts
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
|
|
||||||
KAMAL.roles_on(host).each do |role|
|
KAMAL.roles_on(host).each do |role|
|
||||||
app = KAMAL.app(role: role)
|
app = KAMAL.app(role: role)
|
||||||
role_config = KAMAL.config.role(role)
|
|
||||||
|
|
||||||
if role_config.assets?
|
if role.assets?
|
||||||
execute *app.extract_assets
|
execute *app.extract_assets
|
||||||
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
||||||
execute *app.sync_asset_volumes(old_version: old_version)
|
execute *app.sync_asset_volumes(old_version: old_version)
|
||||||
@@ -27,7 +26,6 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
KAMAL.roles_on(host).each do |role|
|
KAMAL.roles_on(host).each do |role|
|
||||||
app = KAMAL.app(role: role)
|
app = KAMAL.app(role: role)
|
||||||
auditor = KAMAL.auditor(role: role)
|
auditor = KAMAL.auditor(role: role)
|
||||||
role_config = KAMAL.config.role(role)
|
|
||||||
|
|
||||||
if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
|
if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
|
||||||
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
||||||
@@ -38,7 +36,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
|
|
||||||
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
||||||
|
|
||||||
execute *app.tie_cord(role_config.cord_host_file) if role_config.uses_cord?
|
execute *app.tie_cord(role.cord_host_file) if role.uses_cord?
|
||||||
|
|
||||||
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
||||||
|
|
||||||
@@ -47,7 +45,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
Kamal::Cli::Healthcheck::Poller.wait_for_healthy(pause_after_ready: true) { capture_with_info(*app.status(version: version)) }
|
||||||
|
|
||||||
if old_version.present?
|
if old_version.present?
|
||||||
if role_config.uses_cord?
|
if role.uses_cord?
|
||||||
cord = capture_with_info(*app.cord(version: old_version), raise_on_non_zero_exit: false).strip
|
cord = capture_with_info(*app.cord(version: old_version), raise_on_non_zero_exit: false).strip
|
||||||
if cord.present?
|
if cord.present?
|
||||||
execute *app.cut_cord(cord)
|
execute *app.cut_cord(cord)
|
||||||
@@ -57,7 +55,7 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
|
|
||||||
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
|
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
|
||||||
|
|
||||||
execute *app.clean_up_assets if role_config.assets?
|
execute *app.clean_up_assets if role.assets?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -202,19 +200,20 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
# FIXME: Catch when app containers aren't running
|
# FIXME: Catch when app containers aren't running
|
||||||
|
|
||||||
grep = options[:grep]
|
grep = options[:grep]
|
||||||
|
since = options[:since]
|
||||||
if options[:follow]
|
if options[:follow]
|
||||||
|
lines = options[:lines].presence || ((since || grep) ? nil : 10) # Default to 10 lines if since or grep isn't set
|
||||||
|
|
||||||
run_locally do
|
run_locally do
|
||||||
info "Following logs on #{KAMAL.primary_host}..."
|
info "Following logs on #{KAMAL.primary_host}..."
|
||||||
|
|
||||||
KAMAL.specific_roles ||= ["web"]
|
KAMAL.specific_roles ||= ["web"]
|
||||||
role = KAMAL.roles_on(KAMAL.primary_host).first
|
role = KAMAL.roles_on(KAMAL.primary_host).first
|
||||||
|
|
||||||
info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
|
info KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep)
|
||||||
exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, grep: grep)
|
exec KAMAL.app(role: role).follow_logs(host: KAMAL.primary_host, lines: lines, grep: grep)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
since = options[:since]
|
|
||||||
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
lines = options[:lines].presence || ((since || grep) ? nil : 100) # Default to 100 lines if since or grep isn't set
|
||||||
|
|
||||||
on(KAMAL.hosts) do |host|
|
on(KAMAL.hosts) do |host|
|
||||||
|
|||||||
@@ -123,8 +123,9 @@ module Kamal::Cli
|
|||||||
yield
|
yield
|
||||||
rescue SSHKit::Runner::ExecuteError => e
|
rescue SSHKit::Runner::ExecuteError => e
|
||||||
if e.message =~ /cannot create directory/
|
if e.message =~ /cannot create directory/
|
||||||
|
say "Deploy lock already in place!", :red
|
||||||
on(KAMAL.primary_host) { puts capture_with_debug(*KAMAL.lock.status) }
|
on(KAMAL.primary_host) { puts capture_with_debug(*KAMAL.lock.status) }
|
||||||
raise LockError, "Deploy lock found"
|
raise LockError, "Deploy lock found. Run 'kamal lock help' for more information"
|
||||||
else
|
else
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,9 +8,8 @@ class Kamal::Cli::Env < Kamal::Cli::Base
|
|||||||
execute *KAMAL.auditor.record("Pushed env files"), verbosity: :debug
|
execute *KAMAL.auditor.record("Pushed env files"), verbosity: :debug
|
||||||
|
|
||||||
KAMAL.roles_on(host).each do |role|
|
KAMAL.roles_on(host).each do |role|
|
||||||
role_config = KAMAL.config.role(role)
|
|
||||||
execute *KAMAL.app(role: role).make_env_directory
|
execute *KAMAL.app(role: role).make_env_directory
|
||||||
upload! StringIO.new(role_config.env_file), role_config.host_env_file_path, mode: 400
|
upload! StringIO.new(role.env_file), role.host_env_file_path, mode: 400
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -36,7 +35,6 @@ class Kamal::Cli::Env < Kamal::Cli::Base
|
|||||||
execute *KAMAL.auditor.record("Deleted env files"), verbosity: :debug
|
execute *KAMAL.auditor.record("Deleted env files"), verbosity: :debug
|
||||||
|
|
||||||
KAMAL.roles_on(host).each do |role|
|
KAMAL.roles_on(host).each do |role|
|
||||||
role_config = KAMAL.config.role(role)
|
|
||||||
execute *KAMAL.app(role: role).remove_env_file
|
execute *KAMAL.app(role: role).remove_env_file
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,15 +1,18 @@
|
|||||||
class Kamal::Cli::Main < Kamal::Cli::Base
|
class Kamal::Cli::Main < Kamal::Cli::Base
|
||||||
desc "setup", "Setup all accessories, push the env, and deploy app to servers"
|
desc "setup", "Setup all accessories, push the env, and deploy app to servers"
|
||||||
|
option :skip_push, aliases: "-P", type: :boolean, default: false, desc: "Skip image build and push"
|
||||||
def setup
|
def setup
|
||||||
print_runtime do
|
print_runtime do
|
||||||
mutating do
|
mutating do
|
||||||
|
invoke_options = deploy_options
|
||||||
|
|
||||||
say "Ensure Docker is installed...", :magenta
|
say "Ensure Docker is installed...", :magenta
|
||||||
invoke "kamal:cli:server:bootstrap"
|
invoke "kamal:cli:server:bootstrap", [], invoke_options
|
||||||
|
|
||||||
say "Push env files...", :magenta
|
say "Push env files...", :magenta
|
||||||
invoke "kamal:cli:env:push"
|
invoke "kamal:cli:env:push", [], invoke_options
|
||||||
|
|
||||||
invoke "kamal:cli:accessory:boot", [ "all" ]
|
invoke "kamal:cli:accessory:boot", [ "all" ], invoke_options
|
||||||
deploy
|
deploy
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -18,12 +18,16 @@ class Kamal::Cli::Prune < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "containers", "Prune all stopped containers, except the last 5"
|
desc "containers", "Prune all stopped containers, except the last n (default 5)"
|
||||||
|
option :retain, type: :numeric, default: nil, desc: "Number of containers to retain"
|
||||||
def containers
|
def containers
|
||||||
|
retain = options.fetch(:retain, KAMAL.config.retain_containers)
|
||||||
|
raise "retain must be at least 1" if retain < 1
|
||||||
|
|
||||||
mutating do
|
mutating do
|
||||||
on(KAMAL.hosts) do
|
on(KAMAL.hosts) do
|
||||||
execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
|
execute *KAMAL.auditor.record("Pruned containers"), verbosity: :debug
|
||||||
execute *KAMAL.prune.app_containers
|
execute *KAMAL.prune.app_containers(retain: retain)
|
||||||
execute *KAMAL.prune.healthcheck_containers
|
execute *KAMAL.prune.healthcheck_containers
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
class Kamal::Cli::Registry < Kamal::Cli::Base
|
class Kamal::Cli::Registry < Kamal::Cli::Base
|
||||||
desc "login", "Log in to registry locally and remotely"
|
desc "login", "Log in to registry locally and remotely"
|
||||||
def login
|
def login
|
||||||
on([ :local ] + KAMAL.hosts) { execute *KAMAL.registry.login }
|
run_locally { execute *KAMAL.registry.login }
|
||||||
|
on(KAMAL.hosts) { execute *KAMAL.registry.login }
|
||||||
# FIXME: This rescue needed?
|
# FIXME: This rescue needed?
|
||||||
rescue ArgumentError => e
|
rescue ArgumentError => e
|
||||||
puts e.message
|
puts e.message
|
||||||
|
|||||||
@@ -17,7 +17,9 @@ class Kamal::Cli::Server < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
if missing.any?
|
if missing.any?
|
||||||
raise "Docker is not installed on #{missing.join(", ")} and can't be automatically installed without having root access and the `curl` command available. Install Docker manually: https://docs.docker.com/engine/install/"
|
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/"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
run_hook "docker-setup"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -77,6 +77,10 @@ registry:
|
|||||||
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
||||||
# hitting 404 on in-flight requests. Combines all files from new and old
|
# hitting 404 on in-flight requests. Combines all files from new and old
|
||||||
# version inside the asset_path.
|
# version inside the asset_path.
|
||||||
|
#
|
||||||
|
# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
|
||||||
|
# See https://github.com/basecamp/kamal/issues/626 for details
|
||||||
|
#
|
||||||
# asset_path: /rails/public/assets
|
# asset_path: /rails/public/assets
|
||||||
|
|
||||||
# Configure rolling deploys by setting a wait time between batches of restarts.
|
# Configure rolling deploys by setting a wait time between batches of restarts.
|
||||||
|
|||||||
7
lib/kamal/cli/templates/sample_hooks/docker-setup.sample
Normal file
7
lib/kamal/cli/templates/sample_hooks/docker-setup.sample
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# A sample docker-setup hook
|
||||||
|
#
|
||||||
|
# Sets up a Docker network which can then be used by the application’s containers
|
||||||
|
|
||||||
|
ssh user@example.com docker network create kamal
|
||||||
@@ -20,7 +20,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
|
|||||||
on(hosts) do
|
on(hosts) do
|
||||||
execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
|
execute *KAMAL.auditor.record("Rebooted traefik"), verbosity: :debug
|
||||||
execute *KAMAL.registry.login
|
execute *KAMAL.registry.login
|
||||||
execute *KAMAL.traefik.stop
|
execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
|
||||||
execute *KAMAL.traefik.remove_container
|
execute *KAMAL.traefik.remove_container
|
||||||
execute *KAMAL.traefik.run
|
execute *KAMAL.traefik.run
|
||||||
end
|
end
|
||||||
@@ -44,7 +44,7 @@ class Kamal::Cli::Traefik < Kamal::Cli::Base
|
|||||||
mutating do
|
mutating do
|
||||||
on(KAMAL.traefik_hosts) do
|
on(KAMAL.traefik_hosts) do
|
||||||
execute *KAMAL.auditor.record("Stopped traefik"), verbosity: :debug
|
execute *KAMAL.auditor.record("Stopped traefik"), verbosity: :debug
|
||||||
execute *KAMAL.traefik.stop
|
execute *KAMAL.traefik.stop, raise_on_non_zero_exit: false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ class Kamal::Commander
|
|||||||
|
|
||||||
def primary_host
|
def primary_host
|
||||||
# Given a list of specific roles, make an effort to match up with the primary_role
|
# Given a list of specific roles, make an effort to match up with the primary_role
|
||||||
specific_hosts&.first || specific_roles&.detect { |role| role.name == config.primary_role }&.primary_host || specific_roles&.first&.primary_host || config.primary_host
|
specific_hosts&.first || specific_roles&.detect { |role| role == config.primary_role }&.primary_host || specific_roles&.first&.primary_host || config.primary_host
|
||||||
end
|
end
|
||||||
|
|
||||||
def primary_role
|
def primary_role
|
||||||
@@ -73,7 +73,7 @@ class Kamal::Commander
|
|||||||
end
|
end
|
||||||
|
|
||||||
def roles_on(host)
|
def roles_on(host)
|
||||||
roles.select { |role| role.hosts.include?(host.to_s) }.map(&:name)
|
roles.select { |role| role.hosts.include?(host.to_s) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def traefik_hosts
|
def traefik_hosts
|
||||||
|
|||||||
@@ -3,12 +3,11 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
|
|
||||||
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
ACTIVE_DOCKER_STATUSES = [ :running, :restarting ]
|
||||||
|
|
||||||
attr_reader :role, :role_config
|
attr_reader :role, :role
|
||||||
|
|
||||||
def initialize(config, role: nil)
|
def initialize(config, role: nil)
|
||||||
super(config)
|
super(config)
|
||||||
@role = role
|
@role = role
|
||||||
@role_config = config.role(self.role)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def run(hostname: nil)
|
def run(hostname: nil)
|
||||||
@@ -19,15 +18,15 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
*(["--hostname", hostname] if hostname),
|
*(["--hostname", hostname] if hostname),
|
||||||
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
"-e", "KAMAL_CONTAINER_NAME=\"#{container_name}\"",
|
||||||
"-e", "KAMAL_VERSION=\"#{config.version}\"",
|
"-e", "KAMAL_VERSION=\"#{config.version}\"",
|
||||||
*role_config.env_args,
|
*role.env_args,
|
||||||
*role_config.health_check_args,
|
*role.health_check_args,
|
||||||
*config.logging_args,
|
*role.logging_args,
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*role_config.asset_volume_args,
|
*role.asset_volume_args,
|
||||||
*role_config.label_args,
|
*role.label_args,
|
||||||
*role_config.option_args,
|
*role.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
role_config.cmd
|
role.cmd
|
||||||
end
|
end
|
||||||
|
|
||||||
def start
|
def start
|
||||||
@@ -64,22 +63,22 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
|||||||
def list_versions(*docker_args, statuses: nil)
|
def list_versions(*docker_args, statuses: nil)
|
||||||
pipe \
|
pipe \
|
||||||
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
||||||
%(while read line; do echo ${line##{role_config.container_prefix}-}; done) # Extract SHA from "service-role-dest-SHA"
|
%(while read line; do echo ${line##{role.container_prefix}-}; done) # Extract SHA from "service-role-dest-SHA"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
def make_env_directory
|
def make_env_directory
|
||||||
make_directory role_config.host_env_directory
|
make_directory role.host_env_directory
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_env_file
|
def remove_env_file
|
||||||
[ :rm, "-f", role_config.host_env_file_path ]
|
[ :rm, "-f", role.host_env_file_path ]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
private
|
private
|
||||||
def container_name(version = nil)
|
def container_name(version = nil)
|
||||||
[ role_config.container_prefix, version || config.version ].compact.join("-")
|
[ role.container_prefix, version || config.version ].compact.join("-")
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_args(statuses: nil)
|
def filter_args(statuses: nil)
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
module Kamal::Commands::App::Assets
|
module Kamal::Commands::App::Assets
|
||||||
def extract_assets
|
def extract_assets
|
||||||
asset_container = "#{role_config.container_prefix}-assets"
|
asset_container = "#{role.container_prefix}-assets"
|
||||||
|
|
||||||
combine \
|
combine \
|
||||||
make_directory(role_config.asset_extracted_path),
|
make_directory(role.asset_extracted_path),
|
||||||
[*docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true"],
|
[*docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true"],
|
||||||
docker(:run, "--name", asset_container, "--detach", "--rm", config.latest_image, "sleep 1000000"),
|
docker(:run, "--name", asset_container, "--detach", "--rm", config.latest_image, "sleep 1000000"),
|
||||||
docker(:cp, "-L", "#{asset_container}:#{role_config.asset_path}/.", role_config.asset_extracted_path),
|
docker(:cp, "-L", "#{asset_container}:#{role.asset_path}/.", role.asset_extracted_path),
|
||||||
docker(:stop, "-t 1", asset_container),
|
docker(:stop, "-t 1", asset_container),
|
||||||
by: "&&"
|
by: "&&"
|
||||||
end
|
end
|
||||||
|
|
||||||
def sync_asset_volumes(old_version: nil)
|
def sync_asset_volumes(old_version: nil)
|
||||||
new_extracted_path, new_volume_path = role_config.asset_extracted_path(config.version), role_config.asset_volume.host_path
|
new_extracted_path, new_volume_path = role.asset_extracted_path(config.version), role.asset_volume.host_path
|
||||||
if old_version.present?
|
if old_version.present?
|
||||||
old_extracted_path, old_volume_path = role_config.asset_extracted_path(old_version), role_config.asset_volume(old_version).host_path
|
old_extracted_path, old_volume_path = role.asset_extracted_path(old_version), role.asset_volume(old_version).host_path
|
||||||
end
|
end
|
||||||
|
|
||||||
commands = [make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path)]
|
commands = [make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path)]
|
||||||
@@ -29,8 +29,8 @@ module Kamal::Commands::App::Assets
|
|||||||
|
|
||||||
def clean_up_assets
|
def clean_up_assets
|
||||||
chain \
|
chain \
|
||||||
find_and_remove_older_siblings(role_config.asset_extracted_path),
|
find_and_remove_older_siblings(role.asset_extracted_path),
|
||||||
find_and_remove_older_siblings(role_config.asset_volume_path)
|
find_and_remove_older_siblings(role.asset_volume_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -39,7 +39,7 @@ module Kamal::Commands::App::Assets
|
|||||||
:find,
|
:find,
|
||||||
Pathname.new(path).dirname.to_s,
|
Pathname.new(path).dirname.to_s,
|
||||||
"-maxdepth 1",
|
"-maxdepth 1",
|
||||||
"-name", "'#{role_config.container_prefix}-*'",
|
"-name", "'#{role.container_prefix}-*'",
|
||||||
"!", "-name", Pathname.new(path).basename.to_s,
|
"!", "-name", Pathname.new(path).basename.to_s,
|
||||||
"-exec rm -rf \"{}\" +"
|
"-exec rm -rf \"{}\" +"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module Kamal::Commands::App::Cord
|
|||||||
def cord(version:)
|
def cord(version:)
|
||||||
pipe \
|
pipe \
|
||||||
docker(:inspect, "-f '{{ range .Mounts }}{{printf \"%s %s\\n\" .Source .Destination}}{{ end }}'", container_name(version)),
|
docker(:inspect, "-f '{{ range .Mounts }}{{printf \"%s %s\\n\" .Source .Destination}}{{ end }}'", container_name(version)),
|
||||||
[:awk, "'$2 == \"#{role_config.cord_volume.container_path}\" {print $1}'"]
|
[:awk, "'$2 == \"#{role.cord_volume.container_path}\" {print $1}'"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def tie_cord(cord)
|
def tie_cord(cord)
|
||||||
@@ -12,8 +12,8 @@ module Kamal::Commands::App::Cord
|
|||||||
def cut_cord(cord)
|
def cut_cord(cord)
|
||||||
remove_directory(cord)
|
remove_directory(cord)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def create_empty_file(file)
|
def create_empty_file(file)
|
||||||
chain \
|
chain \
|
||||||
make_directory_for(file),
|
make_directory_for(file),
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ module Kamal::Commands::App::Execution
|
|||||||
docker :run,
|
docker :run,
|
||||||
("-it" if interactive),
|
("-it" if interactive),
|
||||||
"--rm",
|
"--rm",
|
||||||
*role_config&.env_args,
|
*role&.env_args,
|
||||||
*config.volume_args,
|
*config.volume_args,
|
||||||
*role_config&.option_args,
|
*role&.option_args,
|
||||||
config.absolute_image,
|
config.absolute_image,
|
||||||
*command
|
*command
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ module Kamal::Commands::App::Logging
|
|||||||
("grep '#{grep}'" if grep)
|
("grep '#{grep}'" if grep)
|
||||||
end
|
end
|
||||||
|
|
||||||
def follow_logs(host:, grep: nil)
|
def follow_logs(host:, lines: nil, grep: nil)
|
||||||
run_over_ssh \
|
run_over_ssh \
|
||||||
pipe(
|
pipe(
|
||||||
current_running_container_id,
|
current_running_container_id,
|
||||||
"xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
"xargs docker logs --timestamps#{" --tail #{lines}" if lines} --follow 2>&1",
|
||||||
(%(grep "#{grep}") if grep)
|
(%(grep "#{grep}") if grep)
|
||||||
),
|
),
|
||||||
host: host
|
host: host
|
||||||
|
|||||||
@@ -62,10 +62,18 @@ module Kamal::Commands
|
|||||||
combine *commands, by: ">"
|
combine *commands, by: ">"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def any(*commands)
|
||||||
|
combine *commands, by: "||"
|
||||||
|
end
|
||||||
|
|
||||||
def xargs(command)
|
def xargs(command)
|
||||||
[ :xargs, command ].flatten
|
[ :xargs, command ].flatten
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def shell(command)
|
||||||
|
[ :sh, "-c", "'#{command.flatten.join(" ").gsub("'", "'\\''")}'" ]
|
||||||
|
end
|
||||||
|
|
||||||
def docker(*args)
|
def docker(*args)
|
||||||
args.compact.unshift :docker
|
args.compact.unshift :docker
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
class BuilderError < StandardError; end
|
class BuilderError < StandardError; end
|
||||||
|
|
||||||
delegate :argumentize, to: Kamal::Utils
|
delegate :argumentize, to: Kamal::Utils
|
||||||
delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, to: :builder_config
|
delegate :args, :secrets, :dockerfile, :local_arch, :local_host, :remote_arch, :remote_host, :cache_from, :cache_to, :ssh, to: :builder_config
|
||||||
|
|
||||||
def clean
|
def clean
|
||||||
docker :image, :rm, "--force", config.absolute_image
|
docker :image, :rm, "--force", config.absolute_image
|
||||||
@@ -14,7 +14,7 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def build_options
|
def build_options
|
||||||
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile ]
|
[ *build_tags, *build_cache, *build_labels, *build_args, *build_secrets, *build_dockerfile, *build_ssh ]
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_context
|
def build_context
|
||||||
@@ -24,7 +24,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
def validate_image
|
def validate_image
|
||||||
pipe \
|
pipe \
|
||||||
docker(:inspect, "-f", "'{{ .Config.Labels.service }}'", config.absolute_image),
|
docker(:inspect, "-f", "'{{ .Config.Labels.service }}'", config.absolute_image),
|
||||||
[:grep, "-x", config.service, "||", "(echo \"Image #{config.absolute_image} is missing the `service` label\" && exit 1)"]
|
any(
|
||||||
|
[:grep, "-x", config.service],
|
||||||
|
"(echo \"Image #{config.absolute_image} is missing the 'service' label\" && exit 1)"
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -60,6 +63,10 @@ class Kamal::Commands::Builder::Base < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def build_ssh
|
||||||
|
argumentize "--ssh", ssh if ssh.present?
|
||||||
|
end
|
||||||
|
|
||||||
def builder_config
|
def builder_config
|
||||||
config.builder
|
config.builder
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class Kamal::Commands::Docker < Kamal::Commands::Base
|
class Kamal::Commands::Docker < Kamal::Commands::Base
|
||||||
# Install Docker using the https://github.com/docker/docker-install convenience script.
|
# Install Docker using the https://github.com/docker/docker-install convenience script.
|
||||||
def install
|
def install
|
||||||
pipe [ :curl, "-fsSL", "https://get.docker.com" ], :sh
|
pipe get_docker, :sh
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks the Docker client version. Fails if Docker is not installed.
|
# Checks the Docker client version. Fails if Docker is not installed.
|
||||||
@@ -18,4 +18,13 @@ class Kamal::Commands::Docker < Kamal::Commands::Base
|
|||||||
def superuser?
|
def superuser?
|
||||||
[ '[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null' ]
|
[ '[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null' ]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
def get_docker
|
||||||
|
shell \
|
||||||
|
any \
|
||||||
|
[ :curl, "-fsSL", "https://get.docker.com" ],
|
||||||
|
[ :wget, "-O -", "https://get.docker.com" ],
|
||||||
|
[ :echo, "\"exit 1\"" ]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ class Kamal::Commands::Prune < Kamal::Commands::Base
|
|||||||
"while read image tag; do docker rmi $tag; done"
|
"while read image tag; do docker rmi $tag; done"
|
||||||
end
|
end
|
||||||
|
|
||||||
def app_containers(keep_last: 5)
|
def app_containers(retain:)
|
||||||
pipe \
|
pipe \
|
||||||
docker(:ps, "-q", "-a", *service_filter, *stopped_containers_filters),
|
docker(:ps, "-q", "-a", *service_filter, *stopped_containers_filters),
|
||||||
"tail -n +#{keep_last + 1}",
|
"tail -n +#{retain + 1}",
|
||||||
"while read container_id; do docker rm $container_id; done"
|
"while read container_id; do docker rm $container_id; done"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ class Kamal::Commands::Registry < Kamal::Commands::Base
|
|||||||
delegate :registry, to: :config
|
delegate :registry, to: :config
|
||||||
|
|
||||||
def login
|
def login
|
||||||
docker :login, registry["server"], "-u", sensitive(lookup("username")), "-p", sensitive(lookup("password"))
|
docker :login,
|
||||||
|
registry["server"],
|
||||||
|
"-u", sensitive(Kamal::Utils.escape_shell_value(lookup("username"))),
|
||||||
|
"-p", sensitive(Kamal::Utils.escape_shell_value(lookup("password")))
|
||||||
end
|
end
|
||||||
|
|
||||||
def logout
|
def logout
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
class Kamal::Commands::Traefik < Kamal::Commands::Base
|
class Kamal::Commands::Traefik < Kamal::Commands::Base
|
||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
DEFAULT_IMAGE = "traefik:v2.9"
|
DEFAULT_IMAGE = "traefik:v2.10"
|
||||||
CONTAINER_PORT = 80
|
CONTAINER_PORT = 80
|
||||||
DEFAULT_ARGS = {
|
DEFAULT_ARGS = {
|
||||||
'log.level' => 'DEBUG'
|
'log.level' => 'DEBUG'
|
||||||
@@ -39,7 +39,7 @@ class Kamal::Commands::Traefik < Kamal::Commands::Base
|
|||||||
end
|
end
|
||||||
|
|
||||||
def start_or_run
|
def start_or_run
|
||||||
combine start, run, by: "||"
|
any start, run
|
||||||
end
|
end
|
||||||
|
|
||||||
def info
|
def info
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ require "erb"
|
|||||||
require "net/ssh/proxy/jump"
|
require "net/ssh/proxy/jump"
|
||||||
|
|
||||||
class Kamal::Configuration
|
class Kamal::Configuration
|
||||||
delegate :service, :image, :servers, :env, :labels, :registry, :stop_wait_time, :hooks_path, to: :raw_config, allow_nil: true
|
delegate :service, :image, :servers, :env, :labels, :registry, :stop_wait_time, :hooks_path, :logging, to: :raw_config, allow_nil: true
|
||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_reader :destination, :raw_config
|
attr_reader :destination, :raw_config
|
||||||
@@ -92,7 +92,19 @@ class Kamal::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
def primary_host
|
def primary_host
|
||||||
role(primary_role)&.primary_host
|
primary_role&.primary_host
|
||||||
|
end
|
||||||
|
|
||||||
|
def primary_role_name
|
||||||
|
raw_config.primary_role || "web"
|
||||||
|
end
|
||||||
|
|
||||||
|
def primary_role
|
||||||
|
role(primary_role_name)
|
||||||
|
end
|
||||||
|
|
||||||
|
def allow_empty_roles?
|
||||||
|
raw_config.allow_empty_roles
|
||||||
end
|
end
|
||||||
|
|
||||||
def traefik_roles
|
def traefik_roles
|
||||||
@@ -127,6 +139,10 @@ class Kamal::Configuration
|
|||||||
raw_config.require_destination
|
raw_config.require_destination
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def retain_containers
|
||||||
|
raw_config.retain_containers || 5
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def volume_args
|
def volume_args
|
||||||
if raw_config.volumes.present?
|
if raw_config.volumes.present?
|
||||||
@@ -137,9 +153,9 @@ class Kamal::Configuration
|
|||||||
end
|
end
|
||||||
|
|
||||||
def logging_args
|
def logging_args
|
||||||
if raw_config.logging.present?
|
if logging.present?
|
||||||
optionize({ "log-driver" => raw_config.logging["driver"] }.compact) +
|
optionize({ "log-driver" => logging["driver"] }.compact) +
|
||||||
argumentize("--log-opt", raw_config.logging["options"])
|
argumentize("--log-opt", logging["options"])
|
||||||
else
|
else
|
||||||
argumentize("--log-opt", { "max-size" => "10m" })
|
argumentize("--log-opt", { "max-size" => "10m" })
|
||||||
end
|
end
|
||||||
@@ -208,17 +224,9 @@ class Kamal::Configuration
|
|||||||
raw_config.asset_path
|
raw_config.asset_path
|
||||||
end
|
end
|
||||||
|
|
||||||
def primary_role
|
|
||||||
raw_config.primary_role || "web"
|
|
||||||
end
|
|
||||||
|
|
||||||
def allow_empty_roles?
|
|
||||||
raw_config.allow_empty_roles
|
|
||||||
end
|
|
||||||
|
|
||||||
|
|
||||||
def valid?
|
def valid?
|
||||||
ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version
|
ensure_destination_if_required && ensure_required_keys_present && ensure_valid_kamal_version && ensure_retain_containers_valid && ensure_valid_service_name
|
||||||
end
|
end
|
||||||
|
|
||||||
def to_h
|
def to_h
|
||||||
@@ -264,12 +272,12 @@ class Kamal::Configuration
|
|||||||
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
raise ArgumentError, "You must specify a password for the registry in config/deploy.yml (or set the ENV variable if that's used)"
|
||||||
end
|
end
|
||||||
|
|
||||||
unless role_names.include?(primary_role)
|
unless role_names.include?(primary_role_name)
|
||||||
raise ArgumentError, "The primary_role #{primary_role} isn't defined"
|
raise ArgumentError, "The primary_role #{primary_role_name} isn't defined"
|
||||||
end
|
end
|
||||||
|
|
||||||
if role(primary_role).hosts.empty?
|
if primary_role.hosts.empty?
|
||||||
raise ArgumentError, "No servers specified for the #{primary_role} primary_role"
|
raise ArgumentError, "No servers specified for the #{primary_role.name} primary_role"
|
||||||
end
|
end
|
||||||
|
|
||||||
unless allow_empty_roles?
|
unless allow_empty_roles?
|
||||||
@@ -283,6 +291,12 @@ class Kamal::Configuration
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_valid_service_name
|
||||||
|
raise ArgumentError, "Service name can only include alphanumeric characters, hyphens, and underscores" unless raw_config[:service] =~ /^[a-z0-9_-]+$/
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
def ensure_valid_kamal_version
|
def ensure_valid_kamal_version
|
||||||
if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)
|
if minimum_version && Gem::Version.new(minimum_version) > Gem::Version.new(Kamal::VERSION)
|
||||||
raise ArgumentError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"
|
raise ArgumentError, "Current version is #{Kamal::VERSION}, minimum required is #{minimum_version}"
|
||||||
@@ -291,6 +305,12 @@ class Kamal::Configuration
|
|||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_retain_containers_valid
|
||||||
|
raise ArgumentError, "Must retain at least 1 container" if retain_containers < 1
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def role_names
|
def role_names
|
||||||
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
raw_config.servers.is_a?(Array) ? [ "web" ] : raw_config.servers.keys.sort
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class Kamal::Configuration::Accessory
|
|||||||
end
|
end
|
||||||
|
|
||||||
def service_name
|
def service_name
|
||||||
"#{config.service}-#{name}"
|
specifics["service"] || "#{config.service}-#{name}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def image
|
def image
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ class Kamal::Configuration::Boot
|
|||||||
limit = @options["limit"]
|
limit = @options["limit"]
|
||||||
|
|
||||||
if limit.to_s.end_with?("%")
|
if limit.to_s.end_with?("%")
|
||||||
@host_count * limit.to_i / 100
|
[@host_count * limit.to_i / 100, 1].max
|
||||||
else
|
else
|
||||||
limit
|
limit
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -81,6 +81,10 @@ class Kamal::Configuration::Builder
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ssh
|
||||||
|
@options["ssh"]
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def valid?
|
def valid?
|
||||||
if @options["cache"] && @options["cache"]["type"]
|
if @options["cache"] && @options["cache"]["type"]
|
||||||
|
|||||||
@@ -3,9 +3,10 @@ class Kamal::Configuration::Role
|
|||||||
delegate :argumentize, :optionize, to: Kamal::Utils
|
delegate :argumentize, :optionize, to: Kamal::Utils
|
||||||
|
|
||||||
attr_accessor :name
|
attr_accessor :name
|
||||||
|
alias to_s name
|
||||||
|
|
||||||
def initialize(name, config:)
|
def initialize(name, config:)
|
||||||
@name, @config = name.inquiry, config
|
@name, @config = name.inquiry, config
|
||||||
end
|
end
|
||||||
|
|
||||||
def primary_host
|
def primary_host
|
||||||
@@ -36,6 +37,18 @@ class Kamal::Configuration::Role
|
|||||||
argumentize "--label", labels
|
argumentize "--label", labels
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def logging_args
|
||||||
|
args = config.logging || {}
|
||||||
|
args.deep_merge!(specializations["logging"]) if specializations["logging"].present?
|
||||||
|
|
||||||
|
if args.any?
|
||||||
|
optionize({ "log-driver" => args["driver"] }.compact) +
|
||||||
|
argumentize("--log-opt", args["options"])
|
||||||
|
else
|
||||||
|
config.logging_args
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
def env
|
def env
|
||||||
if config.env && config.env["secret"]
|
if config.env && config.env["secret"]
|
||||||
@@ -101,7 +114,7 @@ class Kamal::Configuration::Role
|
|||||||
end
|
end
|
||||||
|
|
||||||
def primary?
|
def primary?
|
||||||
@config.primary_role == name
|
self == @config.primary_role
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
require "sshkit"
|
require "sshkit"
|
||||||
require "sshkit/dsl"
|
require "sshkit/dsl"
|
||||||
|
require "net/scp"
|
||||||
require "active_support/core_ext/hash/deep_merge"
|
require "active_support/core_ext/hash/deep_merge"
|
||||||
require "json"
|
require "json"
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
module Kamal
|
module Kamal
|
||||||
VERSION = "1.3.1"
|
VERSION = "1.4.0"
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -148,6 +148,30 @@ class CliAccessoryTest < CliTestCase
|
|||||||
assert_match "rm -rf app-mysql", run_command("remove_service_directory", "mysql")
|
assert_match "rm -rf app-mysql", run_command("remove_service_directory", "mysql")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "hosts param respected" do
|
||||||
|
Kamal::Cli::Accessory.any_instance.expects(:directories).with("redis")
|
||||||
|
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
||||||
|
|
||||||
|
run_command("boot", "redis", "--hosts", "1.1.1.1").tap do |output|
|
||||||
|
assert_match /docker login.*on 1.1.1.1/, output
|
||||||
|
refute_match /docker login.*on 1.1.1.2/, output
|
||||||
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
||||||
|
refute_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.2", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "hosts param intersected with configuration" do
|
||||||
|
Kamal::Cli::Accessory.any_instance.expects(:directories).with("redis")
|
||||||
|
Kamal::Cli::Accessory.any_instance.expects(:upload).with("redis")
|
||||||
|
|
||||||
|
run_command("boot", "redis", "--hosts", "1.1.1.1,1.1.1.3").tap do |output|
|
||||||
|
assert_match /docker login.*on 1.1.1.1/, output
|
||||||
|
refute_match /docker login.*on 1.1.1.3/, output
|
||||||
|
assert_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.1", output
|
||||||
|
refute_match "docker run --name app-redis --detach --restart unless-stopped --log-opt max-size=\"10m\" --publish 6379:6379 --env-file .kamal/env/accessories/app-redis.env --volume $PWD/app-redis/data:/data --label service=\"app-redis\" redis:latest on 1.1.1.3", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command)
|
def run_command(*command)
|
||||||
stdouted { Kamal::Cli::Accessory.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
stdouted { Kamal::Cli::Accessory.start([*command, "-c", "test/fixtures/deploy_with_accessories.yml"]) }
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ class CliBuildTest < CliTestCase
|
|||||||
run_command("pull").tap do |output|
|
run_command("pull").tap do |output|
|
||||||
assert_match /docker image rm --force dhh\/app:999/, output
|
assert_match /docker image rm --force dhh\/app:999/, output
|
||||||
assert_match /docker pull dhh\/app:999/, output
|
assert_match /docker pull dhh\/app:999/, output
|
||||||
assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the `service` label\" && exit 1)", output
|
assert_match "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:999 | grep -x app || (echo \"Image dhh/app:999 is missing the 'service' label\" && exit 1)", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -2,12 +2,47 @@ require_relative "cli_test_case"
|
|||||||
|
|
||||||
class CliMainTest < CliTestCase
|
class CliMainTest < CliTestCase
|
||||||
test "setup" do
|
test "setup" do
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap")
|
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push")
|
|
||||||
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ])
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
|
||||||
Kamal::Cli::Main.any_instance.expects(:deploy)
|
Kamal::Cli::Main.any_instance.expects(:deploy)
|
||||||
|
|
||||||
run_command("setup")
|
run_command("setup").tap do |output|
|
||||||
|
assert_match /Ensure Docker is installed.../, output
|
||||||
|
assert_match /Push env files.../, output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "setup with skip_push" do
|
||||||
|
invoke_options = { "config_file" => "test/fixtures/deploy_simple.yml", "version" => "999", "skip_hooks" => false }
|
||||||
|
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:server:bootstrap", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:env:push", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:accessory:boot", [ "all" ], invoke_options)
|
||||||
|
# deploy
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:registry:login", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:build:pull", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:traefik:boot", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:healthcheck:perform", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:stale_containers", [], invoke_options.merge(stop: true))
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:app:boot", [], invoke_options)
|
||||||
|
Kamal::Cli::Main.any_instance.expects(:invoke).with("kamal:cli:prune:all", [], invoke_options)
|
||||||
|
|
||||||
|
run_command("setup", "--skip_push").tap do |output|
|
||||||
|
assert_match /Ensure Docker is installed.../, output
|
||||||
|
assert_match /Push env files.../, output
|
||||||
|
# deploy
|
||||||
|
assert_match /Acquiring the deploy lock/, output
|
||||||
|
assert_match /Log into image registry/, output
|
||||||
|
assert_match /Pull app image/, output
|
||||||
|
assert_match /Ensure Traefik is running/, output
|
||||||
|
assert_match /Ensure app can pass healthcheck/, output
|
||||||
|
assert_match /Detect stale containers/, output
|
||||||
|
assert_match /Prune old containers and images/, output
|
||||||
|
assert_match /Releasing the deploy lock/, output
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "deploy" do
|
test "deploy" do
|
||||||
|
|||||||
@@ -20,6 +20,15 @@ class CliPruneTest < CliTestCase
|
|||||||
assert_match /docker ps -q -a --filter label=service=app --filter status=created --filter status=exited --filter status=dead | tail -n +6 | while read container_id; do docker rm $container_id; done on 1.1.1.\d/, output
|
assert_match /docker ps -q -a --filter label=service=app --filter status=created --filter status=exited --filter status=dead | tail -n +6 | while read container_id; do docker rm $container_id; done on 1.1.1.\d/, output
|
||||||
assert_match /docker container prune --force --filter label=service=healthcheck-app on 1.1.1.\d/, output
|
assert_match /docker container prune --force --filter label=service=healthcheck-app on 1.1.1.\d/, output
|
||||||
end
|
end
|
||||||
|
|
||||||
|
run_command("containers", "--retain", "10").tap do |output|
|
||||||
|
assert_match /docker ps -q -a --filter label=service=app --filter status=created --filter status=exited --filter status=dead | tail -n +11 | while read container_id; do docker rm $container_id; done on 1.1.1.\d/, output
|
||||||
|
assert_match /docker container prune --force --filter label=service=healthcheck-app on 1.1.1.\d/, output
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raises(RuntimeError, "retain must be at least 1") do
|
||||||
|
run_command("containers", "--retain", "0")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -21,12 +21,15 @@ class CliServerTest < CliTestCase
|
|||||||
test "bootstrap install as root user" do
|
test "bootstrap install as root user" do
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(false).at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null', raise_on_non_zero_exit: false).returns(true).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with('[ "${EUID:-$(id -u)}" -eq 0 ] || command -v sudo >/dev/null || command -v su >/dev/null', raise_on_non_zero_exit: false).returns(true).at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:curl, "-fsSL", "https://get.docker.com", "|", :sh).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:sh, "-c", "'curl -fsSL https://get.docker.com || wget -O - https://get.docker.com || echo \"exit 1\"'", "|", :sh).at_least_once
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:mkdir, "-p", ".kamal").returns("").at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:mkdir, "-p", ".kamal").returns("").at_least_once
|
||||||
|
Kamal::Commands::Hook.any_instance.stubs(:hook_exists?).returns(true)
|
||||||
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(".kamal/hooks/docker-setup", anything).at_least_once
|
||||||
|
|
||||||
run_command("bootstrap").tap do |output|
|
run_command("bootstrap").tap do |output|
|
||||||
("1.1.1.1".."1.1.1.4").map do |host|
|
("1.1.1.1".."1.1.1.4").map do |host|
|
||||||
assert_match "Missing Docker on #{host}. Installing…", output
|
assert_match "Missing Docker on #{host}. Installing…", output
|
||||||
|
assert_match "Running the docker-setup hook", output
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -83,14 +83,14 @@ class CommanderTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "primary_role" do
|
test "primary_role" do
|
||||||
assert_equal "web", @kamal.primary_role
|
assert_equal "web", @kamal.primary_role.name
|
||||||
@kamal.specific_roles = "workers"
|
@kamal.specific_roles = "workers"
|
||||||
assert_equal "workers", @kamal.primary_role
|
assert_equal "workers", @kamal.primary_role.name
|
||||||
end
|
end
|
||||||
|
|
||||||
test "roles_on" do
|
test "roles_on" do
|
||||||
assert_equal [ "web" ], @kamal.roles_on("1.1.1.1")
|
assert_equal [ "web" ], @kamal.roles_on("1.1.1.1").map(&:name)
|
||||||
assert_equal [ "workers" ], @kamal.roles_on("1.1.1.3")
|
assert_equal [ "workers" ], @kamal.roles_on("1.1.1.3").map(&:name)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "default group strategy" do
|
test "default group strategy" do
|
||||||
@@ -109,12 +109,18 @@ class CommanderTest < ActiveSupport::TestCase
|
|||||||
assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy)
|
assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "percentage-based group strategy limit is at least 1" do
|
||||||
|
configure_with(:deploy_with_low_percentage_boot_strategy)
|
||||||
|
|
||||||
|
assert_equal({ in: :groups, limit: 1, wait: 2 }, @kamal.boot_strategy)
|
||||||
|
end
|
||||||
|
|
||||||
test "try to match the primary role from a list of specific roles" do
|
test "try to match the primary role from a list of specific roles" do
|
||||||
configure_with(:deploy_primary_web_role_override)
|
configure_with(:deploy_primary_web_role_override)
|
||||||
|
|
||||||
@kamal.specific_roles = [ "web_*" ]
|
@kamal.specific_roles = [ "web_*" ]
|
||||||
assert_equal [ "web_chicago", "web_tokyo" ], @kamal.roles.map(&:name)
|
assert_equal [ "web_chicago", "web_tokyo" ], @kamal.roles.map(&:name)
|
||||||
assert_equal "web_tokyo", @kamal.primary_role
|
assert_equal "web_tokyo", @kamal.primary_role.name
|
||||||
assert_equal "1.1.1.3", @kamal.primary_host
|
assert_equal "1.1.1.3", @kamal.primary_host
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"busybox" => {
|
"busybox" => {
|
||||||
|
"service" => "custom-busybox",
|
||||||
"image" => "busybox:latest",
|
"image" => "busybox:latest",
|
||||||
"host" => "1.1.1.7"
|
"host" => "1.1.1.7"
|
||||||
}
|
}
|
||||||
@@ -57,7 +58,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
new_command(:redis).run.join(" ")
|
new_command(:redis).run.join(" ")
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-busybox --detach --restart unless-stopped --log-opt max-size=\"10m\" --env-file .kamal/env/accessories/app-busybox.env --label service=\"app-busybox\" busybox:latest",
|
"docker run --name custom-busybox --detach --restart unless-stopped --log-opt max-size=\"10m\" --env-file .kamal/env/accessories/custom-busybox.env --label service=\"custom-busybox\" busybox:latest",
|
||||||
new_command(:busybox).run.join(" ")
|
new_command(:busybox).run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -65,7 +66,7 @@ class CommandsAccessoryTest < ActiveSupport::TestCase
|
|||||||
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "100m", "max-file" => "3" } }
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker run --name app-busybox --detach --restart unless-stopped --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --env-file .kamal/env/accessories/app-busybox.env --label service=\"app-busybox\" busybox:latest",
|
"docker run --name custom-busybox --detach --restart unless-stopped --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --env-file .kamal/env/accessories/custom-busybox.env --label service=\"custom-busybox\" busybox:latest",
|
||||||
new_command(:busybox).run.join(" ")
|
new_command(:busybox).run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -71,6 +71,15 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
new_command.run.join(" ")
|
new_command.run.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "run with role logging config" do
|
||||||
|
@config[:logging] = { "driver" => "local", "options" => { "max-size" => "10m", "max-file" => "3" } }
|
||||||
|
@config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "logging" => { "driver" => "local", "options" => { "max-size" => "100m" } } } }
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"docker run --detach --restart unless-stopped --name app-web-999 -e KAMAL_CONTAINER_NAME=\"app-web-999\" -e KAMAL_VERSION=\"999\" --env-file .kamal/env/roles/app-web.env --health-cmd \"(curl -f http://localhost:3000/up || exit 1) && (stat /tmp/kamal-cord/cord > /dev/null || exit 1)\" --health-interval \"1s\" --volume $(pwd)/.kamal/cords/app-web-12345678901234567890123456789012:/tmp/kamal-cord --log-driver \"local\" --log-opt max-size=\"100m\" --log-opt max-file=\"3\" --label service=\"app\" --label role=\"web\" --label traefik.http.services.app-web.loadbalancer.server.scheme=\"http\" --label traefik.http.routers.app-web.rule=\"PathPrefix(\\`/\\`)\" --label traefik.http.routers.app-web.priority=\"2\" --label traefik.http.middlewares.app-web-retry.retry.attempts=\"5\" --label traefik.http.middlewares.app-web-retry.retry.initialinterval=\"500ms\" --label traefik.http.routers.app-web.middlewares=\"app-web-retry@docker\" dhh/app:999",
|
||||||
|
new_command.run.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
test "start" do
|
test "start" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker start app-web-999",
|
"docker start app-web-999",
|
||||||
@@ -145,12 +154,20 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "follow logs" do
|
test "follow logs" do
|
||||||
assert_match \
|
assert_match \
|
||||||
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 10 --follow 2>&1",
|
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --follow 2>&1",
|
||||||
new_command.follow_logs(host: "app-1")
|
new_command.follow_logs(host: "app-1")
|
||||||
|
|
||||||
assert_match \
|
assert_match \
|
||||||
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 10 --follow 2>&1 | grep \"Completed\"",
|
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --follow 2>&1 | grep \"Completed\"",
|
||||||
new_command.follow_logs(host: "app-1", grep: "Completed")
|
new_command.follow_logs(host: "app-1", grep: "Completed")
|
||||||
|
|
||||||
|
assert_match \
|
||||||
|
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 123 --follow 2>&1",
|
||||||
|
new_command.follow_logs(host: "app-1", lines: 123)
|
||||||
|
|
||||||
|
assert_match \
|
||||||
|
"docker ps --quiet --filter label=service=app --filter label=role=web --filter status=running --filter status=restarting --latest | xargs docker logs --timestamps --tail 123 --follow 2>&1 | grep \"Completed\"",
|
||||||
|
new_command.follow_logs(host: "app-1", lines: 123, grep: "Completed")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
||||||
@@ -383,6 +400,7 @@ class CommandsAppTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
private
|
private
|
||||||
def new_command(role: "web", **additional_config)
|
def new_command(role: "web", **additional_config)
|
||||||
Kamal::Commands::App.new(Kamal::Configuration.new(@config.merge(additional_config), destination: @destination, version: "999"), role: role)
|
config = Kamal::Configuration.new(@config.merge(additional_config), destination: @destination, version: "999")
|
||||||
|
Kamal::Commands::App.new(config, role: config.role(role))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -111,8 +111,16 @@ class CommandsBuilderTest < ActiveSupport::TestCase
|
|||||||
builder.push.join(" ")
|
builder.push.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "build with ssh agent socket" do
|
||||||
|
builder = new_builder_command(builder: { "ssh" => 'default=$SSH_AUTH_SOCK' })
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"-t dhh/app:123 -t dhh/app:latest --label service=\"app\" --file Dockerfile --ssh default=$SSH_AUTH_SOCK",
|
||||||
|
builder.target.build_options.join(" ")
|
||||||
|
end
|
||||||
|
|
||||||
test "validate image" do
|
test "validate image" do
|
||||||
assert_equal "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:123 | grep -x app || (echo \"Image dhh/app:123 is missing the `service` label\" && exit 1)", new_builder_command.validate_image.join(" ")
|
assert_equal "docker inspect -f '{{ .Config.Labels.service }}' dhh/app:123 | grep -x app || (echo \"Image dhh/app:123 is missing the 'service' label\" && exit 1)", new_builder_command.validate_image.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ class CommandsDockerTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "install" do
|
test "install" do
|
||||||
assert_equal "curl -fsSL https://get.docker.com | sh", @docker.install.join(" ")
|
assert_equal "sh -c 'curl -fsSL https://get.docker.com || wget -O - https://get.docker.com || echo \"exit 1\"' | sh", @docker.install.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "installed?" do
|
test "installed?" do
|
||||||
|
|||||||
@@ -23,7 +23,11 @@ class CommandsPruneTest < ActiveSupport::TestCase
|
|||||||
test "app containers" do
|
test "app containers" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker ps -q -a --filter label=service=app --filter status=created --filter status=exited --filter status=dead | tail -n +6 | while read container_id; do docker rm $container_id; done",
|
"docker ps -q -a --filter label=service=app --filter status=created --filter status=exited --filter status=dead | tail -n +6 | while read container_id; do docker rm $container_id; done",
|
||||||
new_command.app_containers.join(" ")
|
new_command.app_containers(retain: 5).join(" ")
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"docker ps -q -a --filter label=service=app --filter status=created --filter status=exited --filter status=dead | tail -n +4 | while read container_id; do docker rm $container_id; done",
|
||||||
|
new_command.app_containers(retain: 3).join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
test "healthcheck containers" do
|
test "healthcheck containers" do
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class CommandsRegistryTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
test "registry login" do
|
test "registry login" do
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker login hub.docker.com -u dhh -p secret",
|
"docker login hub.docker.com -u \"dhh\" -p \"secret\"",
|
||||||
@registry.login.join(" ")
|
@registry.login.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -24,7 +24,18 @@ class CommandsRegistryTest < ActiveSupport::TestCase
|
|||||||
@config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ]
|
@config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ]
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker login hub.docker.com -u dhh -p more-secret",
|
"docker login hub.docker.com -u \"dhh\" -p \"more-secret\"",
|
||||||
|
@registry.login.join(" ")
|
||||||
|
ensure
|
||||||
|
ENV.delete("KAMAL_REGISTRY_PASSWORD")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "registry login escape password" do
|
||||||
|
ENV["KAMAL_REGISTRY_PASSWORD"] = "more-secret'\""
|
||||||
|
@config[:registry]["password"] = [ "KAMAL_REGISTRY_PASSWORD" ]
|
||||||
|
|
||||||
|
assert_equal \
|
||||||
|
"docker login hub.docker.com -u \"dhh\" -p \"more-secret'\\\"\"",
|
||||||
@registry.login.join(" ")
|
@registry.login.join(" ")
|
||||||
ensure
|
ensure
|
||||||
ENV.delete("KAMAL_REGISTRY_PASSWORD")
|
ENV.delete("KAMAL_REGISTRY_PASSWORD")
|
||||||
@@ -35,7 +46,7 @@ class CommandsRegistryTest < ActiveSupport::TestCase
|
|||||||
@config[:registry]["username"] = [ "KAMAL_REGISTRY_USERNAME" ]
|
@config[:registry]["username"] = [ "KAMAL_REGISTRY_USERNAME" ]
|
||||||
|
|
||||||
assert_equal \
|
assert_equal \
|
||||||
"docker login hub.docker.com -u also-secret -p secret",
|
"docker login hub.docker.com -u \"also-secret\" -p \"secret\"",
|
||||||
@registry.login.join(" ")
|
@registry.login.join(" ")
|
||||||
ensure
|
ensure
|
||||||
ENV.delete("KAMAL_REGISTRY_USERNAME")
|
ENV.delete("KAMAL_REGISTRY_USERNAME")
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"monitoring" => {
|
"monitoring" => {
|
||||||
|
"service" => "custom-monitoring",
|
||||||
"image" => "monitoring:latest",
|
"image" => "monitoring:latest",
|
||||||
"roles" => [ "web" ],
|
"roles" => [ "web" ],
|
||||||
"port" => "4321:4321",
|
"port" => "4321:4321",
|
||||||
@@ -72,6 +73,7 @@ class ConfigurationAccessoryTest < ActiveSupport::TestCase
|
|||||||
test "service name" do
|
test "service name" do
|
||||||
assert_equal "app-mysql", @config.accessory(:mysql).service_name
|
assert_equal "app-mysql", @config.accessory(:mysql).service_name
|
||||||
assert_equal "app-redis", @config.accessory(:redis).service_name
|
assert_equal "app-redis", @config.accessory(:redis).service_name
|
||||||
|
assert_equal "custom-monitoring", @config.accessory(:monitoring).service_name
|
||||||
end
|
end
|
||||||
|
|
||||||
test "port" do
|
test "port" do
|
||||||
|
|||||||
@@ -148,4 +148,14 @@ class ConfigurationBuilderTest < ActiveSupport::TestCase
|
|||||||
|
|
||||||
assert_equal "..", @config_with_builder_option.builder.context
|
assert_equal "..", @config_with_builder_option.builder.context
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "ssh" do
|
||||||
|
assert_nil @config.builder.ssh
|
||||||
|
end
|
||||||
|
|
||||||
|
test "setting ssh params" do
|
||||||
|
@deploy_with_builder_option[:builder] = { "ssh" => 'default=$SSH_AUTH_SOCK' }
|
||||||
|
|
||||||
|
assert_equal 'default=$SSH_AUTH_SOCK', @config_with_builder_option.builder.ssh
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -42,6 +42,16 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "service name valid" do
|
||||||
|
assert Kamal::Configuration.new(@deploy.tap { _1[:service] = "hey-app1_primary" }).valid?
|
||||||
|
end
|
||||||
|
|
||||||
|
test "service name invalid" do
|
||||||
|
assert_raise(ArgumentError) do
|
||||||
|
Kamal::Configuration.new @deploy.tap { _1[:service] = "app.com" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "roles" do
|
test "roles" do
|
||||||
assert_equal %w[ web ], @config.roles.collect(&:name)
|
assert_equal %w[ web ], @config.roles.collect(&:name)
|
||||||
assert_equal %w[ web workers ], @config_with_roles.roles.collect(&:name)
|
assert_equal %w[ web workers ], @config_with_roles.roles.collect(&:name)
|
||||||
@@ -290,16 +300,16 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
|
|
||||||
test "primary role" do
|
test "primary role" do
|
||||||
assert_equal "web", @config.primary_role
|
assert_equal "web", @config.primary_role.name
|
||||||
|
|
||||||
config = Kamal::Configuration.new(@deploy_with_roles.deep_merge({
|
config = Kamal::Configuration.new(@deploy_with_roles.deep_merge({
|
||||||
servers: { "alternate_web" => { "hosts" => [ "1.1.1.4", "1.1.1.5" ] } },
|
servers: { "alternate_web" => { "hosts" => [ "1.1.1.4", "1.1.1.5" ] } },
|
||||||
primary_role: "alternate_web" } ))
|
primary_role: "alternate_web" } ))
|
||||||
|
|
||||||
|
|
||||||
assert_equal "alternate_web", config.primary_role
|
assert_equal "alternate_web", config.primary_role.name
|
||||||
assert_equal "1.1.1.4", config.primary_host
|
assert_equal "1.1.1.4", config.primary_host
|
||||||
assert config.role(:alternate_web).primary?
|
assert config.role(:alternate_web).primary?
|
||||||
assert config.role(:alternate_web).running_traefik?
|
assert config.role(:alternate_web).running_traefik?
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -309,4 +319,12 @@ class ConfigurationTest < ActiveSupport::TestCase
|
|||||||
end
|
end
|
||||||
assert_match /bar isn't defined/, error.message
|
assert_match /bar isn't defined/, error.message
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "retain_containers" do
|
||||||
|
assert_equal 5, @config.retain_containers
|
||||||
|
config = Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 2))
|
||||||
|
assert_equal 2, config.retain_containers
|
||||||
|
|
||||||
|
assert_raises(ArgumentError) { Kamal::Configuration.new(@deploy_with_roles.merge(retain_containers: 0)) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
17
test/fixtures/deploy_with_low_percentage_boot_strategy.yml
vendored
Normal file
17
test/fixtures/deploy_with_low_percentage_boot_strategy.yml
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
service: app
|
||||||
|
image: dhh/app
|
||||||
|
servers:
|
||||||
|
web:
|
||||||
|
- "1.1.1.1"
|
||||||
|
- "1.1.1.2"
|
||||||
|
workers:
|
||||||
|
- "1.1.1.3"
|
||||||
|
- "1.1.1.4"
|
||||||
|
|
||||||
|
registry:
|
||||||
|
username: user
|
||||||
|
password: pw
|
||||||
|
|
||||||
|
boot:
|
||||||
|
limit: 1%
|
||||||
|
wait: 2
|
||||||
@@ -13,5 +13,5 @@ registry:
|
|||||||
password: pw
|
password: pw
|
||||||
|
|
||||||
boot:
|
boot:
|
||||||
limit: 25%
|
limit: 1%
|
||||||
wait: 2
|
wait: 2
|
||||||
|
|||||||
3
test/integration/docker/deployer/app/.kamal/hooks/docker-setup
Executable file
3
test/integration/docker/deployer/app/.kamal/hooks/docker-setup
Executable file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
echo "Docker set up!"
|
||||||
|
mkdir -p /tmp/${TEST_ID} && touch /tmp/${TEST_ID}/docker-setup
|
||||||
@@ -24,9 +24,10 @@ traefik:
|
|||||||
args:
|
args:
|
||||||
accesslog: true
|
accesslog: true
|
||||||
accesslog.format: json
|
accesslog.format: json
|
||||||
image: registry:4443/traefik:v2.9
|
image: registry:4443/traefik:v2.10
|
||||||
accessories:
|
accessories:
|
||||||
busybox:
|
busybox:
|
||||||
|
service: custom-busybox
|
||||||
image: registry:4443/busybox:1.36.0
|
image: registry:4443/busybox:1.36.0
|
||||||
cmd: sh -c 'echo "Starting busybox..."; trap exit term; while true; do sleep 1; done'
|
cmd: sh -c 'echo "Starting busybox..."; trap exit term; while true; do sleep 1; done'
|
||||||
roles:
|
roles:
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ push_image_to_registry_4443() {
|
|||||||
|
|
||||||
install_kamal
|
install_kamal
|
||||||
push_image_to_registry_4443 nginx 1-alpine-slim
|
push_image_to_registry_4443 nginx 1-alpine-slim
|
||||||
push_image_to_registry_4443 traefik v2.9
|
push_image_to_registry_4443 traefik v2.10
|
||||||
push_image_to_registry_4443 busybox 1.36.0
|
push_image_to_registry_4443 busybox 1.36.0
|
||||||
|
|
||||||
# .ssh is on a shared volume that persists between runs. Clean it up as the
|
# .ssh is on a shared volume that persists between runs. Clean it up as the
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class LockTest < IntegrationTest
|
|||||||
assert_match /Locked by: Deployer at .*\nVersion: #{latest_app_version}\nMessage: Integration Tests/m, status
|
assert_match /Locked by: Deployer at .*\nVersion: #{latest_app_version}\nMessage: Integration Tests/m, status
|
||||||
|
|
||||||
error = kamal :deploy, capture: true, raise_on_error: false
|
error = kamal :deploy, capture: true, raise_on_error: false
|
||||||
assert_match /Deploy lock found/m, error
|
assert_match /Deploy lock found. Run 'kamal lock help' for more information/m, error
|
||||||
|
|
||||||
kamal :lock, :release
|
kamal :lock, :release
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class MainTest < IntegrationTest
|
|||||||
assert_match /Traefik Host: vm2/, details
|
assert_match /Traefik Host: vm2/, details
|
||||||
assert_match /App Host: vm1/, details
|
assert_match /App Host: vm1/, details
|
||||||
assert_match /App Host: vm2/, details
|
assert_match /App Host: vm2/, details
|
||||||
assert_match /traefik:v2.9/, details
|
assert_match /traefik:v2.10/, details
|
||||||
assert_match /registry:4443\/app:#{first_version}/, details
|
assert_match /registry:4443\/app:#{first_version}/, details
|
||||||
|
|
||||||
audit = kamal :audit, capture: true
|
audit = kamal :audit, capture: true
|
||||||
@@ -60,6 +60,19 @@ class MainTest < IntegrationTest
|
|||||||
assert_equal({ "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "cord"=>"/tmp/kamal-cord", "log_lines" => 50, "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1" }, config[:healthcheck])
|
assert_equal({ "path" => "/up", "port" => 3000, "max_attempts" => 7, "exposed_port" => 3999, "cord"=>"/tmp/kamal-cord", "log_lines" => 50, "cmd"=>"wget -qO- http://localhost > /dev/null || exit 1" }, config[:healthcheck])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "setup and remove" do
|
||||||
|
# Check remove completes when nothing has been setup yet
|
||||||
|
kamal :remove, "-y"
|
||||||
|
assert_no_images_or_containers
|
||||||
|
|
||||||
|
kamal :envify
|
||||||
|
kamal :setup
|
||||||
|
assert_images_and_containers
|
||||||
|
|
||||||
|
kamal :remove, "-y"
|
||||||
|
assert_no_images_or_containers
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def assert_local_env_file(contents)
|
def assert_local_env_file(contents)
|
||||||
assert_equal contents, deployer_exec("cat .env", capture: true)
|
assert_equal contents, deployer_exec("cat .env", capture: true)
|
||||||
@@ -84,4 +97,22 @@ class MainTest < IntegrationTest
|
|||||||
|
|
||||||
assert_equal "200", Net::HTTP.get_response(URI.parse("http://localhost:12345/versions/.hidden")).code
|
assert_equal "200", Net::HTTP.get_response(URI.parse("http://localhost:12345/versions/.hidden")).code
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def vm1_image_ids
|
||||||
|
docker_compose("exec vm1 docker image ls -q", capture: true).strip.split("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def vm1_container_ids
|
||||||
|
docker_compose("exec vm1 docker ps -a -q", capture: true).strip.split("\n")
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_no_images_or_containers
|
||||||
|
assert vm1_image_ids.empty?
|
||||||
|
assert vm1_container_ids.empty?
|
||||||
|
end
|
||||||
|
|
||||||
|
def assert_images_and_containers
|
||||||
|
assert vm1_image_ids.any?
|
||||||
|
assert vm1_container_ids.any?
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -52,11 +52,11 @@ class TraefikTest < IntegrationTest
|
|||||||
|
|
||||||
private
|
private
|
||||||
def assert_traefik_running
|
def assert_traefik_running
|
||||||
assert_match /traefik:v2.9 "\/entrypoint.sh/, traefik_details
|
assert_match /traefik:v2.10 "\/entrypoint.sh/, traefik_details
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_traefik_not_running
|
def assert_traefik_not_running
|
||||||
refute_match /traefik:v2.9 "\/entrypoint.sh/, traefik_details
|
refute_match /traefik:v2.10 "\/entrypoint.sh/, traefik_details
|
||||||
end
|
end
|
||||||
|
|
||||||
def traefik_details
|
def traefik_details
|
||||||
|
|||||||
Reference in New Issue
Block a user