diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 3d043d72..97ed3838 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -9,7 +9,7 @@ class Kamal::Cli::App < Kamal::Cli::Base on(KAMAL.hosts) do execute *KAMAL.auditor.record("Tagging #{KAMAL.config.absolute_image} as the latest image"), verbosity: :debug - execute *KAMAL.app.tag_current_as_latest + execute *KAMAL.app.tag_current_image_as_latest KAMAL.roles_on(host).each do |role| app = KAMAL.app(role: role) diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 5f0744cc..f3ebe567 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -1,4 +1,6 @@ class Kamal::Commands::App < Kamal::Commands::Base + include Assets, Containers, Cord, Execution, Images, Logging + ACTIVE_DOCKER_STATUSES = [ :running, :restarting ] attr_reader :role, :role_config @@ -46,51 +48,6 @@ class Kamal::Commands::App < Kamal::Commands::Base end - def logs(since: nil, lines: nil, grep: nil) - pipe \ - current_running_container_id, - "xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1", - ("grep '#{grep}'" if grep) - end - - def follow_logs(host:, grep: nil) - run_over_ssh \ - pipe( - current_running_container_id, - "xargs docker logs --timestamps --tail 10 --follow 2>&1", - (%(grep "#{grep}") if grep) - ), - host: host - end - - - def execute_in_existing_container(*command, interactive: false) - docker :exec, - ("-it" if interactive), - container_name, - *command - end - - def execute_in_new_container(*command, interactive: false) - docker :run, - ("-it" if interactive), - "--rm", - *role_config&.env_args, - *config.volume_args, - *role_config&.option_args, - config.absolute_image, - *command - end - - def execute_in_existing_container_over_ssh(*command, host:) - run_over_ssh execute_in_existing_container(*command, interactive: true), host: host - end - - def execute_in_new_container_over_ssh(*command, host:) - run_over_ssh execute_in_new_container(*command, interactive: true), host: host - end - - def current_running_container_id docker :ps, "--quiet", *filter_args(statuses: ACTIVE_DOCKER_STATUSES), "--latest" end @@ -109,95 +66,15 @@ class Kamal::Commands::App < Kamal::Commands::Base %(while read line; do echo ${line##{role_config.container_prefix}-}; done) # Extract SHA from "service-role-dest-SHA" end - def list_containers - docker :container, :ls, "--all", *filter_args - end - - def list_container_names - [ *list_containers, "--format", "'{{ .Names }}'" ] - end - - def remove_container(version:) - pipe \ - container_id_for(container_name: container_name(version)), - xargs(docker(:container, :rm)) - end - - def rename_container(version:, new_version:) - docker :rename, container_name(version), container_name(new_version) - end - - def remove_containers - docker :container, :prune, "--force", *filter_args - end - - def list_images - docker :image, :ls, config.repository - end - - def remove_images - docker :image, :prune, "--all", "--force", *filter_args - end - - def tag_current_as_latest - docker :tag, config.absolute_image, config.latest_image - end def make_env_directory make_directory role_config.host_env_directory end def remove_env_file - [:rm, "-f", role_config.host_env_file_path] + [ :rm, "-f", role_config.host_env_file_path ] end - def cord(version:) - pipe \ - 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}'"] - end - - def tie_cord(cord) - create_empty_file(cord) - end - - def cut_cord(cord) - 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 1000000"), - 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, continue_on_error: true) - commands << copy_contents(old_extracted_path, new_volume_path, continue_on_error: true) - end - - chain *commands - end - - def clean_up_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) @@ -209,7 +86,7 @@ class Kamal::Commands::App < Kamal::Commands::Base end def service_role_dest - [config.service, role, config.destination].compact.join("-") + [ config.service, role, config.destination ].compact.join("-") end def filters(statuses: nil) @@ -221,19 +98,4 @@ class Kamal::Commands::App < Kamal::Commands::Base end end end - - def find_and_remove_older_siblings(path) - [ - :find, - Pathname.new(path).dirname.to_s, - "-maxdepth 1", - "-name", "'#{role_config.container_prefix}-*'", - "!", "-name", Pathname.new(path).basename.to_s, - "-exec rm -rf \"{}\" +" - ] - end - - def copy_contents(source, destination, continue_on_error: false) - [ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error)] - end end diff --git a/lib/kamal/commands/app/assets.rb b/lib/kamal/commands/app/assets.rb new file mode 100644 index 00000000..cc65627a --- /dev/null +++ b/lib/kamal/commands/app/assets.rb @@ -0,0 +1,51 @@ +module Kamal::Commands::App::Assets + 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 1000000"), + 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, continue_on_error: true) + commands << copy_contents(old_extracted_path, new_volume_path, continue_on_error: true) + end + + chain *commands + end + + def clean_up_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 find_and_remove_older_siblings(path) + [ + :find, + Pathname.new(path).dirname.to_s, + "-maxdepth 1", + "-name", "'#{role_config.container_prefix}-*'", + "!", "-name", Pathname.new(path).basename.to_s, + "-exec rm -rf \"{}\" +" + ] + end + + def copy_contents(source, destination, continue_on_error: false) + [ :cp, "-rnT", "#{source}", destination, *("|| true" if continue_on_error)] + end +end diff --git a/lib/kamal/commands/app/containers.rb b/lib/kamal/commands/app/containers.rb new file mode 100644 index 00000000..a62d9a35 --- /dev/null +++ b/lib/kamal/commands/app/containers.rb @@ -0,0 +1,23 @@ +module Kamal::Commands::App::Containers + def list_containers + docker :container, :ls, "--all", *filter_args + end + + def list_container_names + [ *list_containers, "--format", "'{{ .Names }}'" ] + end + + def remove_container(version:) + pipe \ + container_id_for(container_name: container_name(version)), + xargs(docker(:container, :rm)) + end + + def rename_container(version:, new_version:) + docker :rename, container_name(version), container_name(new_version) + end + + def remove_containers + docker :container, :prune, "--force", *filter_args + end +end diff --git a/lib/kamal/commands/app/cord.rb b/lib/kamal/commands/app/cord.rb new file mode 100644 index 00000000..ef722d66 --- /dev/null +++ b/lib/kamal/commands/app/cord.rb @@ -0,0 +1,22 @@ +module Kamal::Commands::App::Cord + def cord(version:) + pipe \ + 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}'"] + end + + def tie_cord(cord) + create_empty_file(cord) + end + + def cut_cord(cord) + remove_directory(cord) + end + + private + def create_empty_file(file) + chain \ + make_directory_for(file), + [:touch, file] + end +end diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb new file mode 100644 index 00000000..ff387726 --- /dev/null +++ b/lib/kamal/commands/app/execution.rb @@ -0,0 +1,27 @@ +module Kamal::Commands::App::Execution + def execute_in_existing_container(*command, interactive: false) + docker :exec, + ("-it" if interactive), + container_name, + *command + end + + def execute_in_new_container(*command, interactive: false) + docker :run, + ("-it" if interactive), + "--rm", + *role_config&.env_args, + *config.volume_args, + *role_config&.option_args, + config.absolute_image, + *command + end + + def execute_in_existing_container_over_ssh(*command, host:) + run_over_ssh execute_in_existing_container(*command, interactive: true), host: host + end + + def execute_in_new_container_over_ssh(*command, host:) + run_over_ssh execute_in_new_container(*command, interactive: true), host: host + end +end diff --git a/lib/kamal/commands/app/images.rb b/lib/kamal/commands/app/images.rb new file mode 100644 index 00000000..9b1ae0b8 --- /dev/null +++ b/lib/kamal/commands/app/images.rb @@ -0,0 +1,13 @@ +module Kamal::Commands::App::Images + def list_images + docker :image, :ls, config.repository + end + + def remove_images + docker :image, :prune, "--all", "--force", *filter_args + end + + def tag_current_image_as_latest + docker :tag, config.absolute_image, config.latest_image + end +end diff --git a/lib/kamal/commands/app/logging.rb b/lib/kamal/commands/app/logging.rb new file mode 100644 index 00000000..d20eda2c --- /dev/null +++ b/lib/kamal/commands/app/logging.rb @@ -0,0 +1,18 @@ +module Kamal::Commands::App::Logging + def logs(since: nil, lines: nil, grep: nil) + pipe \ + current_running_container_id, + "xargs docker logs#{" --since #{since}" if since}#{" --tail #{lines}" if lines} 2>&1", + ("grep '#{grep}'" if grep) + end + + def follow_logs(host:, grep: nil) + run_over_ssh \ + pipe( + current_running_container_id, + "xargs docker logs --timestamps --tail 10 --follow 2>&1", + (%(grep "#{grep}") if grep) + ), + host: host + end +end diff --git a/lib/kamal/commands/base.rb b/lib/kamal/commands/base.rb index ff31c747..7552a92d 100644 --- a/lib/kamal/commands/base.rb +++ b/lib/kamal/commands/base.rb @@ -73,11 +73,5 @@ module Kamal::Commands def tags(**details) Kamal::Tags.from_config(config, **details) end - - def create_empty_file(file) - chain \ - make_directory_for(file), - [:touch, file] - end end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 278dcaa1..d9d78bb8 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -317,10 +317,10 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.remove_images.join(" ") end - test "tag_current_as_latest" do + test "tag_current_image_as_latest" do assert_equal \ "docker tag dhh/app:999 dhh/app:latest", - new_command.tag_current_as_latest.join(" ") + new_command.tag_current_image_as_latest.join(" ") end test "make_env_directory" do