wasmer_deploy_schema/schema/
app.rs1use std::collections::HashMap;
2
3use anyhow::Context;
4use schemars::JsonSchema;
5use serde::{Deserialize, Serialize};
6
7use super::{entity::EntityDescriptorConst, AnyEntity, Entity, WorkloadV2};
8
9pub const APP_ID_PREFIX: &str = "da_";
10
11#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
13pub struct AppMeta {
14 pub app_id: String,
15 pub app_version_id: String,
16}
17
18impl AppMeta {
19 pub const ANNOTATION_BACKEND_APP_ID: &str = "wasmer.io/app_id";
21
22 pub const ANNOTATION_BACKEND_APP_VERSION_ID: &str = "wasmer.io/app_version_id";
24
25 pub fn try_from_annotations(
27 map: &HashMap<String, serde_json::Value>,
28 ) -> Result<Self, anyhow::Error> {
29 let app_id = map
30 .get(Self::ANNOTATION_BACKEND_APP_ID)
31 .context("missing annotation for app id")?
32 .as_str()
33 .context("app id annotation is not a string")?
34 .to_string();
35
36 let app_version_id = map
37 .get(Self::ANNOTATION_BACKEND_APP_VERSION_ID)
38 .context("missing annotation for version id")?
39 .as_str()
40 .context("version id annotation is not a string")?
41 .to_string();
42
43 Ok(Self {
44 app_id,
45 app_version_id,
46 })
47 }
48
49 pub fn try_from_entity<T>(entity: &Entity<T>) -> Result<Self, anyhow::Error> {
50 Self::try_from_annotations(&entity.meta.annotations)
51 }
52
53 pub fn new(app_id: String, app_version_id: String) -> Self {
54 Self {
55 app_id,
56 app_version_id,
57 }
58 }
59
60 pub fn to_annotations_map(self) -> HashMap<String, serde_json::Value> {
61 let mut map = HashMap::new();
62 map.insert(
63 Self::ANNOTATION_BACKEND_APP_ID.to_string(),
64 serde_json::Value::String(self.app_id),
65 );
66 map.insert(
67 Self::ANNOTATION_BACKEND_APP_VERSION_ID.to_string(),
68 serde_json::Value::String(self.app_version_id),
69 );
70 map
71 }
72}
73
74#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
78pub struct AppV1Spec {
79 #[serde(default)]
82 #[serde(skip_serializing_if = "Vec::is_empty")]
83 pub aliases: Vec<String>,
84
85 pub workload: WorkloadV2,
87}
88
89#[derive(Serialize, Deserialize, JsonSchema, PartialEq, Eq, Clone, Debug)]
90pub struct AppStateV1 {}
91
92impl EntityDescriptorConst for AppV1Spec {
93 const NAMESPACE: &'static str = "wasmer.io";
94 const NAME: &'static str = "App";
95 const VERSION: &'static str = "1-alpha1";
96 const KIND: &'static str = "wasmer.io/App.v1";
97
98 type Spec = AppV1Spec;
99 type State = AppStateV1;
100}
101
102pub type AppV1 = super::Entity<AppV1Spec, AnyEntity>;
103
104#[cfg(test)]
105mod tests {
106 use pretty_assertions::assert_eq;
107
108 use crate::schema::{EntityMeta, EnvVarV1};
109
110 use super::*;
111
112 #[test]
114 fn test_deser_app_v1_sparse() {
115 let inp = r#"
116kind: wasmer.io/App.v1
117meta:
118 name: my-app
119spec:
120 workload:
121 source: theduke/amaze
122"#;
123
124 let a1 = serde_yaml::from_str::<AppV1>(inp).unwrap();
125
126 assert_eq!(
127 a1,
128 AppV1 {
129 meta: EntityMeta::new("my-app"),
130 spec: AppV1Spec {
131 aliases: Vec::new(),
132 workload: crate::schema::WorkloadV2 {
133 source: "theduke/amaze".parse().unwrap(),
134 capabilities: Default::default(),
135 },
136 },
137 children: None,
138 },
139 );
140 }
141
142 #[test]
143 fn test_deser_app_v1_full() {
144 let inp = r#"
145kind: wasmer.io/App.v1
146meta:
147 name: my-app
148 description: hello
149 labels:
150 "my/label": "value"
151 annotations:
152 "my/annotation": {nested: [1, 2, 3]}
153spec:
154 aliases:
155 - a
156 - b
157 workload:
158 source: "theduke/my-app"
159"#;
160
161 let a1 = serde_yaml::from_str::<AppV1>(inp).unwrap();
162
163 let expected = AppV1 {
164 meta: EntityMeta {
165 name: "my-app".to_string(),
166 description: Some("hello".to_string()),
167 labels: vec![("my/label".to_string(), "value".to_string())]
168 .into_iter()
169 .collect(),
170 annotations: vec![(
171 "my/annotation".to_string(),
172 serde_json::json!({
173 "nested": [1, 2, 3],
174 }),
175 )]
176 .into_iter()
177 .collect(),
178 parent: None,
179 },
180 spec: AppV1Spec {
181 aliases: vec!["a".to_string(), "b".to_string()],
182 workload: WorkloadV2 {
183 source: "theduke/my-app".parse().unwrap(),
184 capabilities: Default::default(),
185 },
186 },
187 children: None,
188 };
189
190 assert_eq!(a1, expected,);
191 }
192
193 #[test]
194 fn test_deser_app_v1_with_cli_and_env_cap() {
195 let raw = r#"
196kind: wasmer.io/App.v1
197meta:
198 description: ''
199 name: christoph/pyenv-dump
200spec:
201 workload:
202 capabilities:
203 wasi:
204 cli_args:
205 - /src/main.py
206 env_vars:
207 - name: PORT
208 value: '80'
209 source: wasmer-tests/python-env-dump@0.3.6
210
211"#;
212
213 let a1 = serde_yaml::from_str::<AppV1>(raw).unwrap();
214
215 let expected = AppV1 {
216 meta: EntityMeta {
217 name: "christoph/pyenv-dump".to_string(),
218 description: Some("".to_string()),
219 labels: Default::default(),
220 annotations: Default::default(),
221 parent: None,
222 },
223 spec: AppV1Spec {
224 aliases: Default::default(),
225 workload: WorkloadV2 {
226 source: "wasmer-tests/python-env-dump@0.3.6".parse().unwrap(),
227 capabilities: crate::schema::CapabilityMapV1 {
228 wasi: Some(crate::schema::CapabilityWasiV1 {
229 cli_args: Some(vec!["/src/main.py".to_string()]),
230 env_vars: Some(vec![EnvVarV1 {
231 name: "PORT".to_string(),
232 source: crate::schema::EnvVarSourceV1::Value("80".to_string()),
233 }]),
234 }),
235 ..Default::default()
236 },
237 },
238 },
239 children: None,
240 };
241
242 assert_eq!(a1, expected,);
243 }
244}