diff --git a/Cargo.lock b/Cargo.lock index a3857de..c78340c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -29,6 +29,8 @@ name = "infra-rs" version = "0.1.0" dependencies = [ "k8s-openapi", + "serde", + "serde_json", ] [[package]] @@ -124,9 +126,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.118" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", diff --git a/Cargo.toml b/Cargo.toml index 9dca517..d7cafca 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,3 +5,5 @@ edition = "2021" [dependencies] k8s-openapi = { version = "0.22.0", features = ["v1_30"] } +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.120" diff --git a/src/infra_rs/ingress.rs b/src/infra_rs/ingress.rs new file mode 100644 index 0000000..b2c826a --- /dev/null +++ b/src/infra_rs/ingress.rs @@ -0,0 +1,69 @@ +use k8s_openapi::{ + api::{ + core::v1::Service, + networking::v1::{ + self as api, HTTPIngressPath, HTTPIngressRuleValue, IngressBackend, IngressRule, + IngressServiceBackend, IngressSpec, IngressTLS, ServiceBackendPort, + }, + }, + apimachinery::pkg::apis::meta::v1::ObjectMeta, +}; + +#[derive(Default)] +pub struct Ingress { + pub name: String, + pub hostname: String, + pub tls_secret: Option, +} + +pub fn new(ingress: Ingress, service: &Service) -> api::Ingress { + api::Ingress { + metadata: ObjectMeta { + name: Some(ingress.name.clone()), + ..Default::default() + }, + spec: Some(IngressSpec { + rules: Some(vec![IngressRule { + host: Some(ingress.hostname.clone()), + http: Some(HTTPIngressRuleValue { + paths: vec![HTTPIngressPath { + path: Some(String::from("/")), + path_type: String::from("Prefix"), + backend: IngressBackend { + service: Some(IngressServiceBackend { + name: service.metadata.clone().name.unwrap(), + port: Some(ServiceBackendPort { + number: Some( + service + .spec + .clone() + .unwrap() + .ports + .unwrap() + .first() + .unwrap() + .port, + ), + ..Default::default() + }), + ..Default::default() + }), + ..Default::default() + }, + ..Default::default() + }], + }), + }]), + tls: if let Some(tls_secret) = ingress.tls_secret { + Some(vec![IngressTLS { + hosts: Some(vec![ingress.hostname]), + secret_name: Some(tls_secret), + }]) + } else { + None + }, + ..Default::default() + }), + ..Default::default() + } +} diff --git a/src/infra_rs/mod.rs b/src/infra_rs/mod.rs index 9862902..414fac8 100644 --- a/src/infra_rs/mod.rs +++ b/src/infra_rs/mod.rs @@ -1 +1,22 @@ +use serde::{self, Deserialize, Serialize}; +use serde_json::Value; + pub mod deployment; +pub mod ingress; +pub mod service; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(untagged)] +pub enum Resource { + Deployment(k8s_openapi::api::apps::v1::Deployment), + Ingress(k8s_openapi::api::networking::v1::Ingress), + Service(k8s_openapi::api::core::v1::Service), +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ResourceList { + #[serde(rename = "apiVersion")] + pub api_version: String, + pub kind: String, + pub items: Vec, +} diff --git a/src/infra_rs/service.rs b/src/infra_rs/service.rs new file mode 100644 index 0000000..59ef052 --- /dev/null +++ b/src/infra_rs/service.rs @@ -0,0 +1,75 @@ +use std::collections::BTreeMap; + +use k8s_openapi::{ + api::{ + apps::v1::Deployment, + core::v1::{self as api, ServicePort, ServiceSpec}, + }, + apimachinery::pkg::apis::meta::v1::ObjectMeta, +}; + +#[derive(Default)] +pub struct Service { + pub name: String, + pub port: i32, +} + +pub fn new(service: Service) -> api::Service { + api::Service { + metadata: ObjectMeta { + name: Some(service.name.clone()), + namespace: Some(service.name.clone()), + ..Default::default() + }, + spec: Some(ServiceSpec { + ports: Some(vec![ServicePort { + name: Some(format!("{}-{}", service.name, service.port)), + port: service.port, + ..Default::default() + }]), + ..Default::default() + }), + ..Default::default() + } +} + +pub fn from(deployment: &Deployment) -> api::Service { + let d = deployment.clone(); + let containers = d.spec.unwrap().template.clone().spec.unwrap().containers; + let ports: Vec = containers + .into_iter() + .map(|c| { + c.ports.unwrap().into_iter().map(|p| ServicePort { + port: p.container_port, + name: Some(format!( + "{}-{}", + d.metadata.name.clone().unwrap(), + p.container_port + )), + ..Default::default() + }) + }) + .flatten() + .collect(); + + api::Service { + metadata: ObjectMeta { + name: Some(d.metadata.name.clone().unwrap()), + namespace: Some(d.metadata.name.clone().unwrap()), + labels: Some(BTreeMap::from([( + String::from("app"), + d.metadata.name.clone().unwrap(), + )])), + ..Default::default() + }, + spec: Some(ServiceSpec { + ports: Some(ports), + selector: Some(BTreeMap::from([( + String::from("app"), + d.metadata.name.clone().unwrap(), + )])), + ..Default::default() + }), + ..Default::default() + } +} diff --git a/src/main.rs b/src/main.rs index fa585f7..96341f7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,10 @@ -use k8s_openapi::serde_json; +use serde_json; mod infra_rs; mod manifests; fn main() { - let test = manifests::vaultwarden::render(); + let test = manifests::render(); println!("{}", serde_json::to_string(&test).unwrap()); } diff --git a/src/manifests/mod.rs b/src/manifests/mod.rs index c3069ac..c0e3ac0 100644 --- a/src/manifests/mod.rs +++ b/src/manifests/mod.rs @@ -1 +1,13 @@ -pub mod vaultwarden; +use crate::infra_rs::ResourceList; + +mod vaultwarden; + +pub fn render() -> ResourceList { + let resources = vec![vaultwarden::render()].into_iter().flatten().collect(); + + ResourceList { + items: resources, + api_version: String::from("v1"), + kind: String::from("List"), + } +} diff --git a/src/manifests/vaultwarden.rs b/src/manifests/vaultwarden.rs index 162a0a8..adfa108 100644 --- a/src/manifests/vaultwarden.rs +++ b/src/manifests/vaultwarden.rs @@ -1,9 +1,14 @@ -use k8s_openapi::api::apps::v1 as api; +use serde_json::Value; -use crate::infra_rs::deployment::{self, Deployment, DeploymentEnv}; +use crate::infra_rs::{ + deployment::{self, Deployment, DeploymentEnv}, + ingress::{self, Ingress}, + service::{self, Service}, + Resource, +}; -pub fn render() -> api::Deployment { - deployment::new(Deployment { +pub fn render() -> Vec { + let deployment = deployment::new(Deployment { name: String::from("vaultwarden"), image: String::from("vaultwarden/server:testing"), ports: vec![80], @@ -18,5 +23,27 @@ pub fn render() -> api::Deployment { ..Default::default() }, ], - }) + }); + + let service = service::from(&deployment); + + let ingress = ingress::new( + Ingress { + name: String::from("vaultwarden"), + hostname: String::from("pw.gmem.ca"), + tls_secret: Some(String::from("gmem-ca-wildcard")), + }, + &service, + ); + + let resources = vec![ + Resource::Deployment(deployment), + Resource::Service(service), + Resource::Ingress(ingress), + ]; + + resources + .into_iter() + .map(|res| serde_json::to_value(res).unwrap()) + .collect() }