85 lines
3.1 KiB
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;
|
|
}
|
|
}
|
|
}
|