Allow multiple arguments for exec commands
If you can have an alias like: ``` aliases: rails: app exec -p rails ``` Then `kamal rails db:migrate:status` will execute `kamal app exec -p rails db:migrate:status`. So this works, we'll allow multiple arguments `app exec` and `server exec` to accept multiple arguments. The arguments are combined by simply joining them with a space. This means that these are equivalent: ``` kamal app exec -p rails db:migrate:status kamal app exec -p "rails db:migrate:status" ``` If you want to pass an argument with spaces, you'll need to quote it: ``` kamal app exec -p "git commit -am \"My comment\"" kamal app exec -p git commit -am "\"My comment\"" ```
This commit is contained in:
@@ -71,11 +71,12 @@ class Kamal::Cli::App < Kamal::Cli::Base
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
desc "exec [CMD]", "Execute a custom command on servers within the app container (use --help to show options)"
|
desc "exec [CMD...]", "Execute a custom command on servers within the app container (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 :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 :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 :env, aliases: "-e", type: :hash, desc: "Set environment variables for the command"
|
||||||
def exec(cmd)
|
def exec(*cmd)
|
||||||
|
cmd = Kamal::Utils.join_commands(cmd)
|
||||||
env = options[:env]
|
env = options[:env]
|
||||||
case
|
case
|
||||||
when options[:interactive] && options[:reuse]
|
when options[:interactive] && options[:reuse]
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
class Kamal::Cli::Server < Kamal::Cli::Base
|
class Kamal::Cli::Server < Kamal::Cli::Base
|
||||||
desc "exec", "Run a custom command on the server (use --help to show options)"
|
desc "exec", "Run a custom command on the server (use --help to show options)"
|
||||||
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
|
option :interactive, type: :boolean, aliases: "-i", default: false, desc: "Run the command interactively (use for console/bash)"
|
||||||
def exec(cmd)
|
def exec(*cmd)
|
||||||
|
cmd = Kamal::Utils.join_commands(cmd)
|
||||||
hosts = KAMAL.hosts | KAMAL.accessory_hosts
|
hosts = KAMAL.hosts | KAMAL.accessory_hosts
|
||||||
|
|
||||||
case
|
case
|
||||||
|
|||||||
@@ -77,4 +77,8 @@ module Kamal::Utils
|
|||||||
def stable_sort!(elements, &block)
|
def stable_sort!(elements, &block)
|
||||||
elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
|
elements.sort_by!.with_index { |element, index| [ block.call(element), index ] }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def join_commands(commands)
|
||||||
|
commands.map(&:strip).join(" ")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -247,6 +247,12 @@ class CliAppTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "exec separate arguments" do
|
||||||
|
run_command("exec", "ruby", " -v").tap do |output|
|
||||||
|
assert_match "docker run --rm --env-file .kamal/env/roles/app-web.env dhh/app:latest ruby -v", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "exec with reuse" do
|
test "exec with reuse" do
|
||||||
run_command("exec", "--reuse", "ruby -v").tap do |output|
|
run_command("exec", "--reuse", "ruby -v").tap do |output|
|
||||||
assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version
|
assert_match "sh -c 'docker ps --latest --format '\\''{{.Names}}'\\'' --filter label=service=app --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=role=web --filter status=running --filter status=restarting' | head -1 | while read line; do echo ${line#app-web-}; done", output # Get current version
|
||||||
|
|||||||
@@ -566,6 +566,13 @@ class CliMainTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "append to command with an alias" do
|
||||||
|
run_command("rails", "db:migrate:status", config_file: "deploy_with_aliases").tap do |output|
|
||||||
|
assert_match "docker exec app-console-999 rails db:migrate:status on 1.1.1.5", output
|
||||||
|
assert_match "App Host: 1.1.1.5", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def run_command(*command, config_file: "deploy_simple")
|
def run_command(*command, config_file: "deploy_simple")
|
||||||
with_argv([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) do
|
with_argv([ *command, "-c", "test/fixtures/#{config_file}.yml" ]) do
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ require_relative "cli_test_case"
|
|||||||
class CliServerTest < CliTestCase
|
class CliServerTest < CliTestCase
|
||||||
test "running a command with exec" do
|
test "running a command with exec" do
|
||||||
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
||||||
.with("date", verbosity: 1)
|
.with("date", verbosity: 1)
|
||||||
.returns("Today")
|
.returns("Today")
|
||||||
|
|
||||||
hosts = "1.1.1.1".."1.1.1.4"
|
hosts = "1.1.1.1".."1.1.1.4"
|
||||||
run_command("exec", "date").tap do |output|
|
run_command("exec", "date").tap do |output|
|
||||||
@@ -15,6 +15,20 @@ class CliServerTest < CliTestCase
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "running a command with exec multiple arguments" do
|
||||||
|
SSHKit::Backend::Abstract.any_instance.stubs(:capture)
|
||||||
|
.with("date -j", verbosity: 1)
|
||||||
|
.returns("Today")
|
||||||
|
|
||||||
|
hosts = "1.1.1.1".."1.1.1.4"
|
||||||
|
run_command("exec", "date", "-j").tap do |output|
|
||||||
|
hosts.map do |host|
|
||||||
|
assert_match "Running 'date -j' on #{hosts.to_a.join(', ')}...", output
|
||||||
|
assert_match "App Host: #{host}\nToday", output
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "bootstrap already installed" do
|
test "bootstrap already installed" do
|
||||||
stub_setup
|
stub_setup
|
||||||
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once
|
SSHKit::Backend::Abstract.any_instance.expects(:execute).with(:docker, "-v", raise_on_non_zero_exit: false).returns(true).at_least_once
|
||||||
|
|||||||
1
test/fixtures/deploy_with_aliases.yml
vendored
1
test/fixtures/deploy_with_aliases.yml
vendored
@@ -18,3 +18,4 @@ aliases:
|
|||||||
info: details
|
info: details
|
||||||
console: app exec --reuse -p -r console "bin/console"
|
console: app exec --reuse -p -r console "bin/console"
|
||||||
exec: app exec --reuse -p -r console
|
exec: app exec --reuse -p -r console
|
||||||
|
rails: app exec --reuse -p -r console rails
|
||||||
|
|||||||
@@ -40,3 +40,4 @@ readiness_delay: 0
|
|||||||
aliases:
|
aliases:
|
||||||
whome: version
|
whome: version
|
||||||
worker_hostname: app exec -r workers -q --reuse hostname
|
worker_hostname: app exec -r workers -q --reuse hostname
|
||||||
|
uname: server exec -q -p uname
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ class MainTest < IntegrationTest
|
|||||||
|
|
||||||
output = kamal :worker_hostname, capture: true
|
output = kamal :worker_hostname, capture: true
|
||||||
assert_match /App Host: vm3\nvm3-[0-9a-f]{12}$/, output
|
assert_match /App Host: vm3\nvm3-[0-9a-f]{12}$/, output
|
||||||
|
|
||||||
|
output = kamal :uname, "-o", capture: true
|
||||||
|
assert_match "App Host: vm1\nGNU/Linux", output
|
||||||
end
|
end
|
||||||
|
|
||||||
test "setup and remove" do
|
test "setup and remove" do
|
||||||
|
|||||||
Reference in New Issue
Block a user