fix(database): choose the MongoDB health check shell at run time

The health check from #11730 picked mongosh or the legacy mongo shell
from the image name (mongo:4*). Images without mongosh whose name does
not start with mongo:4, such as mongo:3.6 or registry mirrors of
mongo:4.4, ran a missing mongosh (exit 127) and stayed unhealthy.

The check now uses mongosh when the container has it and falls back
to the legacy mongo shell otherwise.

Replace the test that only checked the source text with tests that run
the generated script with fake shells.
This commit is contained in:
Andras Bacsai
2026-09-30 20:48:54 +02:00
parent 0604101781
commit b13fa4dad0
2 changed files with 63 additions and 72 deletions
+18 -23
View File
@@ -328,41 +328,36 @@ class StartMongodb
return $environment_variables->all();
}
/**
* The shell is chosen when the check runs: images before MongoDB 5 (and some custom images) have only the legacy mongo shell.
*/
private function generate_health_check_command(): array
{
$usesLegacyShell = str($this->database->image)->startsWith('mongo:4');
$command = [
'CMD',
$usesLegacyShell ? 'mongo' : 'mongosh',
'--quiet',
'--host',
$this->database->uuid,
];
$mongosh = $this->health_check_shell_command(legacy: false);
$mongo = $this->health_check_shell_command(legacy: true);
return ['CMD-SHELL', "if command -v mongosh >/dev/null 2>&1; then {$mongosh}; else {$mongo}; fi"];
}
private function health_check_shell_command(bool $legacy): string
{
$command = [$legacy ? 'mongo' : 'mongosh', '--quiet', '--host', $this->database->uuid];
if ($this->database->enable_ssl) {
$command = [
...$command,
$usesLegacyShell ? '--ssl' : '--tls',
$usesLegacyShell ? '--sslCAFile' : '--tlsCAFile',
'/etc/mongo/certs/ca.pem',
];
$command = [...$command, $legacy ? '--ssl' : '--tls', $legacy ? '--sslCAFile' : '--tlsCAFile', '/etc/mongo/certs/ca.pem'];
if ($this->database->ssl_mode === 'verify-full') {
$command = [
...$command,
$usesLegacyShell ? '--sslPEMKeyFile' : '--tlsCertificateKeyFile',
'/etc/mongo/certs/server.pem',
];
$command = [...$command, $legacy ? '--sslPEMKeyFile' : '--tlsCertificateKeyFile', '/etc/mongo/certs/server.pem'];
}
}
return [
$command = [
...$command,
'--eval',
$usesLegacyShell
? 'quit(db.isMaster().ismaster === true ? 0 : 1)'
: 'quit(db.hello().isWritablePrimary === true ? 0 : 1)',
$legacy ? 'quit(db.isMaster().ismaster === true ? 0 : 1)' : 'quit(db.hello().isWritablePrimary === true ? 0 : 1)',
];
return implode(' ', array_map('escapeshellarg', $command));
}
private function add_custom_mongo_conf()
+45 -49
View File
@@ -3,14 +3,6 @@
use App\Actions\Database\StartMongodb;
use App\Models\StandaloneMongodb;
it('uses the generated MongoDB health check in the Compose configuration', function () {
$source = remoteOutputSource('app/Actions/Database/StartMongodb.php');
expect($source)
->toContain("'healthcheck' => \$this->database->healthCheckConfiguration(\$this->generate_health_check_command())")
->not->toContain("'healthcheck' => \$this->database->healthCheckConfiguration([\n 'CMD',\n 'echo',\n 'ok'");
});
function generatedMongodbHealthCheckCommand(array $attributes): array
{
$action = new StartMongodb;
@@ -21,69 +13,73 @@ function generatedMongodbHealthCheckCommand(array $attributes): array
return $method->invoke($action);
}
it('generates a credential-free writable-primary MongoDB health check', function (string $image, bool $enableSsl, ?string $sslMode, array $expectedCommand) {
$command = generatedMongodbHealthCheckCommand([
/**
* Runs the health check script with fake shells on PATH and returns the program and arguments that were called.
*
* @param list<string> $availableShells
*/
function runMongodbHealthCheck(array $healthCheck, array $availableShells): string
{
$bin = sys_get_temp_dir().'/mongodb-healthcheck-'.bin2hex(random_bytes(4));
mkdir($bin);
foreach ($availableShells as $shell) {
file_put_contents("$bin/$shell", "#!/bin/sh\necho $shell; for arg in \"\$@\"; do echo \"\$arg\"; done\n");
chmod("$bin/$shell", 0755);
}
$output = shell_exec('PATH='.escapeshellarg($bin).' /bin/sh -c '.escapeshellarg($healthCheck[1]));
array_map('unlink', glob("$bin/*"));
rmdir($bin);
return trim((string) $output);
}
it('uses mongosh when the image has it and the legacy mongo shell otherwise', function (array $availableShells, bool $enableSsl, ?string $sslMode, array $expectedCall) {
$healthCheck = generatedMongodbHealthCheckCommand([
'uuid' => 'mongodb-test-resource',
'image' => $image,
'image' => 'registry.example.com/mongo:any',
'enable_ssl' => $enableSsl,
'ssl_mode' => $sslMode,
'mongo_initdb_root_username' => 'root-user',
'mongo_initdb_root_password' => 'root-password',
]);
expect($command)->toBe($expectedCommand)
->and(implode(' ', $command))
expect($healthCheck[0])->toBe('CMD-SHELL')
->and($healthCheck[1])
->not->toContain('root-user')
->not->toContain('root-password')
->not->toContain('--tlsAllowInvalidHostnames')
->not->toContain('--tlsAllowInvalidCertificates');
->not->toContain('AllowInvalid')
->and(runMongodbHealthCheck($healthCheck, $availableShells))->toBe(implode("\n", $expectedCall));
})->with([
'without TLS' => ['mongo:8', false, null, [
'CMD', 'mongosh', '--quiet', '--host', 'mongodb-test-resource', '--eval',
'mongosh without TLS' => [['mongosh', 'mongo'], false, null, [
'mongosh', '--quiet', '--host', 'mongodb-test-resource', '--eval',
'quit(db.hello().isWritablePrimary === true ? 0 : 1)',
]],
'TLS allow mode' => ['mongo:8', true, 'allow', [
'CMD', 'mongosh', '--quiet', '--host', 'mongodb-test-resource',
'mongosh with TLS require' => [['mongosh'], true, 'require', [
'mongosh', '--quiet', '--host', 'mongodb-test-resource',
'--tls', '--tlsCAFile', '/etc/mongo/certs/ca.pem', '--eval',
'quit(db.hello().isWritablePrimary === true ? 0 : 1)',
]],
'TLS prefer mode' => ['mongo:8', true, 'prefer', [
'CMD', 'mongosh', '--quiet', '--host', 'mongodb-test-resource',
'--tls', '--tlsCAFile', '/etc/mongo/certs/ca.pem', '--eval',
'mongosh with TLS verify-full' => [['mongosh'], true, 'verify-full', [
'mongosh', '--quiet', '--host', 'mongodb-test-resource',
'--tls', '--tlsCAFile', '/etc/mongo/certs/ca.pem',
'--tlsCertificateKeyFile', '/etc/mongo/certs/server.pem', '--eval',
'quit(db.hello().isWritablePrimary === true ? 0 : 1)',
]],
'TLS require mode' => ['mongo:8', true, 'require', [
'CMD', 'mongosh', '--quiet', '--host', 'mongodb-test-resource',
'--tls', '--tlsCAFile', '/etc/mongo/certs/ca.pem', '--eval',
'quit(db.hello().isWritablePrimary === true ? 0 : 1)',
]],
'TLS verify-full mode' => ['mongo:8', true, 'verify-full', [
'CMD', 'mongosh', '--quiet', '--host', 'mongodb-test-resource',
'--tls',
'--tlsCAFile',
'/etc/mongo/certs/ca.pem',
'--tlsCertificateKeyFile',
'/etc/mongo/certs/server.pem',
'--eval',
'quit(db.hello().isWritablePrimary === true ? 0 : 1)',
]],
'MongoDB 4 without TLS' => ['mongo:4.4', false, null, [
'CMD', 'mongo', '--quiet', '--host', 'mongodb-test-resource', '--eval',
'legacy mongo without TLS' => [['mongo'], false, null, [
'mongo', '--quiet', '--host', 'mongodb-test-resource', '--eval',
'quit(db.isMaster().ismaster === true ? 0 : 1)',
]],
'MongoDB 4 TLS require mode' => ['mongo:4.4', true, 'require', [
'CMD', 'mongo', '--quiet', '--host', 'mongodb-test-resource',
'legacy mongo with TLS require' => [['mongo'], true, 'require', [
'mongo', '--quiet', '--host', 'mongodb-test-resource',
'--ssl', '--sslCAFile', '/etc/mongo/certs/ca.pem', '--eval',
'quit(db.isMaster().ismaster === true ? 0 : 1)',
]],
'MongoDB 4 TLS verify-full mode' => ['mongo:4.4', true, 'verify-full', [
'CMD', 'mongo', '--quiet', '--host', 'mongodb-test-resource',
'--ssl',
'--sslCAFile',
'/etc/mongo/certs/ca.pem',
'--sslPEMKeyFile',
'/etc/mongo/certs/server.pem',
'--eval',
'legacy mongo with TLS verify-full' => [['mongo'], true, 'verify-full', [
'mongo', '--quiet', '--host', 'mongodb-test-resource',
'--ssl', '--sslCAFile', '/etc/mongo/certs/ca.pem',
'--sslPEMKeyFile', '/etc/mongo/certs/server.pem', '--eval',
'quit(db.isMaster().ismaster === true ? 0 : 1)',
]],
]);