Adds a scope-based access control layer to all 81 existing API routes. Sits alongside existing session-cookie auth (does not replace it). What this PR does: - Audits every existing route and documents required scope per route (see documentation/auth/route-scope-audit.md) - Adds classes/auth/scope.php with 10 scope constants and role→scope defaults - Adds classes/auth/scope_middleware.php with requireScope/requireAnyScope/requireRole - Applies require*() calls to all 81 existing routes - Adds ScopeMiddlewareTest (unit, 178 lines) and RouteScopeTest (integration, 212 lines) Coexistence note: This branch's classes/auth/scope.php is a stub that will be replaced by classes/auth/scope_registry.php (from TRU-145 / PR #396) when that PR merges first. The two have compatible APIs. Refs: TRU-149
648 lines
31 KiB
PHP
648 lines
31 KiB
PHP
<?php
|
|
|
|
namespace routes;
|
|
|
|
use classes\authentication;
|
|
use classes\release_manager;
|
|
use Throwable;
|
|
use traits\route_t;
|
|
|
|
use app\auth\Scope;
|
|
use app\auth\ScopeMiddleware;
|
|
|
|
class releaseManagerRoute
|
|
{
|
|
use route_t;
|
|
|
|
public function run(): void
|
|
{
|
|
$this->get('/release/bootstrap', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/release/bootstrap');
|
|
global $response;
|
|
$response->success((new release_manager())->bootstrap());
|
|
});
|
|
|
|
$this->get('/release/runtime', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/release/runtime');
|
|
global $response;
|
|
$response->success((new release_manager())->runtimeForCurrentPrincipal($this->getParametersAsArray()));
|
|
});
|
|
|
|
$this->post('/release/timeline/events', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/timeline/events');
|
|
global $response;
|
|
$payload = $this->requestPayload();
|
|
$events = is_array($payload['events'] ?? null) ? $payload['events'] : ($payload['event'] ?? $payload);
|
|
$context = is_array($payload['context'] ?? null) ? $payload['context'] : [];
|
|
$response->success((new release_manager())->ingestTimelineEvents(
|
|
is_array($events) ? $events : [],
|
|
$context
|
|
), 202);
|
|
});
|
|
|
|
$this->post('/release/github/webhook', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/github/webhook');
|
|
global $response;
|
|
try {
|
|
$headers = function_exists('getallheaders') ? getallheaders() : [];
|
|
$rawBody = file_get_contents('php://input') ?: '';
|
|
$response->success((new release_manager())->handleGithubWebhook($headers, $rawBody), 202);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 401);
|
|
}
|
|
});
|
|
|
|
$this->post('/release/gate/test-runs', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/gate/test-runs');
|
|
global $response;
|
|
$manager = new release_manager();
|
|
if (!$manager->verifyReleaseGateToken($this->releaseGateToken())) {
|
|
$response->error(['message' => 'Invalid release gate token.'], 401);
|
|
}
|
|
|
|
try {
|
|
$response->success($manager->runReleaseTest($this->requestPayload(), null), 202);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
});
|
|
|
|
$this->post('/release/gate/frontend-version', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/release/gate/frontend-version');
|
|
global $response;
|
|
$manager = new release_manager();
|
|
if (!$manager->verifyReleaseGateToken($this->releaseGateToken())) {
|
|
$response->error(['message' => 'Invalid release gate token.'], 401);
|
|
}
|
|
|
|
try {
|
|
$release = release_manager::normalizeFrontendVersionGateInput($this->requestPayload());
|
|
redis->set('worker_target_version', $release['version']);
|
|
$observed = (string)(redis->get('worker_target_version') ?? '');
|
|
if (!hash_equals($release['version'], $observed)) {
|
|
throw new \RuntimeException('Frontend release version read-back failed.');
|
|
}
|
|
|
|
$response->success([
|
|
'version' => $observed,
|
|
'repository' => $release['repository'],
|
|
'branch' => $release['branch'],
|
|
'build_id' => $release['build_id'],
|
|
]);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 422);
|
|
}
|
|
});
|
|
|
|
$this->get('/release/gate/frontend-version', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/release/gate/frontend-version');
|
|
global $response;
|
|
$manager = new release_manager();
|
|
if (!$manager->verifyReleaseGateToken($this->releaseGateToken())) {
|
|
$response->error(['message' => 'Invalid release gate token.'], 401);
|
|
}
|
|
|
|
$response->success([
|
|
'version' => strtolower(trim((string)(redis->get('worker_target_version') ?? ''))),
|
|
]);
|
|
});
|
|
|
|
$this->get('/superuser/releases', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$response->success((new release_manager())->summary());
|
|
}, [
|
|
'superuser_release_manager_view' => 'View release manager channels, deployments, and health',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/config', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/config');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$response->success((new release_manager())->releaseConfig());
|
|
}, [
|
|
'superuser_release_manager_view' => 'View Release Manager source configuration',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/operations', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/operations');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$response->success((new release_manager())->listOperations($this->getParametersAsArray()));
|
|
}, [
|
|
'superuser_release_manager_view' => 'View release operation runs',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/operations/{id}', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/operations/{id}');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
try {
|
|
$response->success((new release_manager())->operationDetail($this->routeId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 404);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_view' => 'Inspect release operation diagnostics',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/test-runs', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/test-runs');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
$response->success((new release_manager())->runReleaseTest($this->requestPayload(), $this->actorUserId()), 202);
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Run Release Manager tests with operation diagnostics',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/coolify-cleanup/preview', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/coolify-cleanup/preview');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->previewCoolifyCleanup($this->requestPayload(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Preview Release Manager Coolify resource cleanup',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/coolify-cleanup/apply', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/coolify-cleanup/apply');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
$this->requirePermission('superuser_coolify_manage');
|
|
try {
|
|
$response->success((new release_manager())->applyCoolifyCleanup($this->requestPayload(), $this->actorUserId()), 202);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Apply Release Manager Coolify resource cleanup',
|
|
'superuser_coolify_manage' => 'Stop or delete Coolify resources managed by Release Manager',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/config', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/config');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_manage');
|
|
try {
|
|
$response->success((new release_manager())->updateReleaseConfig($this->requestPayload(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_manage' => 'Update Release Manager source configuration',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/github/repositories', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/github/repositories');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
try {
|
|
$response->success((new release_manager())->listGithubRepositories($this->getParametersAsArray()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_view' => 'List private GitHub repositories available to Release Manager',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/github/branches', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/github/branches');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->listGithubBranches($this->getParametersAsArray()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'List GitHub branches for a Release Manager repository',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/github/commits', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/github/commits');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->listGithubCommits($this->getParametersAsArray()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'List or resolve GitHub commits for a Release Manager repository',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/github/test', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/github/test');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
$response->success((new release_manager())->testGithubRepositoryAccess($this->requestPayload()));
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Test Release Manager access to a GitHub repository, branch, and commit',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/channels', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/channels');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$response->success((new release_manager())->listChannels());
|
|
}, [
|
|
'superuser_release_manager_view' => 'View release channels',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/channels', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_manage');
|
|
try {
|
|
$response->success((new release_manager())->createChannel($this->requestPayload(), $this->actorUserId()), 201);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_manage' => 'Create release channels',
|
|
]);
|
|
|
|
$this->patch('/superuser/releases/channels/{id}', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_manage');
|
|
try {
|
|
$response->success((new release_manager())->updateChannel($this->routeId(), $this->requestPayload(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_manage' => 'Update release channels',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/channels/{id}/rollback', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}/rollback');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_rollback');
|
|
try {
|
|
$response->success((new release_manager())->rollbackChannel($this->routeId(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_rollback' => 'Rollback an active release channel',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/channels/{id}/sync', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}/sync');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->syncChannel($this->routeId(), $this->actorUserId()), 202);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Sync the latest frontend and API branch commits into a release channel',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/channels/{id}/bundle', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/channels/{id}/bundle');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->setChannelBundle($this->routeId(), $this->requestPayload(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Set the active release bundle for a channel',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/assignments', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/assignments');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$response->success((new release_manager())->listAssignments());
|
|
}, [
|
|
'superuser_release_manager_view' => 'View release channel assignments',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/assignment-subjects', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/assignment-subjects');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_manage');
|
|
$response->success((new release_manager())->searchAssignmentSubjects($this->getParametersAsArray()));
|
|
}, [
|
|
'superuser_release_manager_manage' => 'Search users, subusers, and customers for Release Manager assignments',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/assignments', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/assignments');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_manage');
|
|
try {
|
|
$response->success((new release_manager())->createAssignment($this->requestPayload(), $this->actorUserId()), 201);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_manage' => 'Assign users, subusers, or customers to release channels',
|
|
]);
|
|
|
|
$this->delete('/superuser/releases/assignments/{id}', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/assignments/{id}');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_manage');
|
|
try {
|
|
$response->success((new release_manager())->deleteAssignment($this->routeId(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 404);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_manage' => 'Remove release channel assignments',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/targets', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/targets');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
$response->success((new release_manager())->listDeploymentTargets());
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'View release deployment targets',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/targets', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/targets');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->upsertDeploymentTarget($this->requestPayload(), $this->actorUserId()), 201);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Create or update GitHub to Coolify release deployment targets',
|
|
]);
|
|
|
|
$this->delete('/superuser/releases/targets/{id}', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/targets/{id}');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->deleteDeploymentTarget($this->routeId(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 404);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Delete release deployment targets',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/service-sets', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/service-sets');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$response->success((new release_manager())->listServiceSets());
|
|
}, [
|
|
'superuser_release_manager_view' => 'View reusable Release Manager service sets',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/service-sets', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/service-sets');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->createServiceSet($this->requestPayload(), $this->actorUserId()), 201);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Create reusable Release Manager service sets',
|
|
]);
|
|
|
|
$this->delete('/superuser/releases/service-sets/{id}', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/service-sets/{id}');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success(
|
|
(new release_manager())->deleteServiceSet(
|
|
$this->routeId(),
|
|
$this->requestPayload(),
|
|
$this->actorUserId()
|
|
)
|
|
);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Remove inactive isolated Release Manager service sets',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/service-sets/{id}/isolated-data-services', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/service-sets/{id}/isolated-data-services');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success(
|
|
(new release_manager())->completeIsolatedStackDataServices(
|
|
$this->routeId(),
|
|
$this->requestPayload(),
|
|
$this->actorUserId()
|
|
),
|
|
202
|
|
);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Create missing isolated stack database, Redis, and MinIO services',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/bundles', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/bundles');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$limit = (int)($this->getParameter('limit') ?? 50);
|
|
$response->success((new release_manager())->listBundles($limit));
|
|
}, [
|
|
'superuser_release_manager_view' => 'View Release Manager bundles',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/bundles', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/bundles');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->createBundle($this->requestPayload(), $this->actorUserId()), 201);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Create Release Manager full-stack bundles',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/bundles/{id}/deploy', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/bundles/{id}/deploy');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->deployBundle($this->routeId(), $this->actorUserId()), 202);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Deploy Release Manager full-stack bundles',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/bundles/{id}/promote', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/bundles/{id}/promote');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->promoteBundle($this->routeId(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Promote Release Manager bundles without data failover',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/deployments', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/deployments');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_view');
|
|
$limit = (int)($this->getParameter('limit') ?? 50);
|
|
$response->success((new release_manager())->listDeployments($limit));
|
|
}, [
|
|
'superuser_release_manager_view' => 'View release deployments',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/deployments', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/deployments');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->startDeployment($this->requestPayload(), $this->actorUserId()), 202);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Trigger release deployments from GitHub/Coolify targets',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/deployments/{id}/promote', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/deployments/{id}/promote');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
try {
|
|
$response->success((new release_manager())->promoteDeployment($this->routeId(), $this->actorUserId()));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 409);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Promote a deployment to its release channel',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/issues/actions', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/issues/actions');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_deploy');
|
|
$response->success((new release_manager())->runIssueAction($this->requestPayload(), $this->actorUserId()));
|
|
}, [
|
|
'superuser_release_manager_deploy' => 'Run a safe Release Manager issue resolution action',
|
|
]);
|
|
|
|
$this->post('/superuser/releases/replay-targets', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_WRITE, '/superuser/releases/replay-targets');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_replay');
|
|
try {
|
|
$response->success((new release_manager())->setReplayTarget($this->requestPayload(), $this->actorUserId()), 201);
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 400);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_replay' => 'Enable release timeline replay capture for a user, customer, subuser, or channel',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/timeline/sessions', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/timeline/sessions');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_replay');
|
|
$response->success((new release_manager())->listTimelineSessions($this->getParametersAsArray()));
|
|
}, [
|
|
'superuser_release_manager_replay' => 'List release timeline replay sessions',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/timeline/sessions/{traceId}', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/timeline/sessions/{traceId}');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_replay');
|
|
try {
|
|
$response->success((new release_manager())->timelineSessionDetail((string)$this->fromRoute('traceId')));
|
|
} catch (Throwable $throwable) {
|
|
$response->error(['message' => $throwable->getMessage()], 404);
|
|
}
|
|
}, [
|
|
'superuser_release_manager_replay' => 'Inspect one release timeline replay session',
|
|
]);
|
|
|
|
$this->get('/superuser/releases/timeline', function () {
|
|
ScopeMiddleware::requireScope(Scope::SUPERUSER_READ, '/superuser/releases/timeline');
|
|
global $response;
|
|
$this->requirePermission('superuser_release_manager_replay');
|
|
$response->success((new release_manager())->searchTimeline($this->getParametersAsArray()));
|
|
}, [
|
|
'superuser_release_manager_replay' => 'Replay release failure timelines',
|
|
]);
|
|
}
|
|
|
|
private function routeId(): int
|
|
{
|
|
$id = (int)$this->fromRoute('id');
|
|
$this->requireParameterIntPositive($id, 'id');
|
|
return $id;
|
|
}
|
|
|
|
private function actorUserId(): ?int
|
|
{
|
|
try {
|
|
$user = (new authentication())->get_user();
|
|
return $user !== false && isset($user->id) ? (int)$user->id : null;
|
|
} catch (Throwable) {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function requestPayload(): array
|
|
{
|
|
$payload = json_decode(file_get_contents('php://input'), true);
|
|
if (!is_array($payload)) {
|
|
$payload = [];
|
|
}
|
|
|
|
if ($_GET !== []) {
|
|
$payload = array_replace($payload, $_GET);
|
|
}
|
|
|
|
return $payload;
|
|
}
|
|
|
|
private function releaseGateToken(): string
|
|
{
|
|
$headers = function_exists('getallheaders') ? getallheaders() : [];
|
|
$authorization = (string)($headers['Authorization'] ?? $headers['authorization'] ?? $_SERVER['HTTP_AUTHORIZATION'] ?? '');
|
|
if (preg_match('/^Bearer\s+(.+)$/i', $authorization, $matches) === 1) {
|
|
return trim($matches[1]);
|
|
}
|
|
|
|
return trim((string)(
|
|
$headers['X-Release-Gate-Token']
|
|
?? $headers['x-release-gate-token']
|
|
?? $_SERVER['HTTP_X_RELEASE_GATE_TOKEN']
|
|
?? ''
|
|
));
|
|
}
|
|
}
|