124 lines
4.6 KiB
PHP
124 lines
4.6 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\File;
|
|
use Illuminate\Support\Facades\Process;
|
|
|
|
/**
|
|
* Die Decke, gefahren vom echten Agenten.
|
|
*
|
|
* Nicht nachgerechnet: die Klemmung sitzt zwischen `git fetch` und dem
|
|
* Schreiben der Statusdatei, und genau dieses Zusammenspiel ist die Frage.
|
|
* `git` liegt als Attrappe im PATH, deren `fetch` sofort gelingt, ohne eine
|
|
* Gegenstelle zu brauchen — alle uebrigen git-Aufrufe gehen ans echte git im
|
|
* Wegwerf-Repo.
|
|
*/
|
|
function runAgentWithCeiling(?string $ceiling, array $tags, string $deployedVersion = '1.7.3'): array
|
|
{
|
|
// Ein WEGWERF-Checkout mit EIGENEM .git — niemals das Repository dieses
|
|
// Arbeitsbaums.
|
|
//
|
|
// Tags liegen im gemeinsamen .git und sind damit auch fuer den Hauptbaum
|
|
// und jede Parallelsitzung sichtbar. Ein hier angelegtes v9.9.9 wuerde
|
|
// `git tag -l 'v*' --sort=-v:refname | head -1` falsch beantworten — und
|
|
// genau diese Frage entscheidet, wohin ein Server aktualisiert. Stirbt der
|
|
// Test vor seinem Aufraeumen, bliebe es liegen.
|
|
//
|
|
// Der Agent bestimmt seine Wurzel aus dem eigenen Pfad
|
|
// (`ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"`). Es genuegt
|
|
// also, deploy/ zu kopieren und ihn dort zu starten.
|
|
$root = sys_get_temp_dir().'/clupilot-ceiling-'.bin2hex(random_bytes(6));
|
|
File::ensureDirectoryExists($root.'/deploy/lib');
|
|
File::ensureDirectoryExists($root.'/storage/app/deploy');
|
|
|
|
File::copy(base_path('deploy/update-agent.sh'), $root.'/deploy/update-agent.sh');
|
|
File::copy(base_path('deploy/lib/release.sh'), $root.'/deploy/lib/release.sh');
|
|
|
|
// MANIFEST_FILE ist `storage/app/deployment.json` — eine Ebene UEBER
|
|
// storage/app/deploy/. Siehe deploy/lib/release.sh:19.
|
|
File::put($root.'/storage/app/deployment.json', json_encode([
|
|
'version' => $deployedVersion,
|
|
'commit' => 'deadbeef',
|
|
'source' => 'refs/tags/v'.$deployedVersion,
|
|
]));
|
|
|
|
if ($ceiling !== null) {
|
|
File::put($root.'/storage/app/deploy/release-ceiling', $ceiling);
|
|
}
|
|
|
|
$tagCommands = '';
|
|
foreach ($tags as $tag) {
|
|
$tagCommands .= "git tag {$tag}\n";
|
|
}
|
|
|
|
$result = Process::path($root)->timeout(90)->run(<<<BASH
|
|
set -e
|
|
git init -q .
|
|
git -c user.email=t@example.com -c user.name=t commit -q --allow-empty -m init
|
|
{$tagCommands}
|
|
stub="\$(mktemp -d)"
|
|
# Nur `fetch` wird abgefangen — es gibt keine Gegenstelle. Alles andere
|
|
# geht ans echte git und wirkt im Wegwerf-Checkout.
|
|
cat > "\$stub/git" <<'GIT'
|
|
#!/bin/sh
|
|
if [ "\$1" = "fetch" ]; then exit 0; fi
|
|
exec /usr/bin/git "\$@"
|
|
GIT
|
|
chmod +x "\$stub/git"
|
|
printf '#!/bin/sh\nexit 1\n' > "\$stub/docker"
|
|
chmod +x "\$stub/docker"
|
|
PATH="\$stub:\$PATH" bash deploy/update-agent.sh >/dev/null 2>&1 || true
|
|
rm -rf "\$stub"
|
|
BASH);
|
|
|
|
expect($result->successful())->toBeTrue($result->errorOutput());
|
|
|
|
$status = json_decode(File::get($root.'/storage/app/deploy/update-status.json'), true);
|
|
|
|
File::deleteDirectory($root);
|
|
|
|
return $status;
|
|
}
|
|
|
|
it('offers only up to the ceiling', function () {
|
|
// Ausgeliefert 1.7.3, vorhanden bis v9.9.9, Decke auf v9.9.8: genau eine
|
|
// Aktualisierung, und das Ziel ist die Decke.
|
|
$status = runAgentWithCeiling('v9.9.8', ['v9.9.8', 'v9.9.9']);
|
|
|
|
expect($status['target_release'])->toBe('v9.9.8')
|
|
->and($status['behind'])->toBe(1)
|
|
->and($status['ceiling'])->toBe('v9.9.8')
|
|
->and($status['ceiling_error'])->toBe('');
|
|
});
|
|
|
|
it('offers nothing when the ceiling points at a tag that does not exist', function () {
|
|
// Die Decke faellt ZU. Ein Rueckfall auf "neueste" installierte genau das,
|
|
// wovon der Besitzer weggenagelt hat.
|
|
$status = runAgentWithCeiling('v9.9.7', ['v9.9.8', 'v9.9.9']);
|
|
|
|
expect($status['behind'])->toBe(0)
|
|
->and($status['ceiling_error'])->toBe('ceiling_missing')
|
|
->and($status['target_release'])->toBe('');
|
|
});
|
|
|
|
it('offers nothing when the ceiling is malformed', function () {
|
|
$status = runAgentWithCeiling('neueste bitte', ['v9.9.8', 'v9.9.9']);
|
|
|
|
expect($status['behind'])->toBe(0)
|
|
->and($status['ceiling_error'])->toBe('ceiling_invalid');
|
|
});
|
|
|
|
it('behaves exactly as before without a ceiling', function () {
|
|
$status = runAgentWithCeiling(null, ['v9.9.8', 'v9.9.9']);
|
|
|
|
expect($status['target_release'])->toBe('v9.9.9')
|
|
->and($status['ceiling'])->toBe('')
|
|
->and($status['ceiling_error'])->toBe('');
|
|
});
|
|
|
|
it('reports the versions that may be pinned to', function () {
|
|
$status = runAgentWithCeiling(null, ['v9.9.8', 'v9.9.9']);
|
|
|
|
expect($status['releases'])->toContain('v9.9.9')
|
|
->and($status['releases'])->toContain('v9.9.8');
|
|
});
|