Skip to content

Commit

Permalink
Merge pull request #470 from basecamp/extract-app-concerns
Browse files Browse the repository at this point in the history
Extract app concerns
  • Loading branch information
dhh authored Sep 16, 2023
2 parents e9ef13d + 6b5c5f0 commit 3a8eb0c
Show file tree
Hide file tree
Showing 10 changed files with 161 additions and 151 deletions.
2 changes: 1 addition & 1 deletion lib/kamal/cli/app.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ def boot

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)
Expand Down
146 changes: 4 additions & 142 deletions lib/kamal/commands/app.rb
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -46,51 +48,6 @@ def info
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
Expand All @@ -109,95 +66,15 @@ def list_versions(*docker_args, statuses: nil)
%(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]
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
[ :rm, "-f", role_config.host_env_file_path ]
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)
Expand All @@ -209,7 +86,7 @@ def filter_args(statuses: nil)
end

def service_role_dest
[config.service, role, config.destination].compact.join("-")
[ config.service, role, config.destination ].compact.join("-")
end

def filters(statuses: nil)
Expand All @@ -221,19 +98,4 @@ def filters(statuses: nil)
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
51 changes: 51 additions & 0 deletions lib/kamal/commands/app/assets.rb
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions lib/kamal/commands/app/containers.rb
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions lib/kamal/commands/app/cord.rb
Original file line number Diff line number Diff line change
@@ -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
27 changes: 27 additions & 0 deletions lib/kamal/commands/app/execution.rb
Original file line number Diff line number Diff line change
@@ -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
13 changes: 13 additions & 0 deletions lib/kamal/commands/app/images.rb
Original file line number Diff line number Diff line change
@@ -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
18 changes: 18 additions & 0 deletions lib/kamal/commands/app/logging.rb
Original file line number Diff line number Diff line change
@@ -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
6 changes: 0 additions & 6 deletions lib/kamal/commands/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,5 @@ def docker(*args)
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
4 changes: 2 additions & 2 deletions test/commands/app_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3a8eb0c

Please sign in to comment.