diff --git a/lib/kamal/cli/app.rb b/lib/kamal/cli/app.rb index 278bd1af..251fb508 100644 --- a/lib/kamal/cli/app.rb +++ b/lib/kamal/cli/app.rb @@ -70,13 +70,15 @@ class Kamal::Cli::App < Kamal::Cli::Base desc "exec [CMD]", "Execute a custom command on servers (use --help to show options)" 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" def exec(cmd) + env = options[:env] case when options[:interactive] && options[:reuse] say "Get current version of running container...", :magenta unless options[:version] using_version(options[:version] || current_running_version) do |version| say "Launching interactive command with version #{version} via SSH from existing container on #{KAMAL.primary_host}...", :magenta - run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host) } + run_locally { exec KAMAL.app(role: KAMAL.primary_role).execute_in_existing_container_over_ssh(cmd, host: KAMAL.primary_host, env: env) } end when options[:interactive] @@ -84,7 +86,7 @@ class Kamal::Cli::App < Kamal::Cli::Base using_version(version_or_latest) do |version| say "Launching interactive command with version #{version} via SSH from new container on #{KAMAL.primary_host}...", :magenta run_locally do - exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host) + exec KAMAL.app(role: KAMAL.primary_role).execute_in_new_container_over_ssh(cmd, host: KAMAL.primary_host, env: env) end end @@ -98,7 +100,7 @@ class Kamal::Cli::App < Kamal::Cli::Base roles.each do |role| execute *KAMAL.auditor.record("Executed cmd '#{cmd}' on app version #{version}", role: role), verbosity: :debug - puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd)) + puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_existing_container(cmd, env: env)) end end end @@ -112,7 +114,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).execute_in_new_container(cmd)) + puts_by_host host, capture_with_info(*KAMAL.app(role: role).execute_in_new_container(cmd, env: env)) end end end diff --git a/lib/kamal/commands/app/execution.rb b/lib/kamal/commands/app/execution.rb index cb07075b..6d32b0c8 100644 --- a/lib/kamal/commands/app/execution.rb +++ b/lib/kamal/commands/app/execution.rb @@ -1,27 +1,29 @@ module Kamal::Commands::App::Execution - def execute_in_existing_container(*command, interactive: false) + def execute_in_existing_container(*command, interactive: false, env:) docker :exec, ("-it" if interactive), + *argumentize("--env", env), container_name, *command end - def execute_in_new_container(*command, interactive: false) + def execute_in_new_container(*command, interactive: false, env:) docker :run, ("-it" if interactive), "--rm", *role&.env_args, + *argumentize("--env", env), *config.volume_args, *role&.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 + def execute_in_existing_container_over_ssh(*command, host:, env:) + run_over_ssh execute_in_existing_container(*command, interactive: true, env: env), host: host end - def execute_in_new_container_over_ssh(*command, host:) - run_over_ssh execute_in_new_container(*command, interactive: true), host: host + def execute_in_new_container_over_ssh(*command, host:, env:) + run_over_ssh execute_in_new_container(*command, interactive: true, env: env), host: host end end diff --git a/test/commands/app_test.rb b/test/commands/app_test.rb index a15e0e7c..0abed6c5 100644 --- a/test/commands/app_test.rb +++ b/test/commands/app_test.rb @@ -174,36 +174,48 @@ class CommandsAppTest < ActiveSupport::TestCase test "execute in new container" do assert_equal \ "docker run --rm --env-file .kamal/env/roles/app-web.env dhh/app:999 bin/rails db:setup", - new_command.execute_in_new_container("bin/rails", "db:setup").join(" ") + new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") + end + + test "execute in new container with env" do + assert_equal \ + "docker run --rm --env-file .kamal/env/roles/app-web.env --env foo=\"bar\" dhh/app:999 bin/rails db:setup", + new_command.execute_in_new_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ") end test "execute in new container with custom options" do @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } } assert_equal \ "docker run --rm --env-file .kamal/env/roles/app-web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails db:setup", - new_command.execute_in_new_container("bin/rails", "db:setup").join(" ") + new_command.execute_in_new_container("bin/rails", "db:setup", env: {}).join(" ") end test "execute in existing container" do assert_equal \ "docker exec app-web-999 bin/rails db:setup", - new_command.execute_in_existing_container("bin/rails", "db:setup").join(" ") + new_command.execute_in_existing_container("bin/rails", "db:setup", env: {}).join(" ") + end + + test "execute in existing container with env" do + assert_equal \ + "docker exec --env foo=\"bar\" app-web-999 bin/rails db:setup", + new_command.execute_in_existing_container("bin/rails", "db:setup", env: { "foo" => "bar" }).join(" ") end test "execute in new container over ssh" do assert_match %r{docker run -it --rm --env-file .kamal/env/roles/app-web.env dhh/app:999 bin/rails c}, - new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1") + new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1", env: {}) end test "execute in new container with custom options over ssh" do @config[:servers] = { "web" => { "hosts" => [ "1.1.1.1" ], "options" => { "mount" => "somewhere", "cap-add" => true } } } assert_match %r{docker run -it --rm --env-file .kamal/env/roles/app-web.env --mount \"somewhere\" --cap-add dhh/app:999 bin/rails c}, - new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1") + new_command.execute_in_new_container_over_ssh("bin/rails", "c", host: "app-1", env: {}) end test "execute in existing container over ssh" do assert_match %r{docker exec -it app-web-999 bin/rails c}, - new_command.execute_in_existing_container_over_ssh("bin/rails", "c", host: "app-1") + new_command.execute_in_existing_container_over_ssh("bin/rails", "c", host: "app-1", env: {}) end test "run over ssh" do