b6b7e4cf Andy McCall 1 month ago ↶
1
2
3
4
use super :: { now_ms, Pool} ;
5
use sqlx:: FromRow;
6
7
# [ derive ( Clone, Debug, FromRow ) ]
8
pub struct Runner {
9
pub id : i64 ,
10
pub name : String,
11
pub token_hash : String,
12
pub source : String,
13
pub key : String,
14
pub key_name : String,
15
pub arch : String,
16
pub os : String,
17
pub version : String,
18
pub labels : String,
19
pub max_jobs : i64 ,
20
pub memory_mib : Option< i64 > ,
21
pub cpus : Option< i64 > ,
22
pub state : String,
23
pub created_ms : i64 ,
24
pub created_by : String,
25
pub last_seen_ms : Option< i64 > ,
26
}
27
28
29
pub const ONLINE_MS : i64 = 90_000 ;
30
31
impl Runner {
32
pub fn online ( & self ) -> bool {
33
self .last_seen_ms.is_some_and ( | t | now_ms ( ) - t < ONLINE_MS )
34
}
35
36
37
pub fn status ( & self ) -> & 'static str {
38
match self .state.as_str ( ) {
39
" disabled" => " disabled" ,
40
" draining" => " draining" ,
41
_ if self .online ( ) => " online" ,
42
_ => " offline" ,
43
}
44
}
45
46
pub fn label_list ( & self ) -> Vec< & str > {
47
self .labels.split ( ' ,' ) .filter ( | l | ! l.is_empty ( ) ) .collect ( )
48
}
49
}
50
51
pub async fn by_token ( pool : & Pool, token_hash : & str ) -> sqlx:: Result< Option< Runner> > {
52
sqlx:: query_as( " SELECT * FROM flow_runners WHERE token_hash = ?" ) .bind ( token_hash) .fetch_optional ( pool) .await
53
}
54
55
pub async fn get ( pool : & Pool, id : i64 ) -> sqlx:: Result< Option< Runner> > {
56
sqlx:: query_as( " SELECT * FROM flow_runners WHERE id = ?" ) .bind ( id) .fetch_optional ( pool) .await
57
}
58
59
pub async fn list ( pool : & Pool) -> sqlx:: Result< Vec< Runner> > {
60
sqlx:: query_as( " SELECT * FROM flow_runners ORDER BY name, id" ) .fetch_all ( pool) .await
61
}
62
63
64
pub async fn configured ( pool : & Pool, token_hash : & str , name : & str ) -> sqlx:: Result< Runner> {
65
sqlx:: query( " INSERT OR IGNORE INTO flow_runners (name, token_hash, source, created_ms) VALUES (?, ?, 'config', ?)" )
66
.bind ( name)
67
.bind ( token_hash)
68
.bind ( now_ms ( ) )
69
.execute ( pool)
70
.await? ;
71
Ok ( by_token ( pool, token_hash) .await? .expect ( " just made" ) )
72
}
73
74
75
76
pub async fn seen ( pool : & Pool, id : i64 , name : Option< & str > , info : Option< & flow_core:: api:: RunnerInfo> ) -> sqlx:: Result< ( ) > {
77
let now = now_ms ( ) ;
78
match info {
79
Some ( i) => {
80
sqlx:: query(
81
" UPDATE flow_runners SET arch = ?, os = ?, version = ?, labels = ?, max_jobs = ?, memory_mib = ?, cpus = ?, last_seen_ms = ? WHERE id = ?" ,
82
)
83
.bind ( & i.arch)
84
.bind ( & i.os)
85
.bind ( & i.version)
86
.bind ( i.labels.join ( " ," ) )
87
.bind ( i.max_jobs as i64 )
88
.bind ( i.memory_mib.map ( i64 :: from) )
89
.bind ( i.cpus.map ( i64 :: from) )
90
.bind ( now)
91
.bind ( id)
92
.execute ( pool)
93
.await? ;
94
}
95
None => {
96
sqlx:: query( " UPDATE flow_runners SET last_seen_ms = ? WHERE id = ?" ) .bind ( now) .bind ( id) .execute ( pool) .await? ;
97
}
98
}
99
if let Some ( n) = name {
100
sqlx:: query( " UPDATE flow_runners SET name = ? WHERE id = ? AND source = 'config'" ) .bind ( n) .bind ( id) .execute ( pool) .await? ;
101
}
102
Ok ( ( ) )
103
}
104
105
pub async fn set_state ( pool : & Pool, id : i64 , state : & str ) -> sqlx:: Result< ( ) > {
106
sqlx:: query( " UPDATE flow_runners SET state = ? WHERE id = ?" ) .bind ( state) .bind ( id) .execute ( pool) .await? ;
107
Ok ( ( ) )
108
}
109
110
111
pub async fn remove ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( ) > {
112
sqlx:: query( " DELETE FROM flow_runners WHERE id = ?" ) .bind ( id) .execute ( pool) .await? ;
113
Ok ( ( ) )
114
}
115
116
117
118
pub async fn bind_key ( pool : & Pool, id : i64 , key : & str , name : & str ) -> sqlx:: Result< String> {
119
sqlx:: query( " UPDATE flow_runners SET key = ?, key_name = ? WHERE id = ? AND key = ''" )
120
.bind ( key)
121
.bind ( name)
122
.bind ( id)
123
.execute ( pool)
124
.await? ;
125
sqlx:: query_scalar( " SELECT key FROM flow_runners WHERE id = ?" ) .bind ( id) .fetch_one ( pool) .await
126
}
127
128
129
pub async fn keys ( pool : & Pool) -> sqlx:: Result< Vec< String> > {
130
sqlx:: query_scalar( " SELECT key FROM flow_runners WHERE key != ''" ) .fetch_all ( pool) .await
131
}
132
133
134
pub async fn add_registration ( pool : & Pool, token_hash : & str , by : & str ) -> sqlx:: Result< ( ) > {
135
let now = now_ms ( ) ;
136
sqlx:: query( " INSERT INTO flow_runner_registrations (token_hash, created_by, created_ms, expires_ms) VALUES (?, ?, ?, ?)" )
137
.bind ( token_hash)
138
.bind ( by)
139
.bind ( now)
140
.bind ( now + 24 * 3_600_000 )
141
.execute ( pool)
142
.await? ;
143
Ok ( ( ) )
144
}
145
146
147
pub async fn pending_registrations ( pool : & Pool) -> sqlx:: Result< Vec< ( String, i64 , i64 ) > > {
148
sqlx:: query_as( " SELECT created_by, created_ms, expires_ms FROM flow_runner_registrations WHERE used_ms IS NULL AND expires_ms > ? ORDER BY created_ms DESC" )
149
.bind ( now_ms ( ) )
150
.fetch_all ( pool)
151
.await
152
}
153
154
155
156
# [ allow ( clippy::too_many_arguments ) ]
157
pub async fn register (
158
pool : & Pool,
159
registration : & str ,
160
name : & str ,
161
token_hash : & str ,
162
key : & str ,
163
key_name : & str ,
164
by_default : & str ,
165
) -> sqlx:: Result< Option< Runner> > {
166
let mut tx = super :: begin_write( pool) .await? ;
167
let now = now_ms ( ) ;
168
let who: Option< String> =
169
sqlx:: query_scalar( " SELECT created_by FROM flow_runner_registrations WHERE token_hash = ? AND used_ms IS NULL AND expires_ms > ?" )
170
.bind ( registration)
171
.bind ( now)
172
.fetch_optional ( & mut * tx)
173
.await? ;
174
let Some ( who) = who else { return Ok ( None ) } ;
175
let id: i64 = sqlx:: query_scalar(
176
" INSERT INTO flow_runners (name, token_hash, source, key, key_name, created_ms, created_by) VALUES (?, ?, 'registered', ?, ?, ?, ?) RETURNING id" ,
177
)
178
.bind ( name)
179
.bind ( token_hash)
180
.bind ( key)
181
.bind ( key_name)
182
.bind ( now)
183
.bind ( if who.is_empty ( ) { by_default } else { & who } )
184
.fetch_one ( & mut * tx)
185
.await? ;
186
sqlx:: query( " UPDATE flow_runner_registrations SET used_ms = ?, runner_id = ? WHERE token_hash = ?" )
187
.bind ( now)
188
.bind ( id)
189
.bind ( registration)
190
.execute ( & mut * tx)
191
.await? ;
192
tx.commit ( ) .await? ;
193
get ( pool, id) .await
194
}
195
196
197
# [ derive ( Clone, Debug, FromRow ) ]
198
pub struct RunnerJob {
199
pub owner : String,
200
pub repo : String,
201
pub run_number : i64 ,
202
pub job : String,
203
pub status : String,
204
pub started_ms : Option< i64 > ,
205
pub finished_ms : Option< i64 > ,
206
pub receipt_attest : String,
207
}
208
209
210
pub async fn history ( pool : & Pool, id : i64 , limit : i64 , before : Option< i64 > ) -> sqlx:: Result< Vec< RunnerJob> > {
211
sqlx:: query_as(
212
" SELECT p.owner AS owner, p.name AS repo, r.number AS run_number, j.name AS job, j.status AS status, j.started_ms AS started_ms, \
213
j.finished_ms AS finished_ms, j.receipt_attest AS receipt_attest \
214
FROM flow_jobs j JOIN flow_runs r ON r.id = j.run_id JOIN repositories p ON p.id = r.repo_id \
215
WHERE j.runner_id = ? AND (? IS NULL OR j.started_ms < ?) ORDER BY j.started_ms DESC LIMIT ?" ,
216
)
217
.bind ( id)
218
.bind ( before)
219
.bind ( before)
220
.bind ( limit)
221
.fetch_all ( pool)
222
.await
223
}
224
225
226
pub async fn totals ( pool : & Pool, id : i64 ) -> sqlx:: Result< ( i64 , i64 , i64 ) > {
227
sqlx:: query_as(
228
" SELECT COUNT(*), COALESCE(SUM(status = 'passed'), 0), COALESCE(SUM(finished_ms - started_ms), 0) \
229
FROM flow_jobs WHERE runner_id = ? AND finished_ms IS NOT NULL" ,
230
)
231
.bind ( id)
232
.fetch_one ( pool)
233
.await
234
}
235
236
237
pub async fn running ( pool : & Pool) -> sqlx:: Result< Vec< ( i64 , i64 ) > > {
238
sqlx:: query_as( " SELECT runner_id, COUNT(*) FROM flow_jobs WHERE status = 'running' AND runner_id IS NOT NULL GROUP BY runner_id" )
239
.fetch_all ( pool)
240
.await
241
}
242
243
pub async fn set_job_runner ( pool : & Pool, job_id : i64 , runner_id : i64 ) -> sqlx:: Result< ( ) > {
244
sqlx:: query( " UPDATE flow_jobs SET runner_id = ? WHERE id = ?" ) .bind ( runner_id) .bind ( job_id) .execute ( pool) .await? ;
245
Ok ( ( ) )
246
}