This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

core / spindle / db / mill_tokens.go
4.1 kB 167 lines
1package db 2 3import ( 4 "database/sql" 5 "encoding/json" 6 "slices" 7 "strings" 8 "time" 9) 10 11// the raw token is never stored, only its hash 12type ExecutorToken struct { 13 Name string 14 CreatedAt string 15 ExpiresAt *time.Time 16 Labels []string 17 QuarantineReason *string 18 QuarantinedAt *string 19} 20 21func (d *DB) AddExecutorToken(name, tokenHash string, expiresAt *time.Time, labels []string) error { 22 normalized := normalizeLabels(labels) 23 labelsBytes, err := json.Marshal(normalized) 24 if err != nil { 25 return err 26 } 27 _, err = d.Exec( 28 `insert into mill_executors (name, token_hash, expires_at, labels) values (?, ?, ?, ?)`, 29 name, tokenHash, expiryArg(expiresAt), string(labelsBytes), 30 ) 31 return err 32} 33 34func (d *DB) ResolveExecutorToken(tokenHash string) (string, []string, bool, error) { 35 var name string 36 var expires sql.NullString 37 var labelsRaw, quarantineReason sql.NullString 38 err := d.QueryRow( 39 `select name, expires_at, labels, quarantine_reason from mill_executors where token_hash = ?`, tokenHash, 40 ).Scan(&name, &expires, &labelsRaw, &quarantineReason) 41 if err == sql.ErrNoRows { 42 return "", nil, false, nil 43 } 44 if err != nil { 45 return "", nil, false, err 46 } 47 if quarantineReason.Valid { 48 return "", nil, false, nil 49 } 50 exp, hasExpiry, err := parseExpiry(expires) 51 if err != nil { 52 return "", nil, false, err 53 } 54 if hasExpiry && time.Now().After(exp) { 55 return "", nil, false, nil 56 } 57 var labels []string 58 if labelsRaw.Valid && labelsRaw.String != "" { 59 if err := json.Unmarshal([]byte(labelsRaw.String), &labels); err != nil { 60 return "", nil, false, err 61 } 62 } 63 return name, labels, true, nil 64} 65 66func (d *DB) QuarantineExecutor(name, reason string) error { 67 _, err := d.Exec( 68 `update mill_executors 69 set quarantine_reason = ?, quarantined_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now') 70 where name = ?`, 71 reason, name, 72 ) 73 return err 74} 75 76func (d *DB) ClearExecutorQuarantine(name string) (bool, error) { 77 res, err := d.Exec( 78 `update mill_executors set quarantine_reason = null, quarantined_at = null where name = ?`, 79 name, 80 ) 81 if err != nil { 82 return false, err 83 } 84 n, err := res.RowsAffected() 85 return n > 0, err 86} 87 88func (d *DB) RevokeExecutorToken(name string) (bool, error) { 89 res, err := d.Exec(`delete from mill_executors where name = ?`, name) 90 if err != nil { 91 return false, err 92 } 93 n, err := res.RowsAffected() 94 return n > 0, err 95} 96 97func (d *DB) ListExecutorTokens() ([]ExecutorToken, error) { 98 rows, err := d.Query(`select name, created_at, expires_at, labels, quarantine_reason, quarantined_at from mill_executors order by name`) 99 if err != nil { 100 return nil, err 101 } 102 defer rows.Close() 103 104 var out []ExecutorToken 105 for rows.Next() { 106 var t ExecutorToken 107 var expires, labelsRaw, quarantineReason, quarantinedAt sql.NullString 108 if err := rows.Scan(&t.Name, &t.CreatedAt, &expires, &labelsRaw, &quarantineReason, &quarantinedAt); err != nil { 109 return nil, err 110 } 111 exp, hasExpiry, err := parseExpiry(expires) 112 if err != nil { 113 return nil, err 114 } 115 if hasExpiry { 116 t.ExpiresAt = &exp 117 } 118 if labelsRaw.Valid && labelsRaw.String != "" { 119 if err := json.Unmarshal([]byte(labelsRaw.String), &t.Labels); err != nil { 120 return nil, err 121 } 122 } 123 if quarantineReason.Valid { 124 t.QuarantineReason = &quarantineReason.String 125 } 126 if quarantinedAt.Valid { 127 t.QuarantinedAt = &quarantinedAt.String 128 } 129 out = append(out, t) 130 } 131 return out, rows.Err() 132} 133 134func normalizeLabels(labels []string) []string { 135 var out []string 136 seen := make(map[string]bool) 137 for _, l := range labels { 138 trimmed := strings.TrimSpace(l) 139 if trimmed == "" { 140 continue 141 } 142 if !seen[trimmed] { 143 seen[trimmed] = true 144 out = append(out, trimmed) 145 } 146 } 147 slices.Sort(out) 148 return out 149} 150 151func expiryArg(t *time.Time) any { 152 if t == nil { 153 return nil 154 } 155 return t.UTC().Format(time.RFC3339) 156} 157 158func parseExpiry(s sql.NullString) (time.Time, bool, error) { 159 if !s.Valid { 160 return time.Time{}, false, nil 161 } 162 t, err := time.Parse(time.RFC3339, s.String) 163 if err != nil { 164 return time.Time{}, false, err 165 } 166 return t, true, nil 167}