Add tests for automatic path-routed release target preparation and application target handling failures in ReleaseManager

This commit is contained in:
Jeppe Bundgaard
2026-05-26 15:11:27 +02:00
parent bc7c0280f2
commit 6ff6ce9b48
3 changed files with 104 additions and 3 deletions
File diff suppressed because one or more lines are too long
+59 -2
View File
@@ -1295,6 +1295,20 @@ class release_manager
$statuses[] = 'failed';
continue;
}
$target = $this->prepareChannelSyncApplicationTarget($target, $actorUserId);
if (($target['_release_auto_prepared_application'] ?? false) === true) {
$this->recordOperationStep(
$operationId,
$app . '_target_prepared',
strtoupper($app) . ' Coolify application target',
'passed',
sprintf('%s target was prepared to create a path-routed Coolify application.', strtoupper($app)),
null,
null,
['target_id' => (int)$target['id'], 'app' => $app]
);
$statuses[] = 'passed';
}
$repository = trim((string)($target['repository'] ?? self::defaultRepositoryForApp($app)));
if ($repository === '') {
@@ -1761,6 +1775,11 @@ class release_manager
throw new RuntimeException('Release deployment target is missing.');
}
return $this->prepareDeploymentTargetAsApplication($targetId, $actorUserId, 'warning');
}
private function prepareDeploymentTargetAsApplication(int $targetId, ?int $actorUserId, string $severity = 'warning'): array
{
$target = $this->getDeploymentTarget($targetId);
$context = self::jsonDecode($target['deploy_context_json'] ?? null);
$context = is_array($context) ? $context : [];
@@ -1774,9 +1793,10 @@ class release_manager
'si',
[self::jsonEncode($context), $targetId]
);
$this->audit((int)$target['channel_id'], null, 'deployment_target_prepared_as_application', $actorUserId, 'warning', [
$this->audit((int)$target['channel_id'], null, 'deployment_target_prepared_as_application', $actorUserId, $severity, [
'target_id' => $targetId,
'replaced_legacy_service_uuid' => $replacedLegacyUuid,
'severity' => $severity,
]);
return [
@@ -1786,6 +1806,20 @@ class release_manager
];
}
private function prepareChannelSyncApplicationTarget(array $target, ?int $actorUserId): array
{
$context = self::jsonDecode($target['deploy_context_json'] ?? null);
$context = is_array($context) ? $context : [];
if (!$this->releaseTargetNeedsApplicationAutoCreate($target, $context)) {
return $target;
}
$this->prepareDeploymentTargetAsApplication((int)$target['id'], $actorUserId, 'info');
$prepared = $this->getDeploymentTarget((int)$target['id']);
$prepared['_release_auto_prepared_application'] = true;
return $prepared;
}
private function releaseStatusIssueByKey(array $summary, string $issueKey): ?array
{
foreach (is_array($summary['status_overview']['issues'] ?? null) ? $summary['status_overview']['issues'] : [] as $issue) {
@@ -2601,7 +2635,10 @@ class release_manager
private static function releaseStatusIssueNeedsApplicationTarget(array $issue): bool
{
$text = strtolower(trim((string)($issue['message'] ?? '') . ' ' . (string)($issue['next_action'] ?? '')));
return str_contains($text, 'stripprefix') || str_contains($text, 'path-routed');
return str_contains($text, 'stripprefix')
|| str_contains($text, 'path-routed')
|| str_contains($text, 'service creation')
|| str_contains($text, 'coolify service');
}
private static function releaseStatusEligibleBundles(array $bundles): array
@@ -5647,6 +5684,26 @@ class release_manager
return 'service';
}
private function releaseTargetNeedsApplicationAutoCreate(array $target, array $context): bool
{
if (!in_array(strtolower(trim((string)($target['app'] ?? ''))), self::APPS, true)) {
return false;
}
if ($this->nullablePositiveInt($target['coolify_instance_id'] ?? null) === null) {
return false;
}
if (trim((string)($target['coolify_service_uuid'] ?? '')) !== '') {
return false;
}
if ($this->toBool($context['coolify_auto_create'] ?? false)) {
return false;
}
$publicUrl = $this->releaseCoolifyPublicUrl($target, $context);
return self::releaseCoolifyPublicUrlNeedsStripPrefixLabels($publicUrl)
|| $this->releaseCoolifyResourceType($context, '') === 'application';
}
private function releaseCoolifyBuildPack(array $target, array $context): string
{
$app = strtolower(trim((string)($target['app'] ?? '')));
@@ -351,6 +351,50 @@ it('does not treat an existing Coolify service as an application just because a
], 'existing-application-uuid'))->toBe('application');
});
it('auto-prepares path-routed release targets for Coolify application creation', function (): void {
$manager = new release_manager();
$needsApplication = new ReflectionMethod(release_manager::class, 'releaseTargetNeedsApplicationAutoCreate');
$needsApplication->setAccessible(true);
$target = [
'id' => 42,
'channel_slug' => 'stable',
'app' => 'api',
'coolify_instance_id' => 7,
'coolify_service_uuid' => '',
'deploy_context_json' => null,
];
expect($needsApplication->invoke($manager, $target, []))->toBeTrue();
expect($needsApplication->invoke($manager, array_replace($target, [
'coolify_service_uuid' => 'existing-service',
]), []))->toBeFalse();
expect($needsApplication->invoke($manager, array_replace($target, [
'coolify_instance_id' => null,
]), []))->toBeFalse();
expect($needsApplication->invoke($manager, $target, [
'coolify_auto_create' => true,
]))->toBeFalse();
});
it('offers application target preparation for missing Coolify service creation failures', function (): void {
$needsApplicationAction = new ReflectionMethod(release_manager::class, 'releaseStatusIssueNeedsApplicationTarget');
$needsApplicationAction->setAccessible(true);
expect($needsApplicationAction->invoke(null, [
'message' => 'API deployment failed before activation.',
'next_action' => 'Select an existing Coolify service or enable Coolify service creation before deployment.',
]))->toBeTrue();
expect($needsApplicationAction->invoke(null, [
'message' => 'Path-routed release targets require a Coolify application resource with StripPrefix labels.',
'next_action' => 'Migrate this target before deploying.',
]))->toBeTrue();
expect($needsApplicationAction->invoke(null, [
'message' => 'Release gate failed.',
'next_action' => 'Run live smoke tests before promotion.',
]))->toBeFalse();
});
it('keeps release branch services out of the production Coolify environment except beta', function (): void {
$manager = new release_manager();
$payloadMethod = new ReflectionMethod(release_manager::class, 'releaseCoolifyServicePayload');