Signed-off-by: Anirudh Oppiliappan anirudh@tangled.org
+242
cmd/populatepipelines/populate_pipelines.go
+242
cmd/populatepipelines/populate_pipelines.go
···
···
1
+
package main
2
+
3
+
import (
4
+
"database/sql"
5
+
"flag"
6
+
"fmt"
7
+
"log"
8
+
"math/rand"
9
+
"time"
10
+
11
+
"github.com/bluesky-social/indigo/atproto/syntax"
12
+
_ "github.com/mattn/go-sqlite3"
13
+
)
14
+
15
+
var (
16
+
dbPath = flag.String("db", "appview.db", "Path to SQLite database")
17
+
count = flag.Int("count", 10, "Number of pipeline runs to generate")
18
+
repo = flag.String("repo", "", "Repository name (e.g., 'did:plc:xyz/myrepo')")
19
+
knot = flag.String("knot", "localhost:8100", "Knot hostname")
20
+
)
21
+
22
+
// StatusKind represents the status of a workflow
23
+
type StatusKind string
24
+
25
+
const (
26
+
StatusKindPending StatusKind = "pending"
27
+
StatusKindRunning StatusKind = "running"
28
+
StatusKindFailed StatusKind = "failed"
29
+
StatusKindTimeout StatusKind = "timeout"
30
+
StatusKindCancelled StatusKind = "cancelled"
31
+
StatusKindSuccess StatusKind = "success"
32
+
)
33
+
34
+
var finishStatuses = []StatusKind{
35
+
StatusKindFailed,
36
+
StatusKindTimeout,
37
+
StatusKindCancelled,
38
+
StatusKindSuccess,
39
+
}
40
+
41
+
// generateRandomSha generates a random 40-character SHA
42
+
func generateRandomSha() string {
43
+
const hexChars = "0123456789abcdef"
44
+
sha := make([]byte, 40)
45
+
for i := range sha {
46
+
sha[i] = hexChars[rand.Intn(len(hexChars))]
47
+
}
48
+
return string(sha)
49
+
}
50
+
51
+
// generateRkey generates a TID-like rkey
52
+
func generateRkey() string {
53
+
// Simple timestamp-based rkey
54
+
now := time.Now().UnixMicro()
55
+
return fmt.Sprintf("%d", now)
56
+
}
57
+
58
+
func main() {
59
+
flag.Parse()
60
+
61
+
if *repo == "" {
62
+
log.Fatal("--repo is required (format: did:plc:xyz/reponame)")
63
+
}
64
+
65
+
// Parse repo into owner and name
66
+
did, repoName, ok := parseRepo(*repo)
67
+
if !ok {
68
+
log.Fatalf("Invalid repo format: %s (expected: did:plc:xyz/reponame)", *repo)
69
+
}
70
+
71
+
db, err := sql.Open("sqlite3", *dbPath)
72
+
if err != nil {
73
+
log.Fatalf("Failed to open database: %v", err)
74
+
}
75
+
defer db.Close()
76
+
77
+
rand.Seed(time.Now().UnixNano())
78
+
79
+
branches := []string{"main", "develop", "feature/auth", "fix/bugs"}
80
+
workflows := []string{"test", "build", "lint", "deploy"}
81
+
82
+
log.Printf("Generating %d pipeline runs for %s...\n", *count, *repo)
83
+
84
+
for i := 0; i < *count; i++ {
85
+
// Random trigger type
86
+
isPush := rand.Float32() > 0.3 // 70% push, 30% PR
87
+
88
+
var triggerId int64
89
+
if isPush {
90
+
triggerId, err = createPushTrigger(db, branches)
91
+
} else {
92
+
triggerId, err = createPRTrigger(db, branches)
93
+
}
94
+
if err != nil {
95
+
log.Fatalf("Failed to create trigger: %v", err)
96
+
}
97
+
98
+
// Create pipeline
99
+
pipelineRkey := generateRkey()
100
+
sha := generateRandomSha()
101
+
createdTime := time.Now().Add(-time.Duration(rand.Intn(7*24*60)) * time.Minute) // Random time in last week
102
+
103
+
_, err = db.Exec(`
104
+
INSERT INTO pipelines (knot, rkey, repo_owner, repo_name, sha, created, trigger_id)
105
+
VALUES (?, ?, ?, ?, ?, ?, ?)
106
+
`, *knot, pipelineRkey, did, repoName, sha, createdTime.Format(time.RFC3339), triggerId)
107
+
108
+
if err != nil {
109
+
log.Fatalf("Failed to create pipeline: %v", err)
110
+
}
111
+
112
+
// Create workflow statuses
113
+
numWorkflows := rand.Intn(len(workflows)-1) + 2 // 2-4 workflows
114
+
selectedWorkflows := make([]string, numWorkflows)
115
+
perm := rand.Perm(len(workflows))
116
+
for j := 0; j < numWorkflows; j++ {
117
+
selectedWorkflows[j] = workflows[perm[j]]
118
+
}
119
+
120
+
for _, workflow := range selectedWorkflows {
121
+
err = createWorkflowStatuses(db, *knot, pipelineRkey, workflow, createdTime)
122
+
if err != nil {
123
+
log.Fatalf("Failed to create workflow statuses: %v", err)
124
+
}
125
+
}
126
+
127
+
log.Printf("Created pipeline %d/%d (rkey: %s)\n", i+1, *count, pipelineRkey)
128
+
129
+
// Small delay to ensure unique rkeys
130
+
time.Sleep(2 * time.Millisecond)
131
+
}
132
+
133
+
log.Println("✓ Pipeline population complete!")
134
+
}
135
+
136
+
func parseRepo(repo string) (syntax.DID, string, bool) {
137
+
// Simple parser for "did:plc:xyz/reponame"
138
+
for i := 0; i < len(repo); i++ {
139
+
if repo[i] == '/' {
140
+
did := syntax.DID(repo[:i])
141
+
name := repo[i+1:]
142
+
if did != "" && name != "" {
143
+
return did, name, true
144
+
}
145
+
}
146
+
}
147
+
return "", "", false
148
+
}
149
+
150
+
func createPushTrigger(db *sql.DB, branches []string) (int64, error) {
151
+
branch := branches[rand.Intn(len(branches))]
152
+
oldSha := generateRandomSha()
153
+
newSha := generateRandomSha()
154
+
155
+
result, err := db.Exec(`
156
+
INSERT INTO triggers (kind, push_ref, push_new_sha, push_old_sha)
157
+
VALUES (?, ?, ?, ?)
158
+
`, "push", "refs/heads/"+branch, newSha, oldSha)
159
+
160
+
if err != nil {
161
+
return 0, err
162
+
}
163
+
164
+
return result.LastInsertId()
165
+
}
166
+
167
+
func createPRTrigger(db *sql.DB, branches []string) (int64, error) {
168
+
targetBranch := branches[0] // Usually main
169
+
sourceBranch := branches[rand.Intn(len(branches)-1)+1]
170
+
sourceSha := generateRandomSha()
171
+
actions := []string{"opened", "synchronize", "reopened"}
172
+
action := actions[rand.Intn(len(actions))]
173
+
174
+
result, err := db.Exec(`
175
+
INSERT INTO triggers (kind, pr_source_branch, pr_target_branch, pr_source_sha, pr_action)
176
+
VALUES (?, ?, ?, ?, ?)
177
+
`, "pull_request", sourceBranch, targetBranch, sourceSha, action)
178
+
179
+
if err != nil {
180
+
return 0, err
181
+
}
182
+
183
+
return result.LastInsertId()
184
+
}
185
+
186
+
func createWorkflowStatuses(db *sql.DB, knot, pipelineRkey, workflow string, startTime time.Time) error {
187
+
// Generate a progression of statuses for the workflow
188
+
statusProgression := []StatusKind{StatusKindPending, StatusKindRunning}
189
+
190
+
// Randomly choose a final status (80% success, 10% failed, 5% timeout, 5% cancelled)
191
+
roll := rand.Float32()
192
+
var finalStatus StatusKind
193
+
switch {
194
+
case roll < 0.80:
195
+
finalStatus = StatusKindSuccess
196
+
case roll < 0.90:
197
+
finalStatus = StatusKindFailed
198
+
case roll < 0.95:
199
+
finalStatus = StatusKindTimeout
200
+
default:
201
+
finalStatus = StatusKindCancelled
202
+
}
203
+
204
+
statusProgression = append(statusProgression, finalStatus)
205
+
206
+
currentTime := startTime
207
+
for i, status := range statusProgression {
208
+
rkey := fmt.Sprintf("%s-%s-%d", pipelineRkey, workflow, i)
209
+
210
+
// Add some realistic time progression (10-60 seconds between statuses)
211
+
if i > 0 {
212
+
currentTime = currentTime.Add(time.Duration(rand.Intn(50)+10) * time.Second)
213
+
}
214
+
215
+
var errorMsg *string
216
+
var exitCode int
217
+
218
+
if status == StatusKindFailed {
219
+
msg := "Command exited with non-zero status"
220
+
errorMsg = &msg
221
+
exitCode = rand.Intn(100) + 1
222
+
} else if status == StatusKindTimeout {
223
+
msg := "Workflow exceeded maximum execution time"
224
+
errorMsg = &msg
225
+
exitCode = 124
226
+
}
227
+
228
+
_, err := db.Exec(`
229
+
INSERT INTO pipeline_statuses (
230
+
spindle, rkey, pipeline_knot, pipeline_rkey,
231
+
created, workflow, status, error, exit_code
232
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
233
+
`, "spindle.example.com", rkey, knot, pipelineRkey,
234
+
currentTime.Format(time.RFC3339), workflow, string(status), errorMsg, exitCode)
235
+
236
+
if err != nil {
237
+
return err
238
+
}
239
+
}
240
+
241
+
return nil
242
+
}
History
1 round
0 comments
anirudh.fi
submitted
#0
1 commit
expand
collapse
cmd/populatepipelines: script to generate dummy pipeline runs
Signed-off-by: Anirudh Oppiliappan <anirudh@tangled.org>
2/3 timeout, 1/3 success
expand
collapse
expand 0 comments
pull request successfully merged