Go · 4214 bytes Raw Blame History
1 // SPDX-License-Identifier: AGPL-3.0-or-later
2
3 // Package events emits webhook-facing Actions lifecycle events.
4 //
5 // These payloads are deliberately structural. Do not include workflow
6 // event_payload, env, permissions, step logs, runner JWTs, or secret material.
7 package events
8
9 import (
10 "context"
11
12 "github.com/jackc/pgx/v5"
13 "github.com/jackc/pgx/v5/pgtype"
14
15 actionsdb "github.com/tenseleyFlow/shithub/internal/actions/sqlc"
16 "github.com/tenseleyFlow/shithub/internal/notif"
17 )
18
19 const (
20 KindWorkflowRun = "workflow_run"
21 KindWorkflowJob = "workflow_job"
22
23 ActionQueued = "queued"
24 ActionRunning = "running"
25 ActionCompleted = "completed"
26 ActionCancelled = "cancelled"
27 )
28
29 // EmitRunTx writes one workflow_run domain event inside the caller's
30 // transaction. Use it when the run mutation and event row must commit
31 // atomically.
32 func EmitRunTx(ctx context.Context, tx pgx.Tx, run actionsdb.WorkflowRun, action string) error {
33 return notif.EmitTx(ctx, tx, runEvent(run, action))
34 }
35
36 // EmitJobTx writes one workflow_job domain event inside the caller's
37 // transaction. The parent run snapshot is included so webhook subscribers do
38 // not need a second API call to identify the workflow execution.
39 func EmitJobTx(ctx context.Context, tx pgx.Tx, run actionsdb.WorkflowRun, job actionsdb.WorkflowJob, action string) error {
40 return notif.EmitTx(ctx, tx, jobEvent(run, job, action))
41 }
42
43 func runEvent(run actionsdb.WorkflowRun, action string) notif.Event {
44 return notif.Event{
45 ActorUserID: int8Value(run.ActorUserID),
46 Kind: KindWorkflowRun,
47 RepoID: run.RepoID,
48 SourceKind: KindWorkflowRun,
49 SourceID: run.ID,
50 Public: false,
51 Extra: map[string]any{
52 "action": action,
53 "workflow_run": runPayload(run),
54 },
55 }
56 }
57
58 func jobEvent(run actionsdb.WorkflowRun, job actionsdb.WorkflowJob, action string) notif.Event {
59 return notif.Event{
60 ActorUserID: int8Value(run.ActorUserID),
61 Kind: KindWorkflowJob,
62 RepoID: run.RepoID,
63 SourceKind: KindWorkflowJob,
64 SourceID: job.ID,
65 Public: false,
66 Extra: map[string]any{
67 "action": action,
68 "workflow_run": runPayload(run),
69 "workflow_job": jobPayload(job),
70 },
71 }
72 }
73
74 func runPayload(run actionsdb.WorkflowRun) map[string]any {
75 return map[string]any{
76 "id": run.ID,
77 "repo_id": run.RepoID,
78 "run_index": run.RunIndex,
79 "workflow_file": run.WorkflowFile,
80 "workflow_name": run.WorkflowName,
81 "head_sha": run.HeadSha,
82 "head_ref": run.HeadRef,
83 "event": string(run.Event),
84 "status": string(run.Status),
85 "conclusion": conclusionValue(run.Conclusion),
86 "actor_user_id": int8Nullable(run.ActorUserID),
87 "parent_run_id": int8Nullable(run.ParentRunID),
88 "created_at": timeValue(run.CreatedAt),
89 "updated_at": timeValue(run.UpdatedAt),
90 "started_at": timeValue(run.StartedAt),
91 "completed_at": timeValue(run.CompletedAt),
92 "trigger_event_id": run.TriggerEventID,
93 }
94 }
95
96 func jobPayload(job actionsdb.WorkflowJob) map[string]any {
97 return map[string]any{
98 "id": job.ID,
99 "run_id": job.RunID,
100 "job_index": job.JobIndex,
101 "job_key": job.JobKey,
102 "job_name": job.JobName,
103 "runs_on": job.RunsOn,
104 "runner_id": int8Nullable(job.RunnerID),
105 "needs_jobs": job.NeedsJobs,
106 "timeout_minutes": job.TimeoutMinutes,
107 "status": string(job.Status),
108 "conclusion": conclusionValue(job.Conclusion),
109 "cancel_requested": job.CancelRequested,
110 "created_at": timeValue(job.CreatedAt),
111 "updated_at": timeValue(job.UpdatedAt),
112 "started_at": timeValue(job.StartedAt),
113 "completed_at": timeValue(job.CompletedAt),
114 }
115 }
116
117 func conclusionValue(v actionsdb.NullCheckConclusion) any {
118 if !v.Valid {
119 return nil
120 }
121 return string(v.CheckConclusion)
122 }
123
124 func int8Value(v pgtype.Int8) int64 {
125 if !v.Valid {
126 return 0
127 }
128 return v.Int64
129 }
130
131 func int8Nullable(v pgtype.Int8) any {
132 if !v.Valid {
133 return nil
134 }
135 return v.Int64
136 }
137
138 func timeValue(v pgtype.Timestamptz) any {
139 if !v.Valid {
140 return nil
141 }
142 return v.Time.UTC().Format("2006-01-02T15:04:05.999999999Z07:00")
143 }
144