diff --git a/features/cli.feature b/features/cli.feature index 6ce5dd29c..fa8fb9c2d 100644 --- a/features/cli.feature +++ b/features/cli.feature @@ -36,7 +36,7 @@ Feature: `wp cli` tasks When I run `wp cli param-dump --with-values | grep -o '"current":' | uniq -c | tr -d ' '` Then STDOUT should be: """ - 19"current": + 20"current": """ And STDERR should be empty And the return code should be 0 diff --git a/features/flags.feature b/features/flags.feature index 63968ce7e..e91b7dac0 100644 --- a/features/flags.feature +++ b/features/flags.feature @@ -361,6 +361,27 @@ Feature: Global flags Running SSH command: docker exec --user 'user' 'wordpress' sh -c """ + Scenario: SSH args should be passed to SSH command + When I try `wp --debug --ssh=wordpress --ssh-args="-o ConnectTimeout=5" --version` + Then STDERR should contain: + """ + Running SSH command: ssh '-o ConnectTimeout=5' -T -vvv 'wordpress' 'wp + """ + + Scenario: Multiple SSH args should be passed to SSH command + When I try `wp --debug --ssh=wordpress --ssh-args="-o ConnectTimeout=5" --ssh-args="-o ServerAliveInterval=10" --version` + Then STDERR should contain: + """ + Running SSH command: ssh '-o ConnectTimeout=5' '-o ServerAliveInterval=10' -T -vvv 'wordpress' 'wp + """ + + Scenario: SSH args should be passed to Docker command + When I try `WP_CLI_DOCKER_NO_INTERACTIVE=1 wp --debug --ssh=docker:wordpress --ssh-args="--env MY_VAR=value" --version` + Then STDERR should contain: + """ + Running SSH command: docker exec '--env MY_VAR=value' 'wordpress' sh -c + """ + Scenario: Customize config-spec with WP_CLI_CONFIG_SPEC_FILTER_CALLBACK Given a WP installation And a wp-cli-early-require.php file: diff --git a/php/WP_CLI/Configurator.php b/php/WP_CLI/Configurator.php index 613c49a8e..e9fe91532 100644 --- a/php/WP_CLI/Configurator.php +++ b/php/WP_CLI/Configurator.php @@ -60,6 +60,7 @@ class Configurator { 'url', 'path', 'ssh', + 'ssh-args', 'http', 'proxyjump', 'key', diff --git a/php/WP_CLI/Runner.php b/php/WP_CLI/Runner.php index 839ae9b99..81e819bd0 100644 --- a/php/WP_CLI/Runner.php +++ b/php/WP_CLI/Runner.php @@ -707,7 +707,7 @@ private function run_ssh_command( string $connection_string ): void { } foreach ( $wp_args as $k => $v ) { - if ( preg_match( '#--ssh=#', (string) $v ) ) { + if ( preg_match( '#^--ssh(?:-args)?(?:=|$)#', (string) $v ) ) { unset( $wp_args[ $k ] ); } } @@ -745,6 +745,12 @@ static function ( $arg ): string { private function generate_ssh_command( $bits, $wp_command ) { $escaped_command = ''; + // Get additional SSH arguments if provided. + $ssh_args_config = WP_CLI::get_config( 'ssh-args' ); + $ssh_args = is_array( $ssh_args_config ) && ! empty( $ssh_args_config ) + ? implode( ' ', array_map( 'escapeshellarg', $ssh_args_config ) ) + : ''; + // Set default values. foreach ( [ 'scheme', 'user', 'host', 'port', 'path', 'key', 'proxyjump' ] as $bit ) { if ( ! isset( $bits[ $bit ] ) ) { @@ -772,10 +778,11 @@ private function generate_ssh_command( $bits, $wp_command ) { : 'docker-compose'; if ( 'docker' === $bits['scheme'] ) { - $command = 'docker exec %s%s%s%s%s sh -c %s'; + $command = 'docker exec %s%s%s%s%s%s sh -c %s'; $escaped_command = sprintf( $command, + $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', $is_stdout_tty && ! getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '-t ' : '', @@ -786,11 +793,12 @@ private function generate_ssh_command( $bits, $wp_command ) { } if ( 'docker-compose' === $bits['scheme'] ) { - $command = '%s exec %s%s%s%s sh -c %s'; + $command = '%s exec %s%s%s%s%s sh -c %s'; $escaped_command = sprintf( $command, $docker_compose_cmd, + $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', $is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', @@ -800,11 +808,12 @@ private function generate_ssh_command( $bits, $wp_command ) { } if ( 'docker-compose-run' === $bits['scheme'] ) { - $command = '%s run %s%s%s%s%s %s'; + $command = '%s run %s%s%s%s%s%s %s'; $escaped_command = sprintf( $command, $docker_compose_cmd, + $ssh_args ? $ssh_args . ' ' : '', $bits['user'] ? '--user ' . escapeshellarg( $bits['user'] ) . ' ' : '', $bits['path'] ? '--workdir ' . escapeshellarg( $bits['path'] ) . ' ' : '', $is_stdout_tty || getenv( 'WP_CLI_DOCKER_NO_TTY' ) ? '' : '-T ', @@ -848,7 +857,7 @@ private function generate_ssh_command( $bits, $wp_command ) { // If we could not resolve the bits still, fallback to just `vagrant ssh` if ( 'vagrant' === $bits['scheme'] ) { - $command = 'vagrant ssh -c %s %s'; + $command = 'vagrant ssh' . ( $ssh_args ? ' ' . $ssh_args : '' ) . ' -c %s %s'; $escaped_command = sprintf( $command, @@ -860,7 +869,7 @@ private function generate_ssh_command( $bits, $wp_command ) { // Default scheme is SSH. if ( 'ssh' === $bits['scheme'] || null === $bits['scheme'] ) { - $command = 'ssh %s %s %s'; + $command = 'ssh %s%s %s %s'; if ( $bits['user'] ) { $bits['host'] = $bits['user'] . '@' . $bits['host']; @@ -887,6 +896,7 @@ private function generate_ssh_command( $bits, $wp_command ) { $escaped_command = sprintf( $command, + $ssh_args ? $ssh_args . ' ' : '', implode( ' ', array_filter( $command_args ) ), escapeshellarg( $bits['host'] ), escapeshellarg( $wp_command ) diff --git a/php/class-wp-cli.php b/php/class-wp-cli.php index b070177c0..4728f3857 100644 --- a/php/class-wp-cli.php +++ b/php/class-wp-cli.php @@ -23,7 +23,7 @@ /** * Various utilities for WP-CLI commands. * - * @phpstan-type GlobalConfig array{path: string|null, ssh: string|null, http: string|null, url: string|null, user: string|null, 'skip-plugins': true|string[], 'skip-themes': true|string[], 'skip-packages': bool, require: string[], exec: string[], context: string, debug: string|true, prompt: false|string, quiet: bool, apache_modules: string[]} + * @phpstan-type GlobalConfig array{path: string|null, ssh: string|null, 'ssh-args': string[], http: string|null, url: string|null, user: string|null, 'skip-plugins': true|string[], 'skip-themes': true|string[], 'skip-packages': bool, require: string[], exec: string[], context: string, debug: string|true, prompt: false|string, quiet: bool, apache_modules: string[]} * * @phpstan-type FlagParameter array{type: 'flag', name: string, description?: string, optional?: bool, repeating?: bool, aliases?: string[]} * @phpstan-type AssocParameter array{type: 'assoc', name: string, description?: string, options?: string[], default?: string, optional?: bool, value: array{optional: bool, name?: string}, repeating?: bool, aliases?: string[]} diff --git a/php/config-spec.php b/php/config-spec.php index 715f139db..192948593 100644 --- a/php/config-spec.php +++ b/php/config-spec.php @@ -19,6 +19,14 @@ 'desc' => 'Perform operation against a remote server over SSH (or a container using scheme of "docker", "docker-compose", "docker-compose-run", "vagrant").', ], + 'ssh-args' => [ + 'runtime' => '=', + 'file' => '', + 'desc' => 'Pass additional arguments to SSH (or other tools specified by --ssh scheme).', + 'multiple' => true, + 'default' => [], + ], + 'http' => [ 'runtime' => '=', 'file' => '',