mirror of
https://github.com/tiennm99/coolify.git
synced 2026-08-14 02:22:24 +00:00
fix(deployment): include commit in preview image tags
Generate pull request preview image tags with both the PR id and commit so different commits on the same PR do not reuse the same image tag. Sanitize and truncate generated tags to stay within Docker tag limits.
This commit is contained in:
@@ -1154,12 +1154,15 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
$this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}";
|
||||
}
|
||||
} elseif ($this->pull_request_id !== 0) {
|
||||
$previewImageTag = $this->previewImageTag();
|
||||
$previewBuildImageTag = $this->previewImageTag(build: true);
|
||||
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build";
|
||||
$this->production_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}";
|
||||
$this->build_image_name = "{$this->application->docker_registry_image_name}:{$previewBuildImageTag}";
|
||||
$this->production_image_name = "{$this->application->docker_registry_image_name}:{$previewImageTag}";
|
||||
} else {
|
||||
$this->build_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}-build";
|
||||
$this->production_image_name = "{$this->application->uuid}:pr-{$this->pull_request_id}";
|
||||
$this->build_image_name = "{$this->application->uuid}:{$previewBuildImageTag}";
|
||||
$this->production_image_name = "{$this->application->uuid}:{$previewImageTag}";
|
||||
}
|
||||
} else {
|
||||
$this->dockerImageTag = str($this->commit)->substr(0, 128);
|
||||
@@ -1176,6 +1179,23 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
}
|
||||
}
|
||||
|
||||
private function previewImageTag(bool $build = false): string
|
||||
{
|
||||
$prefix = "pr-{$this->pull_request_id}-";
|
||||
$suffix = $build ? '-build' : '';
|
||||
$maxCommitLength = max(1, 128 - strlen($prefix) - strlen($suffix));
|
||||
$commit = Str::of($this->commit ?: 'HEAD')
|
||||
->replaceMatches('/[^A-Za-z0-9_.-]/', '-')
|
||||
->substr(0, $maxCommitLength)
|
||||
->toString();
|
||||
|
||||
if ($commit === '') {
|
||||
$commit = 'HEAD';
|
||||
}
|
||||
|
||||
return "{$prefix}{$commit}{$suffix}";
|
||||
}
|
||||
|
||||
private function just_restart()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}.");
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Application;
|
||||
|
||||
function makePreviewImageNameJob(string $commit, int $pullRequestId = 42, ?string $registryImageName = null): object
|
||||
{
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
$job = $reflection->newInstanceWithoutConstructor();
|
||||
|
||||
$application = new Application;
|
||||
$application->uuid = 'preview-app';
|
||||
$application->build_pack = 'dockerfile';
|
||||
$application->dockerfile = null;
|
||||
$application->docker_registry_image_name = $registryImageName;
|
||||
|
||||
foreach ([
|
||||
'application' => $application,
|
||||
'pull_request_id' => $pullRequestId,
|
||||
'commit' => $commit,
|
||||
] as $property => $value) {
|
||||
$reflectionProperty = $reflection->getProperty($property);
|
||||
$reflectionProperty->setAccessible(true);
|
||||
$reflectionProperty->setValue($job, $value);
|
||||
}
|
||||
|
||||
return $job;
|
||||
}
|
||||
|
||||
function generatePreviewImageNames(object $job): array
|
||||
{
|
||||
$reflection = new ReflectionClass(ApplicationDeploymentJob::class);
|
||||
$method = $reflection->getMethod('generate_image_names');
|
||||
$method->setAccessible(true);
|
||||
$method->invoke($job);
|
||||
|
||||
$buildImageName = $reflection->getProperty('build_image_name');
|
||||
$buildImageName->setAccessible(true);
|
||||
|
||||
$productionImageName = $reflection->getProperty('production_image_name');
|
||||
$productionImageName->setAccessible(true);
|
||||
|
||||
return [
|
||||
'build' => $buildImageName->getValue($job),
|
||||
'production' => $productionImageName->getValue($job),
|
||||
];
|
||||
}
|
||||
|
||||
it('includes the pull request id and commit in preview image names', function () {
|
||||
$names = generatePreviewImageNames(makePreviewImageNameJob(
|
||||
commit: '111222333444555666777888999000aaabbbccc1',
|
||||
pullRequestId: 123,
|
||||
));
|
||||
|
||||
expect($names['production'])->toBe('preview-app:pr-123-111222333444555666777888999000aaabbbccc1')
|
||||
->and($names['build'])->toBe('preview-app:pr-123-111222333444555666777888999000aaabbbccc1-build');
|
||||
});
|
||||
|
||||
it('generates different preview image names for different commits on the same pull request', function () {
|
||||
$firstCommitNames = generatePreviewImageNames(makePreviewImageNameJob(
|
||||
commit: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||||
pullRequestId: 123,
|
||||
));
|
||||
$secondCommitNames = generatePreviewImageNames(makePreviewImageNameJob(
|
||||
commit: 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
|
||||
pullRequestId: 123,
|
||||
));
|
||||
|
||||
expect($firstCommitNames['production'])->not->toBe($secondCommitNames['production'])
|
||||
->and($firstCommitNames['build'])->not->toBe($secondCommitNames['build']);
|
||||
});
|
||||
|
||||
it('uses the configured registry image name for commit-specific preview tags', function () {
|
||||
$names = generatePreviewImageNames(makePreviewImageNameJob(
|
||||
commit: '111222333444555666777888999000aaabbbccc1',
|
||||
pullRequestId: 123,
|
||||
registryImageName: 'registry.example.com/team/app',
|
||||
));
|
||||
|
||||
expect($names['production'])->toBe('registry.example.com/team/app:pr-123-111222333444555666777888999000aaabbbccc1')
|
||||
->and($names['build'])->toBe('registry.example.com/team/app:pr-123-111222333444555666777888999000aaabbbccc1-build');
|
||||
});
|
||||
|
||||
it('sanitizes and truncates preview image tags to docker tag limits', function () {
|
||||
$names = generatePreviewImageNames(makePreviewImageNameJob(
|
||||
commit: str_repeat('feature/add dockerfile changes/', 10),
|
||||
pullRequestId: 123,
|
||||
));
|
||||
|
||||
$productionTag = str($names['production'])->after(':')->toString();
|
||||
$buildTag = str($names['build'])->after(':')->toString();
|
||||
|
||||
expect(strlen($productionTag))->toBeLessThanOrEqual(128)
|
||||
->and(strlen($buildTag))->toBeLessThanOrEqual(128)
|
||||
->and($productionTag)->toMatch('/^pr-123-[A-Za-z0-9_.-]+$/')
|
||||
->and($buildTag)->toMatch('/^pr-123-[A-Za-z0-9_.-]+-build$/');
|
||||
});
|
||||
|
||||
it('keeps non-preview dockerfile image names commit based', function () {
|
||||
$names = generatePreviewImageNames(makePreviewImageNameJob(
|
||||
commit: '111222333444555666777888999000aaabbbccc1',
|
||||
pullRequestId: 0,
|
||||
));
|
||||
|
||||
expect($names['production'])->toBe('preview-app:111222333444555666777888999000aaabbbccc1')
|
||||
->and($names['build'])->toBe('preview-app:111222333444555666777888999000aaabbbccc1-build');
|
||||
});
|
||||
Reference in New Issue
Block a user