Skip to main content

cufflink_types/
runner.rs

1//! The runner API: what a tenant's runner reads of the one tenant its
2//! runner key is bound to. The extension runner reads the native services
3//! it runs, their binaries and their environments; a tenant-scoped web
4//! runner the web apps it serves, their bundles and their environments.
5
6use serde::{Deserialize, Serialize};
7use std::collections::BTreeMap;
8use uuid::Uuid;
9
10/// Whether `hash` is a SHA-256 as the runner API addresses an artifact by:
11/// 64 lowercase hex digits.
12pub fn is_sha256(hash: &str) -> bool {
13    hash.len() == 64
14        && hash
15            .bytes()
16            .all(|b| b.is_ascii_digit() || (b'a'..=b'f').contains(&b))
17}
18
19/// What a tenant's extension runner runs; a service's binary and
20/// environment are under it.
21pub const RUNNER_NATIVE_PATH: &str = "/api/runner/native";
22
23/// What a tenant-scoped web runner serves; an app's bundle and environment
24/// are under it.
25pub const RUNNER_WEB_PATH: &str = "/api/runner/web";
26
27/// The native services the extension runner of `tenant`, the tenant its
28/// key is bound to, runs: each active one at its current version.
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct NativeSet {
31    pub tenant: String,
32    pub services: Vec<NativeService>,
33}
34
35/// A native service's current deployment, as its runner runs it.
36#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37pub struct NativeService {
38    pub service_id: Uuid,
39    pub service_name: String,
40    pub version: i32,
41    /// The SHA-256 of its binary, hex; `None` when the deployment records
42    /// no binary.
43    pub sha256: Option<String>,
44    /// The size of its binary in bytes.
45    pub size: Option<u64>,
46    /// The extensions its manifest provides.
47    pub extensions: Vec<String>,
48}
49
50/// The web apps the web runner of `tenant`, the tenant its key is bound to,
51/// serves: each one's current version with a bundle.
52#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
53pub struct WebSet {
54    pub tenant: String,
55    pub apps: Vec<WebApp>,
56}
57
58/// A web app's current deployment, as its runner serves it.
59#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
60pub struct WebApp {
61    pub service_id: Uuid,
62    pub service_name: String,
63    pub version: i32,
64    /// The SHA-256 of its bundle, hex; `None` for a bundle uploaded before
65    /// bundles were hashed, which the runner API cannot serve.
66    pub sha256: Option<String>,
67    /// The size of its bundle in bytes.
68    pub size: Option<u64>,
69}
70
71/// A service's configs and secrets, opened: the variables its runner
72/// starts it with.
73#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct Environment {
75    pub environment: BTreeMap<String, String>,
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81    use crate::host::tests::round_trip;
82    use serde_json::json;
83
84    #[test]
85    fn an_artifact_is_addressed_by_its_lowercase_sha256() {
86        let hash = "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08";
87        assert!(is_sha256(hash));
88        for other in [
89            &hash[1..],
90            &format!("{hash}0"),
91            &hash.to_uppercase(),
92            &format!("../{}", &hash[3..]),
93            "environment",
94        ] {
95            assert!(!is_sha256(other), "{other}");
96        }
97    }
98
99    #[test]
100    fn what_a_runner_is_answered_round_trips() {
101        round_trip(
102            &NativeSet {
103                tenant: "acme".into(),
104                services: vec![NativeService {
105                    service_id: Uuid::nil(),
106                    service_name: "ext-pdf".into(),
107                    version: 4,
108                    sha256: Some("ab".into()),
109                    size: Some(9),
110                    extensions: vec!["pdf.render".into()],
111                }],
112            },
113            json!({
114                "tenant": "acme",
115                "services": [{
116                    "service_id": "00000000-0000-0000-0000-000000000000",
117                    "service_name": "ext-pdf",
118                    "version": 4,
119                    "sha256": "ab",
120                    "size": 9,
121                    "extensions": ["pdf.render"],
122                }],
123            }),
124        );
125        round_trip(
126            &WebSet {
127                tenant: "acme".into(),
128                apps: vec![WebApp {
129                    service_id: Uuid::nil(),
130                    service_name: "portal".into(),
131                    version: 3,
132                    sha256: None,
133                    size: None,
134                }],
135            },
136            json!({
137                "tenant": "acme",
138                "apps": [{
139                    "service_id": "00000000-0000-0000-0000-000000000000",
140                    "service_name": "portal",
141                    "version": 3,
142                    "sha256": null,
143                    "size": null,
144                }],
145            }),
146        );
147        round_trip(
148            &Environment {
149                environment: [("GOOGLE_KEY".to_string(), "k".to_string())].into(),
150            },
151            json!({"environment": {"GOOGLE_KEY": "k"}}),
152        );
153    }
154}