Compare commits
1 Commits
fix-per-ro
...
accumulati
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c122f97181 |
@@ -20,6 +20,9 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
auditor = KAMAL.auditor(role: role)
|
||||
role_config = KAMAL.config.role(role)
|
||||
|
||||
execute *app.extract_assets if role_config.assets?
|
||||
|
||||
|
||||
if capture_with_info(*app.container_id_for_version(version), raise_on_non_zero_exit: false).present?
|
||||
tmp_version = "#{version}_replaced_#{SecureRandom.hex(8)}"
|
||||
info "Renaming container #{version} to #{tmp_version} as already deployed on #{host}"
|
||||
@@ -27,13 +30,14 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
execute *app.rename_container(version: version, new_version: tmp_version)
|
||||
end
|
||||
|
||||
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
||||
|
||||
old_version = capture_with_info(*app.current_running_version, raise_on_non_zero_exit: false).strip
|
||||
original_old_version = old_version.gsub(/_replaced_[a-f0-9]{16}$/, "")
|
||||
|
||||
if role_config.uses_cord?
|
||||
execute *app.tie_cord(role_config.cord_host_file)
|
||||
end
|
||||
execute *app.sync_asset_volumes(old_version: original_old_version) if role_config.assets?
|
||||
|
||||
execute *app.tie_cord(role_config.cord_host_file) if role_config.uses_cord?
|
||||
|
||||
execute *auditor.record("Booted app version #{version}"), verbosity: :debug
|
||||
|
||||
execute *app.run(hostname: "#{host}-#{SecureRandom.hex(6)}")
|
||||
|
||||
@@ -47,7 +51,10 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
||||
Kamal::Utils::HealthcheckPoller.wait_for_unhealthy(pause_after_ready: true) { capture_with_info(*app.status(version: old_version)) }
|
||||
end
|
||||
end
|
||||
|
||||
execute *app.stop(version: old_version), raise_on_non_zero_exit: false
|
||||
|
||||
execute *app.cleanup_assets if role_config.assets?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,6 +20,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
*role_config.health_check_args,
|
||||
*config.logging_args,
|
||||
*config.volume_args,
|
||||
*role_config.asset_volume_args,
|
||||
*role_config.label_args,
|
||||
*role_config.option_args,
|
||||
config.absolute_image,
|
||||
@@ -105,7 +106,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
def list_versions(*docker_args, statuses: nil)
|
||||
pipe \
|
||||
docker(:ps, *filter_args(statuses: statuses), *docker_args, "--format", '"{{.Names}}"'),
|
||||
%(while read line; do echo ${line##{role_config.full_name}-}; done) # Extract SHA from "service-role-dest-SHA"
|
||||
%(while read line; do echo ${line##{role_config.container_prefix}-}; done) # Extract SHA from "service-role-dest-SHA"
|
||||
end
|
||||
|
||||
def list_containers
|
||||
@@ -153,7 +154,7 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
def cord(version:)
|
||||
pipe \
|
||||
docker(:inspect, "-f '{{ range .Mounts }}{{ .Source }} {{ .Destination }} {{ end }}'", container_name(version)),
|
||||
[:awk, "'$2 == \"#{role_config.cord_container_directory}\" {print $1}'"]
|
||||
[:awk, "'$2 == \"#{role_config.cord_volume.container_path}\" {print $1}'"]
|
||||
end
|
||||
|
||||
def tie_cord(cord)
|
||||
@@ -164,9 +165,43 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
remove_directory(cord)
|
||||
end
|
||||
|
||||
def extract_assets
|
||||
asset_container = "#{role_config.container_prefix}-assets"
|
||||
|
||||
combine \
|
||||
make_directory(role_config.asset_extracted_path),
|
||||
[*docker(:stop, "-t 1", asset_container, "2> /dev/null"), "|| true"],
|
||||
docker(:run, "--name", asset_container, "--detach", "--rm", config.latest_image, "sleep infinity"),
|
||||
docker(:cp, "-L", "#{asset_container}:#{role_config.asset_path}/.", role_config.asset_extracted_path),
|
||||
docker(:stop, "-t 1", asset_container),
|
||||
by: "&&"
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
end
|
||||
|
||||
commands = [make_directory(new_volume_path), copy_contents(new_extracted_path, new_volume_path)]
|
||||
|
||||
if old_version.present?
|
||||
commands << copy_contents(new_extracted_path, old_volume_path)
|
||||
commands << copy_contents(old_extracted_path, new_volume_path)
|
||||
end
|
||||
|
||||
chain *commands
|
||||
end
|
||||
|
||||
def cleanup_assets
|
||||
chain \
|
||||
find_and_remove_older_siblings(role_config.asset_extracted_path),
|
||||
find_and_remove_older_siblings(role_config.asset_volume_path)
|
||||
end
|
||||
|
||||
private
|
||||
def container_name(version = nil)
|
||||
[ role_config.full_name, version || config.version ].compact.join("-")
|
||||
[ role_config.container_prefix, version || config.version ].compact.join("-")
|
||||
end
|
||||
|
||||
def filter_args(statuses: nil)
|
||||
@@ -186,4 +221,19 @@ class Kamal::Commands::App < Kamal::Commands::Base
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def find_and_remove_older_siblings(path)
|
||||
[
|
||||
:find,
|
||||
Pathname.new(path).dirname,
|
||||
"-maxdepth 1",
|
||||
"-name", "'#{role_config.container_prefix}-*'",
|
||||
"!", "-name", Pathname.new(path).basename,
|
||||
"-exec rm -rf \"{}\" +"
|
||||
]
|
||||
end
|
||||
|
||||
def copy_contents(source, destination)
|
||||
[ :cp, "-rn", "#{source}/*", destination ]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -211,6 +211,10 @@ class Kamal::Configuration
|
||||
@run_id ||= SecureRandom.hex(16)
|
||||
end
|
||||
|
||||
def asset_path
|
||||
raw_config.asset_path
|
||||
end
|
||||
|
||||
private
|
||||
# Will raise ArgumentError if any required config keys are missing
|
||||
def ensure_required_keys_present
|
||||
|
||||
@@ -48,11 +48,15 @@ class Kamal::Configuration::Role
|
||||
argumentize "--env-file", host_env_file_path
|
||||
end
|
||||
|
||||
def asset_volume_args
|
||||
asset_volume&.docker_args
|
||||
end
|
||||
|
||||
def health_check_args(cord: true)
|
||||
if health_check_cmd.present?
|
||||
if cord && uses_cord?
|
||||
optionize({ "health-cmd" => health_check_cmd_with_cord, "health-interval" => health_check_interval })
|
||||
.concat(["--volume", "#{cord_host_directory}:#{cord_container_directory}"])
|
||||
.concat(cord_volume.docker_args)
|
||||
else
|
||||
optionize({ "health-cmd" => health_check_cmd, "health-interval" => health_check_interval })
|
||||
end
|
||||
@@ -74,15 +78,23 @@ class Kamal::Configuration::Role
|
||||
end
|
||||
|
||||
def uses_cord?
|
||||
running_traefik? && cord_container_directory.present? && health_check_cmd.present?
|
||||
running_traefik? && cord_volume && health_check_cmd.present?
|
||||
end
|
||||
|
||||
def cord_host_directory
|
||||
File.join config.run_directory_as_docker_volume, "cords", [full_name, config.run_id].join("-")
|
||||
File.join config.run_directory_as_docker_volume, "cords", [container_prefix, config.run_id].join("-")
|
||||
end
|
||||
|
||||
def cord_volume
|
||||
if (cord = health_check_options["cord"])
|
||||
@cord_volume ||= Kamal::Configuration::Volume.new \
|
||||
host_path: File.join(config.run_directory, "cords", [container_prefix, config.run_id].join("-")),
|
||||
container_path: cord
|
||||
end
|
||||
end
|
||||
|
||||
def cord_host_file
|
||||
File.join cord_host_directory, CORD_FILE
|
||||
File.join cord_volume.host_path, CORD_FILE
|
||||
end
|
||||
|
||||
def cord_container_directory
|
||||
@@ -90,7 +102,7 @@ class Kamal::Configuration::Role
|
||||
end
|
||||
|
||||
def cord_container_file
|
||||
File.join cord_container_directory, CORD_FILE
|
||||
File.join cord_volume.container_path, CORD_FILE
|
||||
end
|
||||
|
||||
|
||||
@@ -110,10 +122,37 @@ class Kamal::Configuration::Role
|
||||
name.web? || specializations["traefik"]
|
||||
end
|
||||
|
||||
def full_name
|
||||
def container_name(version = nil)
|
||||
[ container_prefix, version || config.version ].compact.join("-")
|
||||
end
|
||||
|
||||
def container_prefix
|
||||
[ config.service, name, config.destination ].compact.join("-")
|
||||
end
|
||||
|
||||
def asset_path
|
||||
specializations["asset_path"] || config.asset_path
|
||||
end
|
||||
|
||||
def assets?
|
||||
asset_path.present? && running_traefik?
|
||||
end
|
||||
|
||||
def asset_volume(version = nil)
|
||||
if assets?
|
||||
Kamal::Configuration::Volume.new \
|
||||
host_path: asset_volume_path(version), container_path: asset_path
|
||||
end
|
||||
end
|
||||
|
||||
def asset_extracted_path(version = nil)
|
||||
File.join config.run_directory, "assets", "extracted", container_name(version)
|
||||
end
|
||||
|
||||
def asset_volume_path(version = nil)
|
||||
File.join config.run_directory, "assets", "volumes", container_name(version)
|
||||
end
|
||||
|
||||
private
|
||||
attr_accessor :config
|
||||
|
||||
|
||||
22
lib/kamal/configuration/volume.rb
Normal file
22
lib/kamal/configuration/volume.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
class Kamal::Configuration::Volume
|
||||
attr_reader :host_path, :container_path
|
||||
delegate :argumentize, to: Kamal::Utils
|
||||
|
||||
def initialize(host_path:, container_path:)
|
||||
@host_path = host_path
|
||||
@container_path = container_path
|
||||
end
|
||||
|
||||
def docker_args
|
||||
argumentize "--volume", "#{host_path_for_docker_volume}:#{container_path}"
|
||||
end
|
||||
|
||||
private
|
||||
def host_path_for_docker_volume
|
||||
if Pathname.new(host_path).absolute?
|
||||
host_path
|
||||
else
|
||||
File.join "$(pwd)", host_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -180,16 +180,15 @@ class ConfigurationRoleTest < ActiveSupport::TestCase
|
||||
assert !@config_with_roles.role(:workers).uses_cord?
|
||||
end
|
||||
|
||||
test "cord host directory" do
|
||||
assert_match %r{\$\(pwd\)/.kamal/cords/app-web-[0-9a-f]{32}}, @config_with_roles.role(:web).cord_host_directory
|
||||
end
|
||||
|
||||
test "cord host file" do
|
||||
assert_match %r{\$\(pwd\)/.kamal/cords/app-web-[0-9a-f]{32}/cord}, @config_with_roles.role(:web).cord_host_file
|
||||
assert_match %r{.kamal/cords/app-web-[0-9a-f]{32}/cord}, @config_with_roles.role(:web).cord_host_file
|
||||
end
|
||||
|
||||
test "cord container directory" do
|
||||
assert_equal "/tmp/kamal-cord", @config_with_roles.role(:web).cord_container_directory
|
||||
test "cord volume" do
|
||||
assert_equal "/tmp/kamal-cord", @config_with_roles.role(:web).cord_volume.container_path
|
||||
assert_match %r{.kamal/cords/app-web-[0-9a-f]{32}}, @config_with_roles.role(:web).cord_volume.host_path
|
||||
assert_equal "--volume", @config_with_roles.role(:web).cord_volume.docker_args[0]
|
||||
assert_match %r{\$\(pwd\)/.kamal/cords/app-web-[0-9a-f]{32}:/tmp/kamal-cord}, @config_with_roles.role(:web).cord_volume.docker_args[1]
|
||||
end
|
||||
|
||||
test "cord container file" do
|
||||
|
||||
@@ -4,4 +4,5 @@ COPY default.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
ARG COMMIT_SHA
|
||||
RUN echo $COMMIT_SHA > /usr/share/nginx/html/version
|
||||
RUN mkdir -p /usr/share/nginx/html/versions && echo "version" > /usr/share/nginx/html/versions/$COMMIT_SHA
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ env:
|
||||
CLEAR_TOKEN: '4321'
|
||||
secret:
|
||||
- SECRET_TOKEN
|
||||
asset_path: /usr/share/nginx/html/versions
|
||||
|
||||
registry:
|
||||
server: registry:4443
|
||||
|
||||
@@ -20,6 +20,8 @@ class MainTest < IntegrationTest
|
||||
assert_app_is_up version: second_version
|
||||
assert_hooks_ran "pre-connect", "pre-build", "pre-deploy", "post-deploy"
|
||||
|
||||
assert_accumulated_assets first_version, second_version
|
||||
|
||||
kamal :rollback, first_version
|
||||
assert_hooks_ran "pre-connect", "pre-deploy", "post-deploy"
|
||||
assert_app_is_up version: first_version
|
||||
@@ -69,4 +71,10 @@ class MainTest < IntegrationTest
|
||||
def assert_no_remote_env_file
|
||||
assert_equal "nofile", docker_compose("exec vm1 stat /root/.kamal/env/roles/app-web.env 2> /dev/null || echo nofile", capture: true)
|
||||
end
|
||||
|
||||
def assert_accumulated_assets(*versions)
|
||||
versions.each do |version|
|
||||
assert_equal "200", Net::HTTP.get_response(URI.parse("http://localhost:12345/versions/#{version}")).code
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user