mirror of
https://github.com/coollabsio/coolify.git
synced 2026-10-28 17:44:36 +08:00
The legacy configuration hash encoded serialized environment variable rows, so hashes from older versions never matched after an upgrade, and the fallback compared the configuration with an empty one. Apps without a deployment snapshot now report no pending changes, and their next deployment rebuilds the image. Closes #11101 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
177 lines
7.0 KiB
PHP
177 lines
7.0 KiB
PHP
<?php
|
|
|
|
use App\Models\Application;
|
|
use App\Models\ApplicationDeploymentQueue;
|
|
use App\Models\Environment;
|
|
use App\Models\EnvironmentVariable;
|
|
use App\Models\Project;
|
|
use App\Models\Team;
|
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
|
use Illuminate\Support\Str;
|
|
|
|
uses(RefreshDatabase::class);
|
|
|
|
function configurationChangedTestApplication(array $attributes = []): Application
|
|
{
|
|
$team = Team::factory()->create();
|
|
$project = Project::factory()->create(['team_id' => $team->id]);
|
|
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
|
|
|
return Application::factory()->create(array_merge([
|
|
'environment_id' => $environment->id,
|
|
'status' => 'running:healthy',
|
|
'build_command' => 'npm run build',
|
|
], $attributes));
|
|
}
|
|
|
|
function configurationChangedDeployment(Application $application): ApplicationDeploymentQueue
|
|
{
|
|
return ApplicationDeploymentQueue::create([
|
|
'application_id' => (string) $application->id,
|
|
'deployment_uuid' => (string) Str::uuid(),
|
|
'status' => 'finished',
|
|
'commit' => 'HEAD',
|
|
]);
|
|
}
|
|
|
|
it('stores deployment configuration snapshot and clears pending changes', function () {
|
|
$application = configurationChangedTestApplication();
|
|
$deployment = configurationChangedDeployment($application);
|
|
|
|
$application->markDeploymentConfigurationApplied($deployment);
|
|
|
|
expect($deployment->refresh()->configuration_hash)->not->toBeNull()
|
|
->and($deployment->configuration_snapshot)->toBeArray()
|
|
->and($application->refresh()->pendingDeploymentConfigurationDiff()->isChanged())->toBeFalse();
|
|
});
|
|
|
|
it('stores a diff between successful deployments', function () {
|
|
$application = configurationChangedTestApplication();
|
|
$firstDeployment = configurationChangedDeployment($application);
|
|
$application->markDeploymentConfigurationApplied($firstDeployment);
|
|
|
|
$application->update(['build_command' => 'pnpm build']);
|
|
$secondDeployment = configurationChangedDeployment($application->refresh());
|
|
$application->markDeploymentConfigurationApplied($secondDeployment);
|
|
|
|
expect($secondDeployment->refresh()->configuration_diff['count'])->toBe(1)
|
|
->and(data_get($secondDeployment->configuration_diff, 'changes.0.label'))->toBe('Build command');
|
|
});
|
|
|
|
it('reports noindex domain changes as requiring a redeploy', function () {
|
|
$application = configurationChangedTestApplication([
|
|
'fqdn' => 'https://app.example.com,https://staging.example.com',
|
|
]);
|
|
$deployment = configurationChangedDeployment($application);
|
|
$application->markDeploymentConfigurationApplied($deployment);
|
|
|
|
$application->setNoindexDomains(['https://staging.example.com']);
|
|
$application->save();
|
|
|
|
$diff = $application->refresh()->pendingDeploymentConfigurationDiff();
|
|
$change = collect($diff->changes())->firstWhere('key', 'domains.noindex_domains');
|
|
|
|
expect($diff->isChanged())->toBeTrue()
|
|
->and($change)->not->toBeNull()
|
|
->and($change['label'])->toBe('Search engine indexing')
|
|
->and($change['impact'])->toBe('redeploy');
|
|
});
|
|
|
|
it('reports domain port-only changes as requiring a redeploy', function () {
|
|
$application = configurationChangedTestApplication([
|
|
'fqdn' => 'https://app.example.com',
|
|
'domain_port_overrides' => ['https://app.example.com' => 3000],
|
|
]);
|
|
$deployment = configurationChangedDeployment($application);
|
|
$application->markDeploymentConfigurationApplied($deployment);
|
|
|
|
$application->update([
|
|
'domain_port_overrides' => ['https://app.example.com' => 8080],
|
|
]);
|
|
|
|
$diff = $application->refresh()->pendingDeploymentConfigurationDiff();
|
|
$change = collect($diff->changes())->firstWhere('key', 'domains.domain_port_overrides');
|
|
|
|
expect($diff->isChanged())->toBeTrue()
|
|
->and($change)->not->toBeNull()
|
|
->and($change['impact'])->toBe('redeploy');
|
|
});
|
|
|
|
it('keeps application deployment snapshots stable when port overrides are reordered', function () {
|
|
$application = configurationChangedTestApplication([
|
|
'fqdn' => 'https://one.example.com,https://two.example.com',
|
|
'domain_port_overrides' => [
|
|
'https://one.example.com' => 3000,
|
|
'https://two.example.com' => 8080,
|
|
],
|
|
]);
|
|
$deployment = configurationChangedDeployment($application);
|
|
$application->markDeploymentConfigurationApplied($deployment);
|
|
|
|
$application->update([
|
|
'domain_port_overrides' => [
|
|
'https://two.example.com' => 8080,
|
|
'https://one.example.com' => 3000,
|
|
],
|
|
]);
|
|
|
|
expect($application->refresh()->pendingDeploymentConfigurationDiff()->isChanged())->toBeFalse();
|
|
});
|
|
|
|
it('does not flag applications whose older snapshot omitted noindex domains', function () {
|
|
$application = configurationChangedTestApplication([
|
|
'fqdn' => 'https://app.example.com',
|
|
]);
|
|
$deployment = configurationChangedDeployment($application);
|
|
$application->markDeploymentConfigurationApplied($deployment);
|
|
|
|
$snapshot = $deployment->refresh()->configuration_snapshot;
|
|
$snapshot['sections']['domains']['items'] = collect($snapshot['sections']['domains']['items'])
|
|
->reject(fn (array $item): bool => $item['key'] === 'noindex_domains')
|
|
->values()
|
|
->all();
|
|
$deployment->update(['configuration_snapshot' => $snapshot]);
|
|
|
|
expect($application->refresh()->pendingDeploymentConfigurationDiff()->isChanged())->toBeFalse();
|
|
});
|
|
|
|
it('does not report changes for applications deployed before configuration snapshots existed', function () {
|
|
$application = configurationChangedTestApplication();
|
|
|
|
EnvironmentVariable::create([
|
|
'key' => 'APP_SECRET',
|
|
'value' => 'secret',
|
|
'is_multiline' => false,
|
|
'is_literal' => false,
|
|
'is_buildtime' => true,
|
|
'is_runtime' => true,
|
|
'resourceable_type' => Application::class,
|
|
'resourceable_id' => $application->id,
|
|
]);
|
|
|
|
configurationChangedDeployment($application);
|
|
$application->forceFill(['config_hash' => md5('hash written by an older Coolify version')])->save();
|
|
|
|
$diff = $application->refresh()->pendingDeploymentConfigurationDiff();
|
|
|
|
expect($diff->isChanged())->toBeFalse()
|
|
->and($diff->count())->toBe(0)
|
|
->and($diff->isLegacyFallback())->toBeTrue()
|
|
->and($diff->requiresBuild())->toBeTrue();
|
|
});
|
|
|
|
it('compares with the deployment snapshot once an application without one is deployed again', function () {
|
|
$application = configurationChangedTestApplication();
|
|
configurationChangedDeployment($application);
|
|
$application->forceFill(['config_hash' => md5('hash written by an older Coolify version')])->save();
|
|
|
|
$application->markDeploymentConfigurationApplied(configurationChangedDeployment($application));
|
|
$application->update(['build_command' => 'pnpm build']);
|
|
|
|
$diff = $application->refresh()->pendingDeploymentConfigurationDiff();
|
|
|
|
expect($diff->isLegacyFallback())->toBeFalse()
|
|
->and(collect($diff->changes())->pluck('label')->all())->toBe(['Build command'])
|
|
->and($diff->requiresBuild())->toBeTrue();
|
|
});
|