From 07646bc020607a1a3c94b5a81217f45f438cb859 Mon Sep 17 00:00:00 2001 From: dhh Date: Sat, 16 Sep 2023 09:51:45 -0700 Subject: [PATCH 1/5] Extract Cord, Assets, and Execution concerns from App It was getting crowded! --- lib/kamal/commands/app.rb | 91 +---------------------------- lib/kamal/commands/app/assets.rb | 51 ++++++++++++++++ lib/kamal/commands/app/cord.rb | 22 +++++++ lib/kamal/commands/app/execution.rb | 27 +++++++++ lib/kamal/commands/base.rb | 6 -- 5 files changed, 102 insertions(+), 95 deletions(-) create mode 100644 lib/kamal/commands/app/assets.rb create mode 100644 lib/kamal/commands/app/cord.rb create mode 100644 lib/kamal/commands/app/execution.rb diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 5f0744cc..e544ca94 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, Cord, Execution + ACTIVE_DOCKER_STATUSES = [ :running, :restarting ] attr_reader :role, :role_config @@ -64,33 +66,6 @@ class Kamal::Commands::App < Kamal::Commands::Base 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 @@ -151,53 +126,6 @@ class Kamal::Commands::App < Kamal::Commands::Base [: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) @@ -221,19 +149,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/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/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 From 76a3086569ddbda6ea169ac0f9f26c18198a12b4 Mon Sep 17 00:00:00 2001 From: dhh Date: Sat, 16 Sep 2023 09:52:54 -0700 Subject: [PATCH 2/5] Group related methods with spacing --- lib/kamal/commands/app.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index e544ca94..e54cf8c7 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -84,6 +84,7 @@ 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 @@ -106,6 +107,7 @@ class Kamal::Commands::App < Kamal::Commands::Base docker :container, :prune, "--force", *filter_args end + def list_images docker :image, :ls, config.repository end @@ -118,6 +120,7 @@ class Kamal::Commands::App < Kamal::Commands::Base docker :tag, config.absolute_image, config.latest_image end + def make_env_directory make_directory role_config.host_env_directory end From 3ae855ef28b62ff44c1524ea0b7de531200dd7bf Mon Sep 17 00:00:00 2001 From: dhh Date: Sat, 16 Sep 2023 09:53:03 -0700 Subject: [PATCH 3/5] Explain method better --- lib/kamal/cli/app.rb | 2 +- lib/kamal/commands/app.rb | 2 +- test/commands/app_test.rb | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) 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 e54cf8c7..23aeb2f6 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -116,7 +116,7 @@ class Kamal::Commands::App < Kamal::Commands::Base docker :image, :prune, "--all", "--force", *filter_args end - def tag_current_as_latest + def tag_current_image_as_latest docker :tag, config.absolute_image, config.latest_image 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 From d303fcc621551b1aa4653995632eec5cc04564be Mon Sep 17 00:00:00 2001 From: dhh Date: Sat, 16 Sep 2023 09:58:09 -0700 Subject: [PATCH 4/5] Extract Containers and Images concerns --- lib/kamal/commands/app.rb | 38 +--------------------------- lib/kamal/commands/app/containers.rb | 23 +++++++++++++++++ lib/kamal/commands/app/images.rb | 13 ++++++++++ 3 files changed, 37 insertions(+), 37 deletions(-) create mode 100644 lib/kamal/commands/app/containers.rb create mode 100644 lib/kamal/commands/app/images.rb diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 23aeb2f6..9cc1bcfa 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -1,5 +1,5 @@ class Kamal::Commands::App < Kamal::Commands::Base - include Assets, Cord, Execution + include Assets, Containers, Cord, Execution, Images ACTIVE_DOCKER_STATUSES = [ :running, :restarting ] @@ -85,42 +85,6 @@ class Kamal::Commands::App < Kamal::Commands::Base 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_image_as_latest - docker :tag, config.absolute_image, config.latest_image - end - - def make_env_directory make_directory role_config.host_env_directory 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/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 From 6b5c5f06502a4ef8de1a0dba7ef908edb1565e51 Mon Sep 17 00:00:00 2001 From: dhh Date: Sat, 16 Sep 2023 10:03:28 -0700 Subject: [PATCH 5/5] Extract Logging too Leave only the core essentials in App --- lib/kamal/commands/app.rb | 24 +++--------------------- lib/kamal/commands/app/logging.rb | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) create mode 100644 lib/kamal/commands/app/logging.rb diff --git a/lib/kamal/commands/app.rb b/lib/kamal/commands/app.rb index 9cc1bcfa..f3ebe567 100644 --- a/lib/kamal/commands/app.rb +++ b/lib/kamal/commands/app.rb @@ -1,5 +1,5 @@ class Kamal::Commands::App < Kamal::Commands::Base - include Assets, Containers, Cord, Execution, Images + include Assets, Containers, Cord, Execution, Images, Logging ACTIVE_DOCKER_STATUSES = [ :running, :restarting ] @@ -48,24 +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 current_running_container_id docker :ps, "--quiet", *filter_args(statuses: ACTIVE_DOCKER_STATUSES), "--latest" end @@ -90,7 +72,7 @@ class Kamal::Commands::App < Kamal::Commands::Base end def remove_env_file - [:rm, "-f", role_config.host_env_file_path] + [ :rm, "-f", role_config.host_env_file_path ] end @@ -104,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) 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