From 3da9ed6579a48a876cc640979391b291f32506b6 Mon Sep 17 00:00:00 2001 From: Ian Stanton Date: Tue, 12 Dec 2023 16:02:37 -0500 Subject: [PATCH] Mount certs from secret in app services (#417) --- tembo-operator/Cargo.lock | 2 +- tembo-operator/Cargo.toml | 2 +- tembo-operator/src/app_service/manager.rs | 39 ++++++++++- tembo-operator/tests/integration_tests.rs | 79 +++++++++++++++++++++++ tembo-operator/yaml/sample-document.yaml | 10 +-- 5 files changed, 119 insertions(+), 13 deletions(-) diff --git a/tembo-operator/Cargo.lock b/tembo-operator/Cargo.lock index 026d879f3..eb9bef72e 100644 --- a/tembo-operator/Cargo.lock +++ b/tembo-operator/Cargo.lock @@ -494,7 +494,7 @@ dependencies = [ [[package]] name = "controller" -version = "0.25.1" +version = "0.25.2" dependencies = [ "actix-web", "anyhow", diff --git a/tembo-operator/Cargo.toml b/tembo-operator/Cargo.toml index 283d4c7ba..1ce18865e 100644 --- a/tembo-operator/Cargo.toml +++ b/tembo-operator/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "controller" description = "Tembo Operator for Postgres" -version = "0.25.1" +version = "0.25.2" edition = "2021" default-run = "controller" license = "Apache-2.0" diff --git a/tembo-operator/src/app_service/manager.rs b/tembo-operator/src/app_service/manager.rs index 737fdcd01..c68da747d 100644 --- a/tembo-operator/src/app_service/manager.rs +++ b/tembo-operator/src/app_service/manager.rs @@ -4,7 +4,8 @@ use k8s_openapi::{ apps::v1::{Deployment, DeploymentSpec}, core::v1::{ Capabilities, Container, ContainerPort, EnvVar, EnvVarSource, HTTPGetAction, PodSpec, - PodTemplateSpec, Probe, SecretKeySelector, SecurityContext, Service, ServicePort, ServiceSpec, + PodTemplateSpec, Probe, SecretKeySelector, SecretVolumeSource, SecurityContext, Service, + ServicePort, ServiceSpec, Volume, VolumeMount, }, }, apimachinery::pkg::{ @@ -355,6 +356,38 @@ fn generate_deployment( // combine the secret env vars and those provided in spec by user env_vars.extend(secret_envs); + // Create volume vec and add certs volume from secret + let mut volumes: Vec = Vec::new(); + let certs_volume = Volume { + name: "tembo-certs".to_string(), + secret: Some(SecretVolumeSource { + secret_name: Some(format!("{}-server1", coredb_name)), + ..SecretVolumeSource::default() + }), + ..Volume::default() + }; + volumes.push(certs_volume); + + // Create volume mounts vec and add certs volume mount + let mut volume_mounts: Vec = Vec::new(); + let certs_volume_mount = VolumeMount { + name: "tembo-certs".to_string(), + mount_path: "/tembo/certs".to_string(), + read_only: Some(true), + ..VolumeMount::default() + }; + volume_mounts.push(certs_volume_mount); + + // Add any user provided volumes / volume mounts + if let Some(storage) = appsvc.storage.clone() { + if let Some(vols) = storage.volumes { + volumes.extend(vols); + } + if let Some(vols) = storage.volume_mounts { + volume_mounts.extend(vols); + } + } + let pod_spec = PodSpec { containers: vec![Container { args: appsvc.args.clone(), @@ -367,10 +400,10 @@ fn generate_deployment( readiness_probe, liveness_probe, security_context: Some(security_context), - volume_mounts: appsvc.storage.clone().and_then(|s| s.volume_mounts), + volume_mounts: Some(volume_mounts), ..Container::default() }], - volumes: appsvc.storage.clone().and_then(|s| s.volumes), + volumes: Some(volumes), ..PodSpec::default() }; diff --git a/tembo-operator/tests/integration_tests.rs b/tembo-operator/tests/integration_tests.rs index 8396edeaa..c8994cb63 100644 --- a/tembo-operator/tests/integration_tests.rs +++ b/tembo-operator/tests/integration_tests.rs @@ -3018,6 +3018,33 @@ mod test { "value": "/certs/tls.crt" }, ], + "storage": { + "volumes": [ + { + "name": "ferretdb-data", + "ephemeral": { + "volumeClaimTemplate": { + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "1Gi" + } + } + } + } + } + } + ], + "volumeMounts": [ + { + "name": "ferretdb-data", + "mountPath": "/state" + } + ] + }, } ], "postgresExporterEnabled": false @@ -3047,6 +3074,38 @@ mod test { assert_eq!(app_1.metadata.name.unwrap(), format!("{cdb_name}-postgrest")); assert_eq!(app_2.metadata.name.unwrap(), format!("{cdb_name}-test-app-1")); + let selector_map = app_0 + .spec + .as_ref() + .and_then(|s| s.selector.match_labels.as_ref()) + .expect("Deployment should have a selector"); + let selector = selector_map + .iter() + .map(|(k, v)| format!("{}={}", k, v)) + .collect::>() + .join(","); + let lp = ListParams::default().labels(&selector); + let pods: Api = Api::namespaced(client.clone(), &namespace); + let pod_list = pods.list(&lp).await.unwrap(); + assert_eq!(pod_list.items.len(), 1); + let app_0_pod = pod_list.items[0].clone(); + let app_0_container = app_0_pod.spec.unwrap().containers[0].clone(); + + // Assert app_0 volume mounts include ferretdb-data and tembo-certs + let volume_mounts = app_0_container.volume_mounts.unwrap(); + let mut found_ferretdb_data = false; + let mut found_tembo_certs = false; + for mount in volume_mounts { + if mount.mount_path == "/state" { + found_ferretdb_data = true; + } + if mount.mount_path == "/tembo/certs" { + found_tembo_certs = true; + } + } + assert!(found_ferretdb_data); + assert!(found_tembo_certs); + // Assert resources in first appService // select the pod let selector_map = app_1 @@ -3081,6 +3140,16 @@ mod test { let app_1_resources = app_1_container.resources.unwrap(); assert_eq!(app_1_resources, expected); + // Assert /tembo/certs is included in volume mounts + let volume_mounts = app_1_container.volume_mounts.unwrap(); + let mut found = false; + for mount in volume_mounts { + if mount.mount_path == "/tembo/certs" { + found = true; + } + } + assert!(found); + let ingresses: Result, errors::OperatorError> = list_resources(client.clone(), cdb_name, &namespace, 1).await; let ingress = ingresses.unwrap(); @@ -3157,6 +3226,16 @@ mod test { let app_2_resources = app_2_container.resources.unwrap(); assert_eq!(app_2_resources, expected); + // Assert /tembo/certs is included in volume mounts + let volume_mounts = app_2_container.volume_mounts.unwrap(); + let mut found = false; + for mount in volume_mounts { + if mount.mount_path == "/tembo/certs" { + found = true; + } + } + assert!(found); + // Delete the one without a service, but leave the postgrest appService let coredb_json = serde_json::json!({ "apiVersion": API_VERSION, diff --git a/tembo-operator/yaml/sample-document.yaml b/tembo-operator/yaml/sample-document.yaml index 8e079e44a..75ebd17ad 100644 --- a/tembo-operator/yaml/sample-document.yaml +++ b/tembo-operator/yaml/sample-document.yaml @@ -24,9 +24,9 @@ spec: - name: FERRETDB_STATE_DIR value: '-' - name: FERRETDB_LISTEN_TLS_CERT_FILE - value: /certs/tls.crt + value: /tembo/certs/tls.crt - name: FERRETDB_LISTEN_TLS_KEY_FILE - value: /certs/tls.key + value: /tembo/certs/tls.key - name: FERRETDB_LISTEN_TLS value: :27018 storage: @@ -40,12 +40,6 @@ spec: resources: requests: storage: 1Gi - - name: ferretdb-certs - secret: - secretName: sample-document-server1 volumeMounts: - name: ferretdb-data mountPath: /state - - name: ferretdb-certs - mountPath: /certs - readOnly: true