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_components(&self, distribution: &str) -> Result<Vec<String>>;
127    async fn get_distribution_release_architectures(
128        &self,
129        distribution: &str,
130        release: &str,
131    ) -> Result<Vec<String>>;
132
133    async fn get_distribution_release_components(
134        &self,
135        distribution: &str,
136        release: &str,
137    ) -> Result<Vec<String>>;
138
139    async fn get_distribution_release_component_architectures(
140        &self,
141        distribution: &str,
142        release: &str,
143        component: &str,
144    ) -> Result<Vec<String>>;
145
146    async fn get_public_keys(&self) -> Result<PublicKey>;
147}
148
149#[async_trait]
150pub trait PackageRestApi {
151    async fn submit_package_report(&self, report: &PackageReport) -> Result<()>;
152
153    async fn get_source_packages(
154        &self,
155        page: Option<&Page>,
156        origin_filter: Option<&OriginFilter>,
157        source_identity_filter: Option<&SourceIdentityFilter>,
158    ) -> Result<ResultPage<SourcePackage>>;
159
160    async fn get_source_package(&self, id: i32) -> Result<SourcePackage>;
161
162    async fn get_binary_packages(
163        &self,
164        page: Option<&Page>,
165        origin_filter: Option<&OriginFilter>,
166        binary_identity_filter: Option<&BinaryIdentityFilter>,
167    ) -> Result<ResultPage<BinaryPackage>>;
168
169    async fn get_binary_package(&self, id: i32) -> Result<BinaryPackage>;
170}
171
172#[async_trait]
173pub trait QueueRestApi {
174    async fn get_queued_jobs(
175        &self,
176        page: Option<&Page>,
177        origin_filter: Option<&OriginFilter>,
178        source_identity_filter: Option<&SourceIdentityFilter>,
179    ) -> Result<ResultPage<QueuedJob>>;
180
181    async fn request_rebuild(&self, request: QueueJobRequest) -> Result<()>;
182    async fn get_queued_job(&self, id: i32) -> Result<QueuedJob>;
183    async fn drop_queued_job(&self, id: i32) -> Result<()>;
184    async fn drop_queued_jobs(
185        &self,
186        origin_filter: Option<&OriginFilter>,
187        source_identity_filter: Option<&SourceIdentityFilter>,
188    ) -> Result<()>;
189    async fn request_work(&self, request: PopQueuedJobRequest) -> Result<JobAssignment>;
190    async fn ping_job(&self, id: i32) -> Result<()>;
191}
192
193#[async_trait]
194pub trait WorkerRestApi {
195    async fn get_workers(&self, page: Option<&Page>) -> Result<ResultPage<Worker>>;
196    async fn register_worker(&self, request: RegisterWorkerRequest) -> Result<()>;
197    async fn get_worker(&self, id: i32) -> Result<Worker>;
198    async fn unregister_worker(&self, id: i32) -> Result<()>;
199}
200
201#[async_trait]
202impl BuildRestApi for Client {
203    async fn get_builds(
204        &self,
205        page: Option<&Page>,
206        origin_filter: Option<&OriginFilter>,
207        source_identity_filter: Option<&SourceIdentityFilter>,
208    ) -> Result<ResultPage<Rebuild>> {
209        let records = self
210            .get(Cow::Borrowed("api/v1/builds"))
211            .query(&page)
212            .query(&origin_filter)
213            .query(&source_identity_filter)
214            .send()
215            .await?
216            .error_for_status()?
217            .json()
218            .await?;
219
220        Ok(records)
221    }
222
223    async fn submit_build_report(&self, request: RebuildReport) -> Result<()> {
224        self.post(Cow::Borrowed("api/v1/builds"))
225            .json(&request)
226            .send_encoded()
227            .await?
228            .error_for_status()?;
229
230        Ok(())
231    }
232
233    async fn get_build(&self, id: i32) -> Result<Rebuild> {
234        let record = self
235            .get(Cow::Owned(format!("api/v1/builds/{id}")))
236            .send()
237            .await?
238            .error_for_status()?
239            .json()
240            .await?;
241
242        Ok(record)
243    }
244
245    async fn get_build_log(&self, id: i32) -> Result<String> {
246        let data = self
247            .get(Cow::Owned(format!("api/v1/builds/{id}/log")))
248            .send()
249            .await?
250            .error_for_status()?
251            .text()
252            .await?;
253
254        Ok(data)
255    }
256
257    async fn get_build_artifacts(&self, id: i32) -> Result<Vec<RebuildArtifact>> {
258        let records = self
259            .get(Cow::Owned(format!("api/v1/builds/{id}/artifacts")))
260            .send()
261            .await?
262            .error_for_status()?
263            .json()
264            .await?;
265
266        Ok(records)
267    }
268
269    async fn get_build_artifact(&self, id: i32, artifact_id: i32) -> Result<RebuildArtifact> {
270        let record = self
271            .get(Cow::Owned(format!(
272                "api/v1/builds/{id}/artifacts/{artifact_id}"
273            )))
274            .send()
275            .await?
276            .error_for_status()?
277            .json()
278            .await?;
279
280        Ok(record)
281    }
282
283    async fn get_build_artifact_diffoscope(&self, id: i32, artifact_id: i32) -> Result<String> {
284        let data = self
285            .get(Cow::Owned(format!(
286                "api/v1/builds/{id}/artifacts/{artifact_id}/diffoscope"
287            )))
288            .send()
289            .await?
290            .error_for_status()?
291            .text()
292            .await?;
293
294        Ok(data)
295    }
296
297    async fn get_build_artifact_attestation(&self, id: i32, artifact_id: i32) -> Result<Vec<u8>> {
298        let data = self
299            .get(Cow::Owned(format!(
300                "api/v1/builds/{id}/artifacts/{artifact_id}/attestation"
301            )))
302            .send()
303            .await?
304            .error_for_status()?
305            .bytes()
306            .await?;
307
308        Ok(Vec::from(data))
309    }
310}
311
312#[async_trait]
313impl DashboardRestApi for Client {
314    async fn get_dashboard(&self, origin_filter: Option<&OriginFilter>) -> Result<DashboardState> {
315        let dashboard = self
316            .get(Cow::Borrowed("api/v1/dashboard"))
317            .query(&origin_filter)
318            .send()
319            .await?
320            .error_for_status()?
321            .json()
322            .await?;
323
324        Ok(dashboard)
325    }
326}
327
328#[async_trait]
329impl MetaRestApi for Client {
330    async fn get_distributions(&self) -> Result<Vec<String>> {
331        let results = self
332            .get(Cow::Borrowed("api/v1/meta/distributions"))
333            .send()
334            .await?
335            .error_for_status()?
336            .json()
337            .await?;
338
339        Ok(results)
340    }
341
342    async fn get_distribution_releases(&self, distribution: &str) -> Result<Vec<String>> {
343        let results = self
344            .get(Cow::Owned(format!(
345                "api/v1/meta/distributions/{distribution}/releases"
346            )))
347            .send()
348            .await?
349            .error_for_status()?
350            .json()
351            .await?;
352
353        Ok(results)
354    }
355
356    async fn get_distribution_architectures(&self, distribution: &str) -> Result<Vec<String>> {
357        let results = self
358            .get(Cow::Owned(format!(
359                "api/v1/meta/distributions/{distribution}/architectures"
360            )))
361            .send()
362            .await?
363            .error_for_status()?
364            .json()
365            .await?;
366
367        Ok(results)
368    }
369
370    async fn get_distribution_components(&self, distribution: &str) -> Result<Vec<String>> {
371        let results = self
372            .get(Cow::Owned(format!(
373                "api/v1/meta/distributions/{distribution}/components"
374            )))
375            .send()
376            .await?
377            .error_for_status()?
378            .json()
379            .await?;
380
381        Ok(results)
382    }
383
384    async fn get_distribution_release_architectures(
385        &self,
386        distribution: &str,
387        release: &str,
388    ) -> Result<Vec<String>> {
389        let results = self
390            .get(Cow::Owned(format!(
391                "api/v1/meta/distributions/{distribution}/releases/{release}/architectures"
392            )))
393            .send()
394            .await?
395            .error_for_status()?
396            .json()
397            .await?;
398
399        Ok(results)
400    }
401
402    async fn get_distribution_release_components(
403        &self,
404        distribution: &str,
405        release: &str,
406    ) -> Result<Vec<String>> {
407        let results = self
408            .get(Cow::Owned(format!(
409                "api/v1/meta/distributions/{distribution}/releases/{release}/components"
410            )))
411            .send()
412            .await?
413            .error_for_status()?
414            .json()
415            .await?;
416
417        Ok(results)
418    }
419
420    async fn get_distribution_release_component_architectures(
421        &self,
422        distribution: &str,
423        release: &str,
424        component: &str,
425    ) -> Result<Vec<String>> {
426        let results = self
427            .get(Cow::Owned(format!(
428                "api/v1/meta/distributions/{distribution}/releases/{release}/components/{component}/architectures"
429            )))
430            .send()
431            .await?
432            .error_for_status()?
433            .json()
434            .await?;
435
436        Ok(results)
437    }
438
439    async fn get_public_keys(&self) -> Result<PublicKey> {
440        let public_key = self
441            .get(Cow::Borrowed("api/v1/meta/public-keys"))
442            .send()
443            .await?
444            .error_for_status()?
445            .json()
446            .await?;
447
448        Ok(public_key)
449    }
450}
451
452#[async_trait]
453impl PackageRestApi for Client {
454    async fn submit_package_report(&self, report: &PackageReport) -> Result<()> {
455        self.post(Cow::Borrowed("api/v1/packages"))
456            .json(report)
457            .send_encoded()
458            .await?
459            .error_for_status()?;
460
461        Ok(())
462    }
463
464    async fn get_source_packages(
465        &self,
466        page: Option<&Page>,
467        origin_filter: Option<&OriginFilter>,
468        source_identity_filter: Option<&SourceIdentityFilter>,
469    ) -> Result<ResultPage<SourcePackage>> {
470        let records = self
471            .get(Cow::Borrowed("api/v1/packages/source"))
472            .query(&page)
473            .query(&origin_filter)
474            .query(&source_identity_filter)
475            .send()
476            .await?
477            .error_for_status()?
478            .json()
479            .await?;
480
481        Ok(records)
482    }
483
484    async fn get_source_package(&self, id: i32) -> Result<SourcePackage> {
485        let record = self
486            .get(Cow::Owned(format!("api/v1/packages/source/{id}")))
487            .send()
488            .await?
489            .error_for_status()?
490            .json()
491            .await?;
492
493        Ok(record)
494    }
495
496    async fn get_binary_packages(
497        &self,
498        page: Option<&Page>,
499        origin_filter: Option<&OriginFilter>,
500        binary_identity_filter: Option<&BinaryIdentityFilter>,
501    ) -> Result<ResultPage<BinaryPackage>> {
502        let records = self
503            .get(Cow::Borrowed("api/v1/packages/binary"))
504            .query(&page)
505            .query(&origin_filter)
506            .query(&binary_identity_filter)
507            .send()
508            .await?
509            .error_for_status()?
510            .json()
511            .await?;
512
513        Ok(records)
514    }
515
516    async fn get_binary_package(&self, id: i32) -> Result<BinaryPackage> {
517        let record = self
518            .get(Cow::Owned(format!("api/v1/packages/binary/{id}")))
519            .send()
520            .await?
521            .error_for_status()?
522            .json()
523            .await?;
524
525        Ok(record)
526    }
527}
528
529#[async_trait]
530impl QueueRestApi for Client {
531    async fn get_queued_jobs(
532        &self,
533        page: Option<&Page>,
534        origin_filter: Option<&OriginFilter>,
535        source_identity_filter: Option<&SourceIdentityFilter>,
536    ) -> Result<ResultPage<QueuedJob>> {
537        let records = self
538            .get(Cow::Borrowed("api/v1/queue"))
539            .query(&page)
540            .query(&origin_filter)
541            .query(&source_identity_filter)
542            .send()
543            .await?
544            .error_for_status()?
545            .json()
546            .await?;
547
548        Ok(records)
549    }
550
551    async fn request_rebuild(&self, request: QueueJobRequest) -> Result<()> {
552        self.post(Cow::Borrowed("api/v1/queue"))
553            .json(&request)
554            .send_encoded()
555            .await?
556            .error_for_status()?;
557
558        Ok(())
559    }
560
561    async fn get_queued_job(&self, id: i32) -> Result<QueuedJob> {
562        let record = self
563            .get(Cow::Owned(format!("api/v1/queue/{id}")))
564            .send()
565            .await?
566            .error_for_status()?
567            .json()
568            .await?;
569
570        Ok(record)
571    }
572
573    async fn drop_queued_job(&self, id: i32) -> Result<()> {
574        self.delete(Cow::Owned(format!("api/v1/queue/{id}")))
575            .send()
576            .await?
577            .error_for_status()?;
578
579        Ok(())
580    }
581
582    async fn drop_queued_jobs(
583        &self,
584        origin_filter: Option<&OriginFilter>,
585        source_identity_filter: Option<&SourceIdentityFilter>,
586    ) -> Result<()> {
587        self.delete(Cow::Borrowed("api/v1/queue"))
588            .query(&origin_filter)
589            .query(&source_identity_filter)
590            .send()
591            .await?
592            .error_for_status()?;
593
594        Ok(())
595    }
596
597    async fn request_work(&self, request: PopQueuedJobRequest) -> Result<JobAssignment> {
598        let record = self
599            .post(Cow::Borrowed("api/v1/queue/pop"))
600            .json(&request)
601            .send_encoded()
602            .await?
603            .error_for_status()?
604            .json()
605            .await?;
606
607        Ok(record)
608    }
609
610    async fn ping_job(&self, id: i32) -> Result<()> {
611        // nginx dies if proxying a request without a Content-Length header
612        self.post(Cow::Owned(format!("api/v1/queue/{id}/ping")))
613            .header("Content-Length", 0)
614            .send()
615            .await?
616            .error_for_status()?;
617
618        Ok(())
619    }
620}
621
622#[async_trait]
623impl WorkerRestApi for Client {
624    async fn get_workers(&self, page: Option<&Page>) -> Result<ResultPage<Worker>> {
625        let workers = self
626            .get(Cow::Borrowed("api/v1/workers"))
627            .query(&page)
628            .send()
629            .await?
630            .error_for_status()?
631            .json()
632            .await?;
633
634        Ok(workers)
635    }
636
637    async fn register_worker(&self, request: RegisterWorkerRequest) -> Result<()> {
638        self.post(Cow::Borrowed("api/v1/workers"))
639            .json(&request)
640            .send_encoded()
641            .await?
642            .error_for_status()?;
643
644        Ok(())
645    }
646
647    async fn get_worker(&self, id: i32) -> Result<Worker> {
648        let worker = self
649            .get(Cow::Owned(format!("api/v1/workers/{id}")))
650            .send()
651            .await?
652            .error_for_status()?
653            .json()
654            .await?;
655
656        Ok(worker)
657    }
658
659    async fn unregister_worker(&self, id: i32) -> Result<()> {
660        self.delete(Cow::Owned(format!("api/v1/workers/{id}")))
661            .send()
662            .await?
663            .error_for_status()?;
664
665        Ok(())
666    }
667}