Add Semaphore integration
Fourth live integration: Ansible run history per template with a
trigger-a-run action — matching the "dashboard + basic actions" depth from
the plan. Adapter built against Semaphore's official published OpenAPI spec
(github.com/semaphoreui/semaphore/blob/develop/api-docs.yml) plus its Go
source directly for the task-status enum, which the spec itself leaves
untyped (db/Task.go, pkg/task_logger/task_logger.go) — instance wasn't
reachable from here (semaphore.int.toolabs.se doesn't resolve outside the
user's LAN), so verified against the real spec/source rather than guessing.
server/src/integrations/semaphore/adapter.ts: GET /api/projects, then GET
/api/project/{id}/templates?sort=name&order=asc per project — each template
in that response already embeds its last_task, so listing every template's
current status across every project needs only one call per project (no
N+1 per-template lookup, unlike Gitea where the run history isn't embedded
in the repo list). POST /api/project/{id}/tasks {template_id} triggers a
run. Follows the same config-in-UI + encrypted-credential pattern as the
other three integrations.
Verified: full build passes. Same as Dockhand — unreachable, so ran a
14-check HTTP test against the live server (real target URL, bogus token):
role gating, credential non-leakage, disabled-integration blocking, and the
wrong_type crash-safety check for this fourth adapter type, confirming the
server stays up throughout. Real template/run data and the trigger-a-run
action still need verification once this app can reach the user's LAN.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1504c19bbd
commit
abb5335a52
7 files changed
+419
-1
No files matched your search
@@ -237,6 +237,40 @@ export interface DockhandContainersResponse {
|
||||
summary: { total: number; running: number };
|
||||
}
|
||||
|
||||
export type SemaphoreTaskStatus =
|
||||
| "waiting"
|
||||
| "starting"
|
||||
| "waiting_confirmation"
|
||||
| "confirmed"
|
||||
| "rejected"
|
||||
| "running"
|
||||
| "stopping"
|
||||
| "stopped"
|
||||
| "success"
|
||||
| "error";
|
||||
|
||||
export interface SemaphoreTask {
|
||||
id: number;
|
||||
status: SemaphoreTaskStatus;
|
||||
created: string | null;
|
||||
start: string | null;
|
||||
end: string | null;
|
||||
}
|
||||
|
||||
export interface SemaphoreTemplate {
|
||||
id: number;
|
||||
projectId: number;
|
||||
projectName: string;
|
||||
name: string;
|
||||
playbook: string;
|
||||
lastTask: SemaphoreTask | null;
|
||||
}
|
||||
|
||||
export interface SemaphoreTemplatesResponse {
|
||||
templates: SemaphoreTemplate[];
|
||||
summary: { total: number; failing: number };
|
||||
}
|
||||
|
||||
export class UnauthorizedError extends Error {}
|
||||
export class ForbiddenError extends Error {}
|
||||
|
||||
@@ -434,5 +468,14 @@ export const api = {
|
||||
{ method: "POST" },
|
||||
),
|
||||
},
|
||||
semaphore: {
|
||||
templates: (integrationId: number) =>
|
||||
request<SemaphoreTemplatesResponse>(`/api/integrations/${integrationId}/semaphore/templates`),
|
||||
run: (integrationId: number, projectId: number, templateId: number) =>
|
||||
request<{ task: SemaphoreTask }>(
|
||||
`/api/integrations/${integrationId}/semaphore/projects/${projectId}/templates/${templateId}/run`,
|
||||
{ method: "POST" },
|
||||
),
|
||||
},
|
||||
},
|
||||
};
|
||||
Reference in new issue
Block a user