From dbe0c3a7f8269db005528b1afd18773e1321722f Mon Sep 17 00:00:00 2001 From: Ali Ismayilov <993934+aliismayilov@users.noreply.github.com> Date: Wed, 7 Aug 2024 15:32:01 +0200 Subject: [PATCH] Allow running detached app commands this is useful for long running rake tasks or scripts that can be run without having to keep open connection to the server. Example: ``` kamal app exec 'bin/rails db:backfill_task' --detach ``` --- lib/kamal/cli/app.rb | 4 +++- lib/kamal/commands/app/execution.rb | 3 ++- test/cli/app_test.rb | 6 ++++++ test/commands/app_test.rb | 6 ++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 74b7b4df..0217dbd1 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -94,9 +94,11 @@ class Kamal::Cli::App < 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 :reuse, type: :boolean, default: false, desc: "Reuse currently running container instead of starting a new one" option :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command" + option :detach, type: :boolean, default: false, desc: "Execute command in a detached container" def exec(*cmd) cmd = Kamal::Utils.join_commands(cmd) env = options[:env] + detach = options[:detach] case when options[:interactive] && options[:reuse] say "Get current version of running container...", :magenta unless options[:version] @@ -138,7 +140,7 @@ class Kamal::Cli::App < Kamal::Cli::Base roles.each do |role| execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}"), verbosity: :debug - puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env)) + puts_by_host host, capture_with_info(*KAMAL.app(role: role, host: host).execute_in_new_container(cmd, env: env, detach: detach)) end end end diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb index 4434c26a..d6fa04fd 100644 --- a/lib/kamal/commands/app/execution.rb +++ b/lib/kamal/commands/app/execution.rb @@ -7,9 +7,10 @@ module Kamal::Commands::App::Execution *command end - def execute_in_new_container(*command, interactive: false, env:) + def execute_in_new_container(*command, interactive: false, detach: false, env:) docker :run, ("-it" if interactive), + ("--detach" if detach), "--rm", "--network", "kamal", *role&.env_args(host), diff --git a/test/cli/app_test.rb b/test/cli/app_test.rb index 5e76179c..63eade9b 100644 --- a/test/cli/app_test.rb +++ b/test/cli/app_test.rb @@ -273,6 +273,12 @@ class CliAppTest < CliTestCase end end + test "exec detach" do + run_command("exec", "--detach", "ruby -v").tap do |output| + assert_match "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:latest ruby -v", output + end + end + test "exec with reuse" do run_command("exec", "--reuse", "ruby -v").tap do |output| assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting --filter ancestor=$(docker image ls --filter reference=dhh/app:latest --format '\\''{{.ID}}'\\'') ; docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --filter label=destination= --filter label=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index 1fb59e8a..abcefed1 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -234,6 +234,12 @@ class CommandsAppTest < ActiveSupport::TestCase new_command.execute_in_new_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ") end + test "execute in new detached container" do + assert_equal \ + "docker run --detach --rm --network kamal --env-file .kamal/apps/app/env/roles/web.env dhh/app:999 bin/rails db:setup", + new_command.execute_in_new_container("bin/rails", "db:setup", detach: true, env: {}).join(" ") + end + test "execute in new container with tags" do @config[:servers] = [ { "1.1.1.1" => "tag1" } ] @config[:env]["tags"] = { "tag1" => { "ENV1" => "value1" } }