Skip to main content

rebuilderd_common/api/v1/
mod.rs

1mod models;
2
3use crate::api::{Client, ZstdRequestBuilder};
4use crate::errors::*;
5use async_trait::async_trait;
6pub use models::*;
7use std::borrow::Cow;
8
9#[cfg(feature = "diesel")]
10use diesel::{
11    deserialize::FromSql,
12    serialize::{IsNull, Output, ToSql},
13    sql_types::Integer,
14    sqlite::{Sqlite, SqliteValue},
15    {AsExpression, FromSqlRow},
16};
17use serde::{Deserialize, Serialize};
18
19/// Represents the priority of an enqueued rebuild job. The job queue is sorted based on priority and
20/// time, so the lower this number is, the more prioritized the job is. It's a little backwards, but
21/// hey.
22///
23/// There are some utility functions on the type for accessing default values for well-defined use
24/// cases. These map to constants in the same namespace as this type, and you can use either one.
25/// ```
26/// use rebuilderd_common::api::v1::Priority;
27///
28/// assert_eq!(Priority::from(1), Priority::default());
29/// assert_eq!(Priority::from(2), Priority::retry());
30/// assert_eq!(Priority::from(0), Priority::manual());
31/// ```
32///
33/// You can also set a completely custom priority. This is mostly useful for external API calls that
34/// orchestrate rebuilds.
35/// ```
36/// use rebuilderd_common::api::v1::Priority;
37///
38/// let custom = Priority::from(10);
39/// assert_eq!(custom, Priority::from(10));
40///
41/// ```
42#[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Copy, Clone)]
43#[cfg_attr(feature = "diesel", derive(FromSqlRow, AsExpression))]
44#[cfg_attr(feature = "diesel", diesel(sql_type = Integer))]
45#[cfg_attr(feature = "diesel", diesel(check_for_backend(diesel::sqlite::Sqlite)))]
46pub struct Priority(i32);
47
48impl Priority {
49    /// The default priority for enqueued rebuilds. The job queue is sorted based on priority and time,
50    /// so the lower this number is, the more prioritized the job is. It's a little backwards, but hey.
51    const DEFAULT_QUEUE_PRIORITY: i32 = 1;
52
53    /// The default priority used for automatically requeued jobs. This priority is lower than the one
54    /// for untested packages.
55    const DEFAULT_RETRY_PRIORITY: i32 = Self::DEFAULT_QUEUE_PRIORITY + 1;
56
57    /// The default priority used for manually retried jobs. This priority is higher than the one for
58    /// untested packages.
59    const DEFAULT_MANUAL_PRIORITY: i32 = Self::DEFAULT_QUEUE_PRIORITY - 1;
60
61    pub fn retry() -> Self {
62        Priority(Self::DEFAULT_RETRY_PRIORITY)
63    }
64
65    pub fn manual() -> Self {
66        Priority(Self::DEFAULT_MANUAL_PRIORITY)
67    }
68}
69
70impl Default for Priority {
71    fn default() -> Self {
72        Priority(Self::DEFAULT_QUEUE_PRIORITY)
73    }
74}
75
76#[cfg(feature = "diesel")]
77impl FromSql<Integer, Sqlite> for Priority {
78    fn from_sql(bytes: SqliteValue) -> diesel::deserialize::Result<Self> {
79        let value = <i32 as FromSql<Integer, Sqlite>>::from_sql(bytes)?;
80        Ok(Priority(value))
81    }
82}
83
84#[cfg(feature = "diesel")]
85impl ToSql<Integer, Sqlite> for Priority {
86    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, Sqlite>) -> diesel::serialize::Result {
87        out.set_value(self.0);
88        Ok(IsNull::No)
89    }
90}
91
92impl From<i32> for Priority {
93    fn from(value: i32) -> Self {
94        Priority(value)
95    }
96}
97
98#[async_trait]
99pub trait BuildRestApi {
100    async fn get_builds(
101        &self,
102        page: Option<&Page>,
103        origin_filter: Option<&OriginFilter>,
104        source_identity_filter: Option<&SourceIdentityFilter>,
105    ) -> Result<ResultPage<Rebuild>>;
106
107    async fn submit_build_report(&self, request: RebuildReport) -> Result<()>;
108    async fn get_build(&self, id: i32) -> Result<Rebuild>;
109    async fn get_build_log(&self, id: i32) -> Result<String>;
110    async fn get_build_artifacts(&self, id: i32) -> Result<Vec<RebuildArtifact>>;
111    async fn get_build_artifact(&self, id: i32, artifact_id: i32) -> Result<RebuildArtifact>;
112    async fn get_build_artifact_diffoscope(&self, id: i32, artifact_id: i32) -> Result<String>;
113    async fn get_build_artifact_attestation(&self, id: i32, artifact_id: i32) -> Result<Vec<u8>>;
114}
115
116#[async_trait]
117pub trait DashboardRestApi {
118    async fn get_dashboard(&self, origin_filter: Option<&OriginFilter>) -> Result<DashboardState>;
119}
120
121#[async_trait]
122pub trait MetaRestApi {
123    async fn get_distributions(&self) -> Result<Vec<String>>;
124    async fn get_distribution_releases(&self, distribution: &str) -> Result<Vec<String>>;
125    async fn get_distribution_architectures(&self, distribution: &str) -> Result<Vec<String>>;
126    async fn get_distribution_release_architectures(
127        &self,
128        distribution: &str,
129        release: &str,
130    ) -> Result<Vec<String>>;
131
132    async fn get_public_keys(&self) -> Result<PublicKey>;
133}
134
135#[async_trait]
136pub trait PackageRestApi {
137    async fn submit_package_report(&self, report: &PackageReport) -> Result<()>;
138
139    async fn get_source_packages(
140        &self,
141        page: Option<&Page>,
142        origin_filter: Option<&OriginFilter>,
143        source_identity_filter: Option<&SourceIdentityFilter>,
144    ) -> Result<ResultPage<SourcePackage>>;
145
146    async fn get_source_package(&self, id: i32) -> Result<SourcePackage>;
147
148    async fn get_binary_packages(
149        &self,
150        page: Option<&Page>,
151        origin_filter: Option<&OriginFilter>,
152        binary_identity_filter: Option<&BinaryIdentityFilter>,
153    ) -> Result<ResultPage<BinaryPackage>>;
154
155    async fn get_binary_package(&self, id: i32) -> Result<BinaryPackage>;
156}
157
158#[async_trait]
159pub trait QueueRestApi {
160    async fn get_queued_jobs(
161        &self,
162        page: Option<&Page>,
163        origin_filter: Option<&OriginFilter>,
164        source_identity_filter: Option<&SourceIdentityFilter>,
165    ) -> Result<ResultPage<QueuedJob>>;
166
167    async fn request_rebuild(&self, request: QueueJobRequest) -> Result<()>;
168    async fn get_queued_job(&self, id: i32) -> Result<QueuedJob>;
169    async fn drop_queued_job(&self, id: i32) -> Result<()>;
170    async fn drop_queued_jobs(
171        &self,
172        origin_filter: Option<&OriginFilter>,
173        source_identity_filter: Option<&SourceIdentityFilter>,
174    ) -> Result<()>;
175    async fn request_work(&self, request: PopQueuedJobRequest) -> Result<JobAssignment>;
176    async fn ping_job(&self, id: i32) -> Result<()>;
177}
178
179#[async_trait]
180pub trait WorkerRestApi {
181    async fn get_workers(&self, page: Option<&Page>) -> Result<ResultPage<Worker>>;
182    async fn register_worker(&self, request: RegisterWorkerRequest) -> Result<()>;
183    async fn get_worker(&self, id: i32) -> Result<Worker>;
184    async fn unregister_worker(&self, id: i32) -> Result<()>;
185}
186
187#[async_trait]
188impl BuildRestApi for Client {
189    async fn get_builds(
190        &self,
191        page: Option<&Page>,
192        origin_filter: Option<&OriginFilter>,
193        source_identity_filter: Option<&SourceIdentityFilter>,
194    ) -> Result<ResultPage<Rebuild>> {
195        let records = self
196            .get(Cow::Borrowed("api/v1/builds"))
197            .query(&page)
198            .query(&origin_filter)
199            .query(&source_identity_filter)
200            .send()
201            .await?
202            .error_for_status()?
203            .json()
204            .await?;
205
206        Ok(records)
207    }
208
209    async fn submit_build_report(&self, request: RebuildReport) -> Result<()> {
210        self.post(Cow::Borrowed("api/v1/builds"))
211            .json(&request)
212            .send_encoded()
213            .await?
214            .error_for_status()?;
215
216        Ok(())
217    }
218
219    async fn get_build(&self, id: i32) -> Result<Rebuild> {
220        let record = self
221            .get(Cow::Owned(format!("api/v1/builds/{id}")))
222            .send()
223            .await?
224            .error_for_status()?
225            .json()
226            .await?;
227
228        Ok(record)
229    }
230
231    async fn get_build_log(&self, id: i32) -> Result<String> {
232        let data = self
233            .get(Cow::Owned(format!("api/v1/builds/{id}/log")))
234            .send()
235            .await?
236            .error_for_status()?
237            .text()
238            .await?;
239
240        Ok(data)
241    }
242
243    async fn get_build_artifacts(&self, id: i32) -> Result<Vec<RebuildArtifact>> {
244        let records = self
245            .get(Cow::Owned(format!("api/v1/builds/{id}/artifacts")))
246            .send()
247            .await?
248            .error_for_status()?
249            .json()
250            .await?;
251
252        Ok(records)
253    }
254
255    async fn get_build_artifact(&self, id: i32, artifact_id: i32) -> Result<RebuildArtifact> {
256        let record = self
257            .get(Cow::Owned(format!(
258                "api/v1/builds/{id}/artifacts/{artifact_id}"
259            )))
260            .send()
261            .await?
262            .error_for_status()?
263            .json()
264            .await?;
265
266        Ok(record)
267    }
268
269    async fn get_build_artifact_diffoscope(&self, id: i32, artifact_id: i32) -> Result<String> {
270        let data = self
271            .get(Cow::Owned(format!(
272                "api/v1/builds/{id}/artifacts/{artifact_id}/diffoscope"
273            )))
274            .send()
275            .await?
276            .error_for_status()?
277            .text()
278            .await?;
279
280        Ok(data)
281    }
282
283    async fn get_build_artifact_attestation(&self, id: i32, artifact_id: i32) -> Result<Vec<u8>> {
284        let data = self
285            .get(Cow::Owned(format!(
286                "api/v1/builds/{id}/artifacts/{artifact_id}/attestation"
287            )))
288            .send()
289            .await?
290            .error_for_status()?
291            .bytes()
292            .await?;
293
294        Ok(Vec::from(data))
295    }
296}
297
298#[async_trait]
299impl DashboardRestApi for Client {
300    async fn get_dashboard(&self, origin_filter: Option<&OriginFilter>) -> Result<DashboardState> {
301        let dashboard = self
302            .get(Cow::Borrowed("api/v1/dashboard"))
303            .query(&origin_filter)
304            .send()
305            .await?
306            .error_for_status()?
307            .json()
308            .await?;
309
310        Ok(dashboard)
311    }
312}
313
314#[async_trait]
315impl MetaRestApi for Client {
316    async fn get_distributions(&self) -> Result<Vec<String>> {
317        let results = self
318            .get(Cow::Borrowed("api/v1/meta/distributions"))
319            .send()
320            .await?
321            .error_for_status()?
322            .json()
323            .await?;
324
325        Ok(results)
326    }
327
328    async fn get_distribution_releases(&self, distribution: &str) -> Result<Vec<String>> {
329        let results = self
330            .get(Cow::Owned(format!(
331                "api/v1/meta/distributions/{distribution}/releases"
332            )))
333            .send()
334            .await?
335            .error_for_status()?
336            .json()
337            .await?;
338
339        Ok(results)
340    }
341
342    async fn get_distribution_architectures(&self, distribution: &str) -> Result<Vec<String>> {
343        let results = self
344            .get(Cow::Owned(format!(
345                "api/v1/meta/distributions/{distribution}/architectures"
346            )))
347            .send()
348            .await?
349            .error_for_status()?
350            .json()
351            .await?;
352
353        Ok(results)
354    }
355
356    async fn get_distribution_release_architectures(
357        &self,
358        distribution: &str,
359        release: &str,
360    ) -> Result<Vec<String>> {
361        let results = self
362            .get(Cow::Owned(format!(
363                "api/v1/meta/distributions/{distribution}/releases/{release}/architectures"
364            )))
365            .send()
366            .await?
367            .error_for_status()?
368            .json()
369            .await?;
370
371        Ok(results)
372    }
373
374    async fn get_public_keys(&self) -> Result<PublicKey> {
375        let public_key = self
376            .get(Cow::Borrowed("api/v1/meta/public-keys"))
377            .send()
378            .await?
379            .error_for_status()?
380            .json()
381            .await?;
382
383        Ok(public_key)
384    }
385}
386
387#[async_trait]
388impl PackageRestApi for Client {
389    async fn submit_package_report(&self, report: &PackageReport) -> Result<()> {
390        self.post(Cow::Borrowed("api/v1/packages"))
391            .json(report)
392            .send_encoded()
393            .await?
394            .error_for_status()?;
395
396        Ok(())
397    }
398
399    async fn get_source_packages(
400        &self,
401        page: Option<&Page>,
402        origin_filter: Option<&OriginFilter>,
403        source_identity_filter: Option<&SourceIdentityFilter>,
404    ) -> Result<ResultPage<SourcePackage>> {
405        let records = self
406            .get(Cow::Borrowed("api/v1/packages/source"))
407            .query(&page)
408            .query(&origin_filter)
409            .query(&source_identity_filter)
410            .send()
411            .await?
412            .error_for_status()?
413            .json()
414            .await?;
415
416        Ok(records)
417    }
418
419    async fn get_source_package(&self, id: i32) -> Result<SourcePackage> {
420        let record = self
421            .get(Cow::Owned(format!("api/v1/packages/source/{id}")))
422            .send()
423            .await?
424            .error_for_status()?
425            .json()
426            .await?;
427
428        Ok(record)
429    }
430
431    async fn get_binary_packages(
432        &self,
433        page: Option<&Page>,
434        origin_filter: Option<&OriginFilter>,
435        binary_identity_filter: Option<&BinaryIdentityFilter>,
436    ) -> Result<ResultPage<BinaryPackage>> {
437        let records = self
438            .get(Cow::Borrowed("api/v1/packages/binary"))
439            .query(&page)
440            .query(&origin_filter)
441            .query(&binary_identity_filter)
442            .send()
443            .await?
444            .error_for_status()?
445            .json()
446            .await?;
447
448        Ok(records)
449    }
450
451    async fn get_binary_package(&self, id: i32) -> Result<BinaryPackage> {
452        let record = self
453            .get(Cow::Owned(format!("api/v1/packages/binary/{id}")))
454            .send()
455            .await?
456            .error_for_status()?
457            .json()
458            .await?;
459
460        Ok(record)
461    }
462}
463
464#[async_trait]
465impl QueueRestApi for Client {
466    async fn get_queued_jobs(
467        &self,
468        page: Option<&Page>,
469        origin_filter: Option<&OriginFilter>,
470        source_identity_filter: Option<&SourceIdentityFilter>,
471    ) -> Result<ResultPage<QueuedJob>> {
472        let records = self
473            .get(Cow::Borrowed("api/v1/queue"))
474            .query(&page)
475            .query(&origin_filter)
476            .query(&source_identity_filter)
477            .send()
478            .await?
479            .error_for_status()?
480            .json()
481            .await?;
482
483        Ok(records)
484    }
485
486    async fn request_rebuild(&self, request: QueueJobRequest) -> Result<()> {
487        self.post(Cow::Borrowed("api/v1/queue"))
488            .json(&request)
489            .send_encoded()
490            .await?
491            .error_for_status()?;
492
493        Ok(())
494    }
495
496    async fn get_queued_job(&self, id: i32) -> Result<QueuedJob> {
497        let record = self
498            .get(Cow::Owned(format!("api/v1/queue/{id}")))
499            .send()
500            .await?
501            .error_for_status()?
502            .json()
503            .await?;
504
505        Ok(record)
506    }
507
508    async fn drop_queued_job(&self, id: i32) -> Result<()> {
509        self.delete(Cow::Owned(format!("api/v1/queue/{id}")))
510            .send()
511            .await?
512            .error_for_status()?;
513
514        Ok(())
515    }
516
517    async fn drop_queued_jobs(
518        &self,
519        origin_filter: Option<&OriginFilter>,
520        source_identity_filter: Option<&SourceIdentityFilter>,
521    ) -> Result<()> {
522        self.delete(Cow::Borrowed("api/v1/queue"))
523            .query(&origin_filter)
524            .query(&source_identity_filter)
525            .send()
526            .await?
527            .error_for_status()?;
528
529        Ok(())
530    }
531
532    async fn request_work(&self, request: PopQueuedJobRequest) -> Result<JobAssignment> {
533        let record = self
534            .post(Cow::Borrowed("api/v1/queue/pop"))
535            .json(&request)
536            .send_encoded()
537            .await?
538            .error_for_status()?
539            .json()
540            .await?;
541
542        Ok(record)
543    }
544
545    async fn ping_job(&self, id: i32) -> Result<()> {
546        // nginx dies if proxying a request without a Content-Length header
547        self.post(Cow::Owned(format!("api/v1/queue/{id}/ping")))
548            .header("Content-Length", 0)
549            .send()
550            .await?
551            .error_for_status()?;
552
553        Ok(())
554    }
555}
556
557#[async_trait]
558impl WorkerRestApi for Client {
559    async fn get_workers(&self, page: Option<&Page>) -> Result<ResultPage<Worker>> {
560        let workers = self
561            .get(Cow::Borrowed("api/v1/workers"))
562            .query(&page)
563            .send()
564            .await?
565            .error_for_status()?
566            .json()
567            .await?;
568
569        Ok(workers)
570    }
571
572    async fn register_worker(&self, request: RegisterWorkerRequest) -> Result<()> {
573        self.post(Cow::Borrowed("api/v1/workers"))
574            .json(&request)
575            .send_encoded()
576            .await?
577            .error_for_status()?;
578
579        Ok(())
580    }
581
582    async fn get_worker(&self, id: i32) -> Result<Worker> {
583        let worker = self
584            .get(Cow::Owned(format!("api/v1/workers/{id}")))
585            .send()
586            .await?
587            .error_for_status()?
588            .json()
589            .await?;
590
591        Ok(worker)
592    }
593
594    async fn unregister_worker(&self, id: i32) -> Result<()> {
595        self.delete(Cow::Owned(format!("api/v1/workers/{id}")))
596            .send()
597            .await?
598            .error_for_status()?;
599
600        Ok(())
601    }
602}