coolify/app/Services/DeploymentConfiguration/ConfigurationDiff.php
Andras Bacsai f8849aba73 feat(deployments): track application configuration diffs
Store deployment configuration snapshots on application deployment queues and compare them against the current application state. Surface grouped pending changes in the configuration checker and use build-impact diffs to decide when an existing image can skip the build step.
2026-05-13 09:58:58 +02:00

112 lines
2.9 KiB
PHP

<?php
namespace App\Services\DeploymentConfiguration;
use Illuminate\Support\Collection;
class ConfigurationDiff
{
/**
* @param array<int, array<string, mixed>> $changes
*/
public function __construct(
protected array $changes = [],
protected bool $legacyFallback = false,
) {}
public static function unchanged(): self
{
return new self;
}
public static function legacy(bool $changed): self
{
if (! $changed) {
return self::unchanged();
}
return new self([
[
'key' => 'legacy.configuration',
'section' => 'configuration',
'section_label' => 'Configuration',
'label' => 'Configuration',
'type' => 'changed',
'impact' => 'build',
'sensitive' => false,
'old_display_value' => 'Previously deployed configuration',
'new_display_value' => 'Current configuration',
],
], true);
}
/**
* @param array<int, array<string, mixed>> $changes
*/
public static function fromChanges(array $changes): self
{
return new self(array_values($changes));
}
public function isChanged(): bool
{
return $this->changes !== [];
}
public function isLegacyFallback(): bool
{
return $this->legacyFallback;
}
public function count(): int
{
return count($this->changes);
}
public function requiresBuild(): bool
{
return collect($this->changes)->contains(fn (array $change): bool => $change['impact'] === 'build');
}
public function requiresRedeploy(): bool
{
return $this->isChanged();
}
/**
* @return array<int, array<string, mixed>>
*/
public function changes(): array
{
return $this->changes;
}
/**
* @return array<string, array{label: string, changes: array<int, array<string, mixed>>}>
*/
public function groupedChanges(): array
{
return collect($this->changes)
->groupBy('section')
->map(fn (Collection $changes): array => [
'label' => (string) data_get($changes->first(), 'section_label', str((string) $changes->keys()->first())->headline()),
'changes' => $changes->values()->all(),
])
->all();
}
/**
* @return array{changed: bool, count: int, requires_build: bool, requires_redeploy: bool, legacy_fallback: bool, changes: array<int, array<string, mixed>>}
*/
public function toArray(): array
{
return [
'changed' => $this->isChanged(),
'count' => $this->count(),
'requires_build' => $this->requiresBuild(),
'requires_redeploy' => $this->requiresRedeploy(),
'legacy_fallback' => $this->isLegacyFallback(),
'changes' => $this->changes(),
];
}
}