Monorepo for Tangled
at c4a4e93dc382b144bdc89a4ed0429a4677cb5535 242 lines 6.5 kB view raw
1package main 2 3import ( 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 15var ( 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 23type StatusKind string 24 25const ( 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 34var finishStatuses = []StatusKind{ 35 StatusKindFailed, 36 StatusKindTimeout, 37 StatusKindCancelled, 38 StatusKindSuccess, 39} 40 41// generateRandomSha generates a random 40-character SHA 42func 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 52func generateRkey() string { 53 // Simple timestamp-based rkey 54 now := time.Now().UnixMicro() 55 return fmt.Sprintf("%d", now) 56} 57 58func 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 136func 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 150func 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 167func 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 186func 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}