fix(applications): allow environment variable references in image tags

Closes #10642
This commit is contained in:
Andras Bacsai
2026-10-27 15:05:23 +01:00
parent d3b1a3d405
commit 4f9ad86e52
7 changed files with 109 additions and 19 deletions
@@ -46,6 +46,11 @@ it('accepts valid docker registry image tags', function (string $tag) {
'uppercase and underscore' => 'PR_123',
'sha256 hash' => '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
'legacy sha256 prefixed hash' => 'sha256-1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
'environment variable reference' => '$API_VERSION',
'braced environment variable reference' => '${API_VERSION}',
'environment variable reference with literal parts' => 'v${API_VERSION}-alpine',
'environment variable reference with default for empty or unset' => '${API_VERSION:-latest}',
'environment variable reference with default for unset' => '${API_VERSION-1.27-alpine}',
]);
it('rejects docker registry image tags with shell metacharacters', function (string $tag) {
@@ -57,6 +62,14 @@ it('rejects docker registry image tags with shell metacharacters', function (str
'pipe' => 'latest|id',
'logical and' => 'latest&&id',
'newline' => "latest\nid",
'bare dollar' => '$',
'positional parameter' => '$1',
'special parameter' => '$$',
'unclosed braced reference' => '${API_VERSION',
'parameter expansion with command substitution default' => '${API_VERSION:-$(id)}',
'parameter expansion with nested reference default' => '${API_VERSION:-${OTHER}}',
'parameter expansion with required error' => '${API_VERSION:?missing}',
'parameter expansion with assignment' => '${API_VERSION:=latest}',
]);
it('accepts supported full docker image reference formats', function (string $imageReference) {
@@ -76,6 +89,21 @@ it('accepts supported full docker image reference formats', function (string $im
'IPv6 registry port image with tag' => '[2a01:4f8::1]:5000/app:latest',
]);
it('accepts an environment variable tag in a full docker image reference only when variable tags are allowed', function () {
$validate = function (DockerImageFormat $rule): array {
$failures = [];
$rule->validate('image', 'ghcr.io/user/app:${API_VERSION:-latest}', function (string $message) use (&$failures): void {
$failures[] = $message;
});
return $failures;
};
expect($validate(new DockerImageFormat(allowVariableTag: true)))->toBeEmpty()
->and($validate(new DockerImageFormat))->not->toBeEmpty();
});
it('rejects unsupported full docker image reference formats', function (string $imageReference) {
$failures = [];
@@ -115,3 +143,20 @@ it('stops deployments when a stored docker registry image value is unsafe', func
expect(fn () => $method->invoke($job))->toThrow(DeploymentException::class);
});
it('allows deployments when the stored docker registry image tag is an environment variable reference', function () {
$job = (new ReflectionClass(ApplicationDeploymentJob::class))->newInstanceWithoutConstructor();
$jobReflection = new ReflectionClass($job);
foreach ([
'application' => new Application([
'docker_registry_image_name' => 'ghcr.io/user/app',
'docker_registry_image_tag' => '$API_VERSION',
]),
'dockerImagePreviewTag' => null,
] as $property => $value) {
$jobReflection->getProperty($property)->setValue($job, $value);
}
$jobReflection->getMethod('validateDockerRegistryImageConfiguration')->invoke($job);
})->throwsNoExceptions();