use anyhow::{bail, Context};
use serde::{Deserialize, Serialize};
use super::{AppV1, AppV1Spec, EntityDescriptorConst, WorkloadV1};
#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug, schemars::JsonSchema)]
pub struct DeploymentV1 {
pub name: String,
pub workload: WorkloadV1,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DeploymentOrApp {
Deployment(DeploymentV1),
App(AppV1),
}
impl DeploymentOrApp {
pub fn to_yaml(&self) -> Result<String, serde_yaml::Error> {
match self {
DeploymentOrApp::Deployment(depl) => serde_yaml::to_string(depl),
DeploymentOrApp::App(app) => app.to_yaml(),
}
}
}
pub fn parse_yaml_deployment_or_app(yaml: &str) -> Result<DeploymentOrApp, anyhow::Error> {
let config_value = serde_yaml::from_str::<serde_yaml::Value>(&yaml)
.with_context(|| format!("Could not parse deployment or app - invalid YAML"))?;
let kind = config_value.get("kind").and_then(|v| v.as_str());
match kind {
Some(AppV1Spec::KIND) => {
let app = serde_yaml::from_value::<AppV1>(config_value)
.context("could not parse schema with kind wasmer.io/App.v1")?;
Ok(DeploymentOrApp::App(app))
}
Some(other) => {
bail!("unsupported app config kind '{other}'");
}
None => {
let depl = serde_yaml::from_value::<DeploymentV1>(config_value)
.context("could not parse config - yaml could neither be parsed as an wasmer.io/App.v1 or a DeploymentV1")?;
Ok(DeploymentOrApp::Deployment(depl))
}
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use super::*;
use crate::schema::{
CapabilityCpuV1, CapabilityFileSystemV1, CapabilityMapV1, CapabilityMemorySwapV1,
CapabilityNetworkV1, MemorySwappinessV1, NetworkEgressV1, NetworkIngressV1, RunnerWCgiV1,
WebcPackageIdentifierV1, WebcSourceV1, WorkloadRunnerV1, WorkloadRunnerWasmSourceV1,
WorkloadRunnerWebcCommandV1,
};
fn build_test_deployment_1() -> DeploymentV1 {
DeploymentV1 {
name: "my-deployment".to_string(),
workload: WorkloadV1 {
name: Some("my-workload".to_string()),
runner: WorkloadRunnerV1::WebcCommand(WorkloadRunnerWebcCommandV1 {
package: WebcPackageIdentifierV1 {
repository: Some("https://wapm.dev".parse().unwrap()),
namespace: "ns".into(),
name: "pkg".into(),
tag: Some("0.1.2".into()),
},
command: "some-command".to_string(),
}),
capabilities: CapabilityMapV1 {
cpu: Some(CapabilityCpuV1 {
maximum_threads: Some(100),
maximum_usage: Some(2000),
}),
fs: Some(CapabilityFileSystemV1 { volumes: vec![] }),
memory_swap: Some(CapabilityMemorySwapV1 {
swappiness: Some(MemorySwappinessV1::Never),
memory_id: None,
maximum_size: Some("1GB".parse().unwrap()),
}),
memory_persistent: None,
network: Some(CapabilityNetworkV1 {
enabled: Some(true),
network_id: None,
ingress: Some(NetworkIngressV1 {
enabled: Some(true),
maximum_bandwidth_per_second: Some("512KB".parse().unwrap()),
}),
egress: Some(NetworkEgressV1 {
enabled: Some(false),
maximum_bandwidth_per_second: Some("5MB".parse().unwrap()),
}),
}),
network_dns: Some(crate::schema::CapabilityNetworkDnsV1 {
enabled: Some(true),
servers: Some(vec!["1.1.1.1".to_string()]),
allowed_hosts: Some(crate::schema::NetworkDnsAllowedHostsV1 {
allow_all_hosts: Some(false),
hosts: vec!["hello.com".to_string()],
regex_patterns: vec![".*\\.wasmer\\.io".to_string()],
wildcard_patterns: vec!["*.wasmer.io".to_string()],
}),
}),
network_gateway: Some(crate::schema::CapabilityNetworkGatewayV1 {
enforce_https: Some(true),
domains: Some(vec!["my-site.com".to_string()]),
}),
logging: None,
other: HashMap::new(),
},
},
}
}
#[test]
fn test_deserialize_deployment_v1_json() {
let definition = r#"
{
"name": "my-deployment",
"workload": {
"name": "my-workload",
"runner": {
"webc_command": {
"package": {
"repository": "https://wapm.dev/",
"namespace": "ns",
"name": "pkg",
"tag": "0.1.2"
},
"command": "some-command"
}
},
"capabilities": {
"cpu": {
"maximum_threads": 100,
"maximum_usage": 2000
},
"fs": {
"volumes": []
},
"memory_swap": {
"swappiness": "never",
"maximum_size": "1000.0 MB"
},
"network": {
"enabled": true,
"network_id": null,
"ingress": {
"enabled": true,
"maximum_bandwidth_per_second": "512.0 KB"
},
"egress": {
"enabled": false,
"maximum_bandwidth_per_second": "5.0 MB"
}
},
"network_dns": {
"enabled": true,
"servers": [
"1.1.1.1"
],
"allowed_hosts": {
"allow_all_hosts": false,
"hosts": [
"hello.com"
],
"regex_patterns": [
".*\\.wasmer\\.io"
],
"wildcard_patterns": [
"*.wasmer.io"
]
}
},
"network_gateway": {
"enforce_https": true,
"domains": [
"my-site.com"
]
}
}
}
}
"#;
let expected = build_test_deployment_1();
let actual = super::super::deserialize_json::<DeploymentV1>(definition.as_bytes()).unwrap();
pretty_assertions::assert_eq!(actual, expected);
let expected = DeploymentV1 {
name: "my-deployment".to_string(),
workload: WorkloadV1 {
name: Some("my-workload".to_string()),
runner: WorkloadRunnerV1::WCgi(RunnerWCgiV1 {
source: WorkloadRunnerWasmSourceV1::Webc(WebcSourceV1 {
package: WebcPackageIdentifierV1 {
repository: Some("https://wapm.dev".parse().unwrap()),
namespace: "theduke".to_string(),
name: "wcgi_http_handler".to_string(),
tag: Some("0.1.0".to_string()),
},
auth_token: None,
}),
dialect: None,
}),
capabilities: Default::default(),
},
};
eprintln!("{}", serde_json::to_string_pretty(&expected).unwrap());
let def2 = r#"
{
"name": "my-deployment",
"workload": {
"name": "my-workload",
"runner": {
"wcgi": {
"source": {
"webc": {
"repository": "https://wapm.dev/",
"namespace": "theduke",
"name": "wcgi_http_handler",
"version": "0.1.0"
}
}
}
}
}
}
"#;
let _ = super::super::deserialize_json::<DeploymentV1>(def2.as_bytes()).unwrap();
}
#[test]
fn test_deserialize_deployment_v1_yaml() {
let definition = r#"---
name: my-deployment
workload:
name: my-workload
runner:
webc_command:
package:
repository: "https://wapm.dev/"
namespace: ns
name: pkg
tag: 0.1.2
command: some-command
capabilities:
cpu:
maximum_threads: 100
maximum_usage: 2000
fs:
volumes: []
memory_swap:
swappiness: never
maximum_size: 1000.0 MB
network:
enabled: true
ingress:
enabled: true
maximum_bandwidth_per_second: 512.0 KB
egress:
enabled: false
maximum_bandwidth_per_second: 5.0 MB
network_dns:
enabled: true
servers:
- 1.1.1.1
allowed_hosts:
allow_all_hosts: false
hosts:
- hello.com
regex_patterns:
- ".*\\.wasmer\\.io"
wildcard_patterns:
- "*.wasmer.io"
network_gateway:
enforce_https: true
domains:
- my-site.com
"#;
let expected = build_test_deployment_1();
let actual = serde_yaml::from_str::<DeploymentV1>(definition).unwrap();
pretty_assertions::assert_eq!(actual, expected);
let ser = serde_yaml::to_string(&expected).unwrap();
eprintln!("\n\n{}\n\n", ser);
pretty_assertions::assert_eq!(ser.trim(), definition.trim());
}
}