tenseleyflow/shithub / 6d6524f

Browse files

migration: workflow_disabled table + sqlc queries

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
6d6524fd726e6ee1052b3b58754a8906e76f51b3
Parents
15a9908
Tree
2186cdb

5 changed files

StatusFile+-
A internal/actions/queries/workflow_disabled.sql 29 0
M internal/actions/sqlc/models.go 7 0
M internal/actions/sqlc/querier.go 12 0
A internal/actions/sqlc/workflow_disabled.sql.go 102 0
A internal/migrationsfs/migrations/0064_workflow_disabled.sql 34 0
internal/actions/queries/workflow_disabled.sqladded
@@ -0,0 +1,29 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+
3
+-- name: IsWorkflowDisabled :one
4
+-- Hot path for trigger.Enqueue: skip enqueueing when the row exists.
5
+SELECT EXISTS (
6
+    SELECT 1 FROM workflow_disabled
7
+    WHERE repo_id = $1 AND workflow_file = $2
8
+) AS disabled;
9
+
10
+-- name: ListDisabledWorkflowsForRepo :many
11
+-- Used by the workflows-list endpoint to mark `state: "disabled"`
12
+-- entries without round-tripping through Is for every file.
13
+SELECT workflow_file
14
+FROM workflow_disabled
15
+WHERE repo_id = $1
16
+ORDER BY workflow_file;
17
+
18
+-- name: DisableWorkflow :exec
19
+-- Idempotent: re-disabling an already-disabled workflow is a no-op
20
+-- and does not bump disabled_at.
21
+INSERT INTO workflow_disabled (repo_id, workflow_file, disabled_by_user_id)
22
+VALUES ($1, $2, sqlc.narg(disabled_by_user_id)::bigint)
23
+ON CONFLICT (repo_id, workflow_file) DO NOTHING;
24
+
25
+-- name: EnableWorkflow :execrows
26
+-- Returns affected-rows so the handler can distinguish 200 (re-enabled)
27
+-- from 404-ish no-op.
28
+DELETE FROM workflow_disabled
29
+WHERE repo_id = $1 AND workflow_file = $2;
internal/actions/sqlc/models.gomodified
@@ -2682,6 +2682,13 @@ type WorkflowArtifact struct {
26822682
 	CreatedAt pgtype.Timestamptz
26832683
 }
26842684
 
2685
+type WorkflowDisabled struct {
2686
+	RepoID           int64
2687
+	WorkflowFile     string
2688
+	DisabledByUserID pgtype.Int8
2689
+	DisabledAt       pgtype.Timestamptz
2690
+}
2691
+
26852692
 type WorkflowJob struct {
26862693
 	ID              int64
26872694
 	RunID           int64
internal/actions/sqlc/querier.gomodified
@@ -27,6 +27,12 @@ type Querier interface {
2727
 	DeleteStaleStepLogChunksForCleanup(ctx context.Context, db DBTX, completedAt pgtype.Timestamptz) (int64, error)
2828
 	DeleteStepLogChunks(ctx context.Context, db DBTX, stepID int64) error
2929
 	DeleteWorkflowArtifactsByIDs(ctx context.Context, db DBTX, dollar_1 []int64) (int64, error)
30
+	// Idempotent: re-disabling an already-disabled workflow is a no-op
31
+	// and does not bump disabled_at.
32
+	DisableWorkflow(ctx context.Context, db DBTX, arg DisableWorkflowParams) error
33
+	// Returns affected-rows so the handler can distinguish 200 (re-enabled)
34
+	// from 404-ish no-op.
35
+	EnableWorkflow(ctx context.Context, db DBTX, arg EnableWorkflowParams) (int64, error)
3036
 	// Idempotent insert: if a row with the same (repo_id, workflow_file,
3137
 	// trigger_event_id) already exists, returns no rows (pgx.ErrNoRows in
3238
 	// Go). The handler treats that as a successful no-op so worker
@@ -65,6 +71,9 @@ type Querier interface {
6571
 	InsertWorkflowRun(ctx context.Context, db DBTX, arg InsertWorkflowRunParams) (WorkflowRun, error)
6672
 	// SPDX-License-Identifier: AGPL-3.0-or-later
6773
 	InsertWorkflowStep(ctx context.Context, db DBTX, arg InsertWorkflowStepParams) (WorkflowStep, error)
74
+	// SPDX-License-Identifier: AGPL-3.0-or-later
75
+	// Hot path for trigger.Enqueue: skip enqueueing when the row exists.
76
+	IsWorkflowDisabled(ctx context.Context, db DBTX, arg IsWorkflowDisabledParams) (bool, error)
6877
 	ListActiveWorkflowRunsForAdmin(ctx context.Context, db DBTX, arg ListActiveWorkflowRunsForAdminParams) ([]WorkflowRun, error)
6978
 	ListAllStepLogChunksForStep(ctx context.Context, db DBTX, stepID int64) ([]WorkflowStepLogChunk, error)
7079
 	ListArtifactsForRun(ctx context.Context, db DBTX, runID int64) ([]ListArtifactsForRunRow, error)
@@ -73,6 +82,9 @@ type Querier interface {
7382
 	// cancel request. cancel-in-progress releases the slot by flipping that job
7483
 	// flag even if the runner is still draining the old container.
7584
 	ListBlockingConcurrencyRunsForUpdate(ctx context.Context, db DBTX, arg ListBlockingConcurrencyRunsForUpdateParams) ([]WorkflowRun, error)
85
+	// Used by the workflows-list endpoint to mark `state: "disabled"`
86
+	// entries without round-tripping through Is for every file.
87
+	ListDisabledWorkflowsForRepo(ctx context.Context, db DBTX, repoID int64) ([]string, error)
7688
 	ListExpiredWorkflowArtifactsForCleanup(ctx context.Context, db DBTX, arg ListExpiredWorkflowArtifactsForCleanupParams) ([]ListExpiredWorkflowArtifactsForCleanupRow, error)
7789
 	ListJobsForRun(ctx context.Context, db DBTX, runID int64) ([]ListJobsForRunRow, error)
7890
 	ListOrgSecrets(ctx context.Context, db DBTX, orgID pgtype.Int8) ([]ListOrgSecretsRow, error)
internal/actions/sqlc/workflow_disabled.sql.goadded
@@ -0,0 +1,102 @@
1
+// Code generated by sqlc. DO NOT EDIT.
2
+// versions:
3
+//   sqlc v1.31.1
4
+// source: workflow_disabled.sql
5
+
6
+package actionsdb
7
+
8
+import (
9
+	"context"
10
+
11
+	"github.com/jackc/pgx/v5/pgtype"
12
+)
13
+
14
+const disableWorkflow = `-- name: DisableWorkflow :exec
15
+INSERT INTO workflow_disabled (repo_id, workflow_file, disabled_by_user_id)
16
+VALUES ($1, $2, $3::bigint)
17
+ON CONFLICT (repo_id, workflow_file) DO NOTHING
18
+`
19
+
20
+type DisableWorkflowParams struct {
21
+	RepoID           int64
22
+	WorkflowFile     string
23
+	DisabledByUserID pgtype.Int8
24
+}
25
+
26
+// Idempotent: re-disabling an already-disabled workflow is a no-op
27
+// and does not bump disabled_at.
28
+func (q *Queries) DisableWorkflow(ctx context.Context, db DBTX, arg DisableWorkflowParams) error {
29
+	_, err := db.Exec(ctx, disableWorkflow, arg.RepoID, arg.WorkflowFile, arg.DisabledByUserID)
30
+	return err
31
+}
32
+
33
+const enableWorkflow = `-- name: EnableWorkflow :execrows
34
+DELETE FROM workflow_disabled
35
+WHERE repo_id = $1 AND workflow_file = $2
36
+`
37
+
38
+type EnableWorkflowParams struct {
39
+	RepoID       int64
40
+	WorkflowFile string
41
+}
42
+
43
+// Returns affected-rows so the handler can distinguish 200 (re-enabled)
44
+// from 404-ish no-op.
45
+func (q *Queries) EnableWorkflow(ctx context.Context, db DBTX, arg EnableWorkflowParams) (int64, error) {
46
+	result, err := db.Exec(ctx, enableWorkflow, arg.RepoID, arg.WorkflowFile)
47
+	if err != nil {
48
+		return 0, err
49
+	}
50
+	return result.RowsAffected(), nil
51
+}
52
+
53
+const isWorkflowDisabled = `-- name: IsWorkflowDisabled :one
54
+
55
+SELECT EXISTS (
56
+    SELECT 1 FROM workflow_disabled
57
+    WHERE repo_id = $1 AND workflow_file = $2
58
+) AS disabled
59
+`
60
+
61
+type IsWorkflowDisabledParams struct {
62
+	RepoID       int64
63
+	WorkflowFile string
64
+}
65
+
66
+// SPDX-License-Identifier: AGPL-3.0-or-later
67
+// Hot path for trigger.Enqueue: skip enqueueing when the row exists.
68
+func (q *Queries) IsWorkflowDisabled(ctx context.Context, db DBTX, arg IsWorkflowDisabledParams) (bool, error) {
69
+	row := db.QueryRow(ctx, isWorkflowDisabled, arg.RepoID, arg.WorkflowFile)
70
+	var disabled bool
71
+	err := row.Scan(&disabled)
72
+	return disabled, err
73
+}
74
+
75
+const listDisabledWorkflowsForRepo = `-- name: ListDisabledWorkflowsForRepo :many
76
+SELECT workflow_file
77
+FROM workflow_disabled
78
+WHERE repo_id = $1
79
+ORDER BY workflow_file
80
+`
81
+
82
+// Used by the workflows-list endpoint to mark `state: "disabled"`
83
+// entries without round-tripping through Is for every file.
84
+func (q *Queries) ListDisabledWorkflowsForRepo(ctx context.Context, db DBTX, repoID int64) ([]string, error) {
85
+	rows, err := db.Query(ctx, listDisabledWorkflowsForRepo, repoID)
86
+	if err != nil {
87
+		return nil, err
88
+	}
89
+	defer rows.Close()
90
+	items := []string{}
91
+	for rows.Next() {
92
+		var workflow_file string
93
+		if err := rows.Scan(&workflow_file); err != nil {
94
+			return nil, err
95
+		}
96
+		items = append(items, workflow_file)
97
+	}
98
+	if err := rows.Err(); err != nil {
99
+		return nil, err
100
+	}
101
+	return items, nil
102
+}
internal/migrationsfs/migrations/0064_workflow_disabled.sqladded
@@ -0,0 +1,34 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+--
3
+-- Per-workflow enable/disable knob for the §13 REST surface.
4
+--
5
+-- We don't have a `workflows` table (workflows are discovered from
6
+-- the git tree on demand), so the discriminator here is the
7
+-- repo-relative path (e.g. ".shithub/workflows/ci.yml"). One row per
8
+-- disabled workflow file; absence of a row means the workflow runs
9
+-- normally.
10
+--
11
+-- The trigger pipeline consults this table on every event: a disabled
12
+-- workflow's runs are not enqueued. Re-enabling (DELETE the row) is
13
+-- the inverse — the next matching event resumes triggering as
14
+-- normal. This is intentionally a flag, not an audit log; the
15
+-- `audit_log` table records the actor + reason for the operator
16
+-- forensics path.
17
+
18
+-- +goose Up
19
+CREATE TABLE workflow_disabled (
20
+    repo_id            bigint      NOT NULL REFERENCES repos(id) ON DELETE CASCADE,
21
+    workflow_file      text        NOT NULL,
22
+    disabled_by_user_id bigint     REFERENCES users(id) ON DELETE SET NULL,
23
+    disabled_at        timestamptz NOT NULL DEFAULT now(),
24
+
25
+    PRIMARY KEY (repo_id, workflow_file),
26
+    CONSTRAINT workflow_disabled_file_format
27
+        CHECK (workflow_file LIKE '.shithub/workflows/%')
28
+);
29
+
30
+CREATE INDEX workflow_disabled_repo_id_idx
31
+    ON workflow_disabled (repo_id);
32
+
33
+-- +goose Down
34
+DROP TABLE IF EXISTS workflow_disabled;