Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,11 @@ jobs:
if: matrix.rust == 'stable' && matrix.os == 'ubuntu-latest'
run: |
# Commands should use self.execute_command(args) not self.executor.execute_command("docker", args)
# The latter causes "docker docker <cmd>" double-command bug
if grep -r 'executor\.execute_command("docker"' src/command/; then
echo "ERROR: Found executor.execute_command(\"docker\", ...) antipattern!"
# The latter causes "docker docker <cmd>" double-command bug (issue #233)
if grep -rE 'executor\.execute_command\("docker"|execute_command\("docker",' src/command.rs src/command/; then
echo "ERROR: Found execute_command(\"docker\", ...) antipattern!"
echo "Use self.execute_command(args) instead to avoid 'docker docker' bug."
echo "For compose commands, pass 'compose' as command name, not 'docker'."
exit 1
fi

Expand Down
45 changes: 43 additions & 2 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,10 @@ pub trait DockerCommand {
// For compose commands, we need to handle "docker compose <subcommand>"
// For regular commands, we handle "docker <command>"
if command_args.first() == Some(&"compose".to_string()) {
// This is a compose command - args are already formatted correctly
executor.execute_command("docker", command_args).await
// This is a compose command - pass "compose" as command name
// and remaining args (skip the "compose" prefix since it becomes the command name)
let remaining_args = command_args.into_iter().skip(1).collect();
executor.execute_command("compose", remaining_args).await
} else {
// Regular docker command - first arg is the command name
let command_name = command_args
Expand Down Expand Up @@ -1076,4 +1078,43 @@ mod tests {
assert!(empty_output.stdout_is_empty());
assert!(empty_output.stderr_is_empty());
}

/// Regression test for issue #233: Verify that compose commands don't produce
/// "docker docker compose" when executed. The args returned by `ComposeCommand`
/// should start with "compose" (not "docker"), and the `execute_command` logic
/// should properly handle this by passing "compose" as the command name.
#[cfg(feature = "compose")]
#[test]
fn test_compose_command_args_structure() {
use crate::compose::ComposeUpCommand;

let cmd = ComposeUpCommand::new()
.file("docker-compose.yml")
.detach()
.service("web");

let args = ComposeCommand::build_command_args(&cmd);

// First arg must be "compose" - this becomes the command name
assert_eq!(args[0], "compose", "compose args must start with 'compose'");

// "docker" should never appear in these args - the runtime binary
// is added separately by CommandExecutor
assert!(
!args.iter().any(|arg| arg == "docker"),
"compose args should not contain 'docker': {args:?}"
);

// Verify expected structure: compose [global opts] up [subcommand opts] [services]
assert!(args.contains(&"up".to_string()), "must contain subcommand");
assert!(args.contains(&"--file".to_string()), "must contain --file");
assert!(
args.contains(&"--detach".to_string()),
"must contain --detach"
);
assert!(
args.contains(&"web".to_string()),
"must contain service name"
);
}
}
22 changes: 22 additions & 0 deletions src/command/compose/ps.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,4 +380,26 @@ mod tests {
assert!(args.contains(&"my-project".to_string()));
assert!(args.contains(&"--all".to_string()));
}

/// Regression test for issue #233: `ComposePsCommand` was failing because
/// the command was being built as "docker docker compose ..." instead of
/// "docker compose ...". This verifies that `build_command_args` does not
/// include "docker" since the runtime binary is added separately.
#[test]
fn test_compose_args_no_docker_prefix() {
let cmd = ComposePsCommand::new()
.file("/path/to/docker-compose.yaml")
.service("php");

let args = ComposeCommand::build_command_args(&cmd);

// Args should start with "compose", not "docker"
assert_eq!(args[0], "compose");
// "docker" should not appear anywhere in the args (the runtime binary
// is added separately by CommandExecutor)
assert!(
!args.iter().any(|arg| arg == "docker"),
"args should not contain 'docker': {args:?}"
);
}
}