clusev/app/Services/PromotionService.php

85 lines
3.1 KiB
PHP

<?php
namespace App\Services;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
/**
* Triggers the public-promotion / yank GitHub Actions workflows on the private staging repo via the
* GitHub API (workflow_dispatch), and reads the public repo's tags. The trigger token
* (config clusev.github_token, needs Actions:Write) ONLY dispatches — the workflows do the public
* push/delete with their own PUBLIC_REPO_TOKEN. Reads of the public repo's tags are anonymous (it is
* public). Every call degrades to false / [] (never throws); the token is only sent in the header.
*/
class PromotionService
{
public function deployPublic(string $betaTag): bool
{
return $this->dispatch('promote-public.yml', ['tag' => $betaTag]);
}
public function yank(string $tag): bool
{
return $this->dispatch('yank.yml', ['tag' => $tag]);
}
/**
* The public repo's tag names, newest first (anonymous read; the repo is public). [] on failure.
*
* @return array<int,string>
*/
public function publicTags(int $limit = 10): array
{
$slug = (string) config('clusev.public_slug');
if ($slug === '') {
return [];
}
// Cached: render() (incl. the 5s page poll) calls this every render, and the anonymous GitHub
// tag API is rate-limited (60/h per IP). 120s keeps it well under the limit; a yank reflects
// once the workflow has actually deleted the tag (~minutes) anyway, so staleness is harmless.
return Cache::remember("clusev:public-tags:{$slug}:{$limit}", 120, function () use ($slug, $limit) {
try {
$res = Http::timeout(5)->acceptJson()->withHeaders(['User-Agent' => 'Clusev-Panel'])
->get("https://api.github.com/repos/{$slug}/tags", ['per_page' => $limit]);
if (! $res->successful()) {
return [];
}
return array_values(array_filter(
array_map(static fn ($t): string => is_array($t) ? (string) ($t['name'] ?? '') : '', (array) $res->json()),
static fn (string $n): bool => $n !== '',
));
} catch (\Throwable $e) {
report($e);
return [];
}
});
}
/** @param array<string,string> $inputs */
private function dispatch(string $workflowFile, array $inputs): bool
{
$token = (string) config('clusev.github_token');
$slug = (string) config('clusev.staging_slug');
if ($token === '' || $slug === '') {
return false;
}
try {
$res = Http::timeout(5)->withToken($token)->acceptJson()->withHeaders(['User-Agent' => 'Clusev-Panel'])
->post("https://api.github.com/repos/{$slug}/actions/workflows/{$workflowFile}/dispatches", [
'ref' => (string) config('clusev.release_branch'),
'inputs' => $inputs,
]);
return $res->successful(); // 204 No Content on success
} catch (\Throwable $e) {
report($e);
return false;
}
}
}