// ABOUTME: HTTP client for pushing run summaries to the metrics receiver. // ABOUTME: Reads execution context from GitHub Actions style environment variables. package summary import ( "bytes" "context" "encoding/json" "fmt" "net/http" "os" "time" ) // ExecutionContext holds GitHub Actions style identifiers for a workflow run type ExecutionContext struct { Organization string `json:"organization"` Repository string `json:"repository"` Workflow string `json:"workflow"` Job string `json:"job"` RunID string `json:"run_id"` } // MetricsPayload is the complete payload sent to the receiver type MetricsPayload struct { Execution ExecutionContext `json:"execution"` Summary RunSummary `json:"run_summary"` } // PushClient sends metrics to the receiver service type PushClient struct { endpoint string client *http.Client ctx ExecutionContext } // NewPushClient creates a new push client configured from environment variables func NewPushClient(endpoint string) *PushClient { return &PushClient{ endpoint: endpoint, client: &http.Client{ Timeout: 30 * time.Second, }, ctx: ExecutionContextFromEnv(), } } // ExecutionContextFromEnv reads execution context from GitHub Actions environment variables func ExecutionContextFromEnv() ExecutionContext { return ExecutionContext{ Organization: getEnvWithFallback("GITHUB_REPOSITORY_OWNER", "GITEA_REPO_OWNER"), Repository: getEnvWithFallback("GITHUB_REPOSITORY", "GITEA_REPO"), Workflow: getEnvWithFallback("GITHUB_WORKFLOW", "GITEA_WORKFLOW"), Job: getEnvWithFallback("GITHUB_JOB", "GITEA_JOB"), RunID: getEnvWithFallback("GITHUB_RUN_ID", "GITEA_RUN_ID"), } } func getEnvWithFallback(keys ...string) string { for _, key := range keys { if val := os.Getenv(key); val != "" { return val } } return "" } // Push sends the run summary to the receiver func (p *PushClient) Push(ctx context.Context, summary *RunSummary) error { if summary == nil { return nil } payload := MetricsPayload{ Execution: p.ctx, Summary: *summary, } body, err := json.Marshal(payload) if err != nil { return fmt.Errorf("marshaling payload: %w", err) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, p.endpoint, bytes.NewReader(body)) if err != nil { return fmt.Errorf("creating request: %w", err) } req.Header.Set("Content-Type", "application/json") resp, err := p.client.Do(req) if err != nil { return fmt.Errorf("sending request: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode < 200 || resp.StatusCode >= 300 { return fmt.Errorf("unexpected status code: %d", resp.StatusCode) } return nil } // ExecutionContext returns the current execution context func (p *PushClient) ExecutionContext() ExecutionContext { return p.ctx }