Gitea's workflow_dispatch endpoint returns 204 No Content with no Location header. DispatchWorkflow required that header, so every successful dispatch errored with "missing Location header" and never yielded a run ID — making the tool unusable for CAD dispatch. - DispatchWorkflow now returns error-only; 204 = success, no Location needed. Body already carried ref+inputs; kept and covered by test. - Tool snapshots the newest existing workflow_dispatch run before dispatch, then polls ListWorkflowRuns after and returns the newest run with ID above that baseline (avoids returning a stale prior run). Falls back to an honest "dispatched, run not yet registered" result rather than failing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
130 lines
3.9 KiB
Go
130 lines
3.9 KiB
Go
package gitea
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/url"
|
|
"strconv"
|
|
)
|
|
|
|
// DispatchWorkflowArgs is the request body for a workflow_dispatch trigger.
|
|
type DispatchWorkflowArgs struct {
|
|
Ref string `json:"ref"`
|
|
Inputs map[string]any `json:"inputs,omitempty"`
|
|
}
|
|
|
|
// DispatchWorkflow triggers a workflow_dispatch event. Gitea returns 204 No
|
|
// Content with NO Location header, so the response carries no run ID — callers
|
|
// resolve the new run separately via ListWorkflowRuns. Returns nil on success.
|
|
func (c *Client) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, args DispatchWorkflowArgs) error {
|
|
p := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/dispatches", owner, repo, workflow)
|
|
payload, err := json.Marshal(args)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
resp, err := c.doRaw(ctx, "POST", p, payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if resp.Status != 204 {
|
|
if mapErr := MapStatus(resp.Status, resp.Body); mapErr != nil {
|
|
return mapErr
|
|
}
|
|
return fmt.Errorf("unexpected status %d", resp.Status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// WorkflowRun represents a Gitea Actions run.
|
|
type WorkflowRun struct {
|
|
ID int64 `json:"id"`
|
|
DisplayTitle string `json:"display_title,omitempty"`
|
|
Status string `json:"status"` // queued | in_progress | completed
|
|
Conclusion string `json:"conclusion"` // success | failure | cancelled | skipped (only when completed)
|
|
Event string `json:"event,omitempty"`
|
|
HeadSHA string `json:"head_sha,omitempty"`
|
|
HeadBranch string `json:"head_branch,omitempty"`
|
|
WorkflowID string `json:"workflow_id,omitempty"`
|
|
RunNumber int64 `json:"run_number,omitempty"`
|
|
StartedAt string `json:"started_at"`
|
|
UpdatedAt string `json:"updated_at,omitempty"`
|
|
HTMLURL string `json:"html_url"`
|
|
Actor struct {
|
|
Login string `json:"login"`
|
|
} `json:"actor,omitempty"`
|
|
}
|
|
|
|
// GetWorkflowRun fetches the status of a specific Actions run.
|
|
func (c *Client) GetWorkflowRun(ctx context.Context, owner, repo string, runID int64) (*WorkflowRun, error) {
|
|
p := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs/%d", owner, repo, runID)
|
|
body, status, err := c.GetJSON(ctx, p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := MapStatus(status, body); err != nil {
|
|
return nil, err
|
|
}
|
|
var run WorkflowRun
|
|
if err := json.Unmarshal(body, &run); err != nil {
|
|
return nil, err
|
|
}
|
|
return &run, nil
|
|
}
|
|
|
|
// ListWorkflowRunsArgs captures the optional query params for ListWorkflowRuns.
|
|
type ListWorkflowRunsArgs struct {
|
|
Branch string
|
|
HeadSHA string
|
|
Status string // queued | in_progress | completed | all
|
|
Event string // push | pull_request | schedule | workflow_dispatch | all
|
|
Workflow string
|
|
Page int
|
|
Limit int
|
|
}
|
|
|
|
type workflowRunsResponse struct {
|
|
TotalCount int64 `json:"total_count"`
|
|
WorkflowRuns []WorkflowRun `json:"workflow_runs"`
|
|
}
|
|
|
|
// ListWorkflowRuns fetches recent Actions runs for a repo with optional filters.
|
|
// Status / Event of "all" or "" are treated as no-filter.
|
|
func (c *Client) ListWorkflowRuns(ctx context.Context, owner, repo string, args ListWorkflowRunsArgs) (*workflowRunsResponse, error) {
|
|
q := url.Values{}
|
|
if args.Branch != "" {
|
|
q.Set("branch", args.Branch)
|
|
}
|
|
if args.HeadSHA != "" {
|
|
q.Set("head_sha", args.HeadSHA)
|
|
}
|
|
if args.Status != "" && args.Status != "all" {
|
|
q.Set("status", args.Status)
|
|
}
|
|
if args.Event != "" && args.Event != "all" {
|
|
q.Set("event", args.Event)
|
|
}
|
|
if args.Workflow != "" {
|
|
q.Set("workflow", args.Workflow)
|
|
}
|
|
if args.Page > 0 {
|
|
q.Set("page", strconv.Itoa(args.Page))
|
|
}
|
|
if args.Limit > 0 {
|
|
q.Set("limit", strconv.Itoa(args.Limit))
|
|
}
|
|
p := fmt.Sprintf("/api/v1/repos/%s/%s/actions/runs?%s", owner, repo, q.Encode())
|
|
body, status, err := c.GetJSON(ctx, p)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := MapStatus(status, body); err != nil {
|
|
return nil, err
|
|
}
|
|
var resp workflowRunsResponse
|
|
if err := json.Unmarshal(body, &resp); err != nil {
|
|
return nil, err
|
|
}
|
|
return &resp, nil
|
|
}
|