A 2026-08-17-i eset általánosítása: a d2d/e2e Docker Swarmban ugyanazt a kódot több service futtatja közös volume-ról, és a queue:work hosszan futó folyamat a betöltött osztályokat a memóriájában tartja. A fájlmásolás után a worker RÉGI kóddal dolgozta fel a jobot - a felület jónak látszott, a háttérfeladat némán a régi viselkedést hozta. - config: 'runtime' minta (app/, config/, database/, routes/, bootstrap/, composer.lock), mert a restart nem csak a jobok változásakor kell; targetenként a service nevek és a Swarm manager elérése - a felületen figyelmeztetés, a teendőkben a konkrét ssh + docker service update parancs - a restart szándékosan a cache ürítés UTÁN: a frissen induló worker különben a régi, cache-elt konfigot töltené be - a d2d schedulernél jelezzük, hogy 0 replikán fut (a restart üresjárat lehet), a t2t-nél pedig azt, hogy queue service még nincs, de a névkonvenció alapján t2t_queue lesz Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
206 lines
8.1 KiB
PHP
206 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Deployment;
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
/**
|
|
* A git diff nyers kimenetéből állítja elő a csomagolható halmazt és a figyelmeztetéseket.
|
|
*
|
|
* Szándékosan nem hív gitet és nem ír fájlt: tisztán a listát és a szabályokat kezeli,
|
|
* így a fázis 2 csomagolója ugyanezt az eredményt tudja majd felhasználni.
|
|
*/
|
|
class ChangeSetAnalyzer
|
|
{
|
|
/**
|
|
* @param array<int, array{status:string, path:string, old_path:?string}> $changedFiles
|
|
* @param array{key:string, label:string, domain:string, remote_path:string, expected_branch:?string}|null $target
|
|
* @return array{
|
|
* files: array<int, array{status:string, path:string, old_path:?string, excluded:bool, action:string}>,
|
|
* copied: array<int, string>,
|
|
* deleted: array<int, string>,
|
|
* counts: array{total:int, copied:int, deleted:int, excluded:int},
|
|
* warnings: array<int, array{level:string, title:string, body:string}>
|
|
* }
|
|
*/
|
|
public function analyze(array $changedFiles, ?array $target = null, ?string $branch = null): array
|
|
{
|
|
/** @var array<int, string> $excludePatterns */
|
|
$excludePatterns = (array) config('deployment.exclude', []);
|
|
|
|
$files = [];
|
|
$copied = [];
|
|
$deleted = [];
|
|
$excludedCount = 0;
|
|
|
|
foreach ($changedFiles as $file) {
|
|
$excluded = Str::is($excludePatterns, $file['path']);
|
|
|
|
// Törlés esetén nincs mit másolni: a fájl a célgépen létezik, itt már nem.
|
|
// Átnevezésnél mindkettő kell - az új útvonal másolandó, a régi törlendő.
|
|
$action = $excluded
|
|
? 'skip'
|
|
: ($file['status'] === 'D' ? 'delete' : 'copy');
|
|
|
|
if ($excluded) {
|
|
$excludedCount++;
|
|
} elseif ($action === 'delete') {
|
|
$deleted[] = $file['path'];
|
|
} else {
|
|
$copied[] = $file['path'];
|
|
|
|
if ($file['old_path'] !== null && ! Str::is($excludePatterns, $file['old_path'])) {
|
|
$deleted[] = $file['old_path'];
|
|
}
|
|
}
|
|
|
|
$files[] = array_merge($file, ['excluded' => $excluded, 'action' => $action]);
|
|
}
|
|
|
|
return [
|
|
'files' => $files,
|
|
'copied' => $copied,
|
|
'deleted' => $deleted,
|
|
'counts' => [
|
|
'total' => count($files),
|
|
'copied' => count($copied),
|
|
'deleted' => count($deleted),
|
|
'excluded' => $excludedCount,
|
|
],
|
|
'warnings' => $this->warnings($copied, $deleted, $excludedCount, $target, $branch),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $copied
|
|
* @param array<int, string> $deleted
|
|
* @param array{key:string, label:string, domain:string, remote_path:string, expected_branch:?string}|null $target
|
|
* @return array<int, array{level:string, title:string, body:string}>
|
|
*/
|
|
private function warnings(array $copied, array $deleted, int $excludedCount, ?array $target, ?string $branch): array
|
|
{
|
|
$warnings = [];
|
|
$maxFiles = (int) config('deployment.max_files', 500);
|
|
|
|
if ($copied === [] && $deleted === []) {
|
|
$warnings[] = [
|
|
'level' => 'info',
|
|
'title' => 'Nincs csomagolható változás',
|
|
'body' => 'A két commit között nincs olyan fájl, ami a célgépre kerülne.',
|
|
];
|
|
}
|
|
|
|
if (count($copied) > $maxFiles) {
|
|
$warnings[] = [
|
|
'level' => 'danger',
|
|
'title' => sprintf('Túl sok fájl (%d db, limit %d)', count($copied), $maxFiles),
|
|
'body' => 'Ekkora tartomány kézi másolással már nehezen ellenőrizhető. Szűkítsd a commit-tartományt, vagy bontsd több csomagra.',
|
|
];
|
|
}
|
|
|
|
$migrations = $this->matching($copied, 'migration');
|
|
|
|
if ($migrations !== []) {
|
|
$warnings[] = [
|
|
'level' => 'warning',
|
|
'title' => sprintf('Migráció a csomagban (%d db)', count($migrations)),
|
|
'body' => 'A fájlok felmásolása után a célgépen le kell futtatni: php artisan migrate',
|
|
];
|
|
}
|
|
|
|
$seeders = $this->matching($copied, 'seeder');
|
|
|
|
if ($seeders !== []) {
|
|
$warnings[] = [
|
|
'level' => 'warning',
|
|
'title' => sprintf('Seeder a csomagban (%d db)', count($seeders)),
|
|
'body' => 'A seeder felmásolása önmagában nem csinál semmit - el kell dönteni, hogy le kell-e futtatni a célgépen. '
|
|
.'Ha feature flageket ír, a lefutása után azok azonnal hatnak.',
|
|
];
|
|
}
|
|
|
|
if ($this->matching($copied, 'composer') !== []) {
|
|
$warnings[] = [
|
|
'level' => 'warning',
|
|
'title' => 'Függőség változott (composer)',
|
|
'body' => 'A vendor/ nincs verziókövetve, ezért nem kerül a csomagba - a célgépen composer install szükséges.',
|
|
];
|
|
}
|
|
|
|
if ($this->matching($copied, 'asset') !== []) {
|
|
$warnings[] = [
|
|
'level' => 'warning',
|
|
'title' => 'Frontend forrás változott',
|
|
'body' => 'A fordított assetek (public/build, public/js, public/css, mix-manifest.json) a .gitignore miatt NEM kerülnek a csomagba. Futtass npm run build-ot, és a fázis 4-től külön csatolhatók lesznek.',
|
|
];
|
|
}
|
|
|
|
if ($this->matching($copied, 'runtime') !== []) {
|
|
$jobs = count($this->matching($copied, 'job'));
|
|
$consoles = count($this->matching($copied, 'console'));
|
|
|
|
$detail = [];
|
|
|
|
if ($jobs > 0) {
|
|
$detail[] = $jobs.' job';
|
|
}
|
|
|
|
if ($consoles > 0) {
|
|
$detail[] = $consoles.' konzol parancs';
|
|
}
|
|
|
|
$warnings[] = [
|
|
'level' => 'warning',
|
|
'title' => 'Hosszan futó konténerek érintve'.($detail === [] ? '' : ' ('.implode(', ', $detail).')'),
|
|
'body' => 'A queue worker és a scheduler külön konténerben fut, és a betöltött osztályokat a memóriájában '
|
|
.'tartja - a fájlok felmásolása után is a RÉGI kóddal dolgozik, amíg újra nem indul. Ez némán rossz '
|
|
.'viselkedést okoz (a job "elkészül", de nem azt csinálja, amit az új kód). A konkrét restart parancsok '
|
|
.'a csomag _TEENDOK.md fájljában lesznek.',
|
|
];
|
|
}
|
|
|
|
if ($deleted !== []) {
|
|
$warnings[] = [
|
|
'level' => 'warning',
|
|
'title' => sprintf('Törlendő fájl a célgépen (%d db)', count($deleted)),
|
|
'body' => 'Ezeket a másolás nem intézi el, kézzel kell törölni - a csomag _TORLENDO.txt fájlja fogja tartalmazni őket.',
|
|
];
|
|
}
|
|
|
|
if ($excludedCount > 0) {
|
|
$warnings[] = [
|
|
'level' => 'info',
|
|
'title' => sprintf('Kihagyott fájl (%d db)', $excludedCount),
|
|
'body' => 'A kizárási lista (.env, storage/, vendor/, node_modules/, .git*) alapján ezek soha nem kerülnek csomagba.',
|
|
];
|
|
}
|
|
|
|
if ($target && $target['expected_branch'] && $branch && $branch !== $target['expected_branch']) {
|
|
$warnings[] = [
|
|
'level' => 'warning',
|
|
'title' => 'Nem a szokásos branch',
|
|
'body' => sprintf(
|
|
'A(z) "%s" környezetre általában a %s branchről megy csomag, most viszont a %s van kiválasztva.',
|
|
$target['label'],
|
|
$target['expected_branch'],
|
|
$branch,
|
|
),
|
|
];
|
|
}
|
|
|
|
return $warnings;
|
|
}
|
|
|
|
/**
|
|
* @param array<int, string> $paths
|
|
* @return array<int, string>
|
|
*/
|
|
private function matching(array $paths, string $group): array
|
|
{
|
|
/** @var array<int, string> $patterns */
|
|
$patterns = (array) config('deployment.attention.'.$group, []);
|
|
|
|
return array_values(array_filter($paths, fn (string $path): bool => Str::is($patterns, $path)));
|
|
}
|
|
}
|