use crate::{Context, CoreDB, Error, Result};
use k8s_openapi::{
api::{
apps::v1::{StatefulSet, StatefulSetSpec},
core::v1::{
Container, ContainerPort, EnvVar, ExecAction, PersistentVolumeClaim, PersistentVolumeClaimSpec,
PodSpec, PodTemplateSpec, Probe, ResourceRequirements, SecurityContext, VolumeMount,
},
},
apimachinery::pkg::{api::resource::Quantity, apis::meta::v1::LabelSelector},
};
use kube::{
api::{Api, ObjectMeta, Patch, PatchParams, ResourceExt},
Resource,
};
use std::{collections::BTreeMap, sync::Arc};
pub fn stateful_set_from_cdb(cdb: &CoreDB) -> StatefulSet {
let ns = cdb.namespace().unwrap();
let name = cdb.name_any();
let mut labels: BTreeMap<String, String> = BTreeMap::new();
let mut pvc_requests: BTreeMap<String, Quantity> = BTreeMap::new();
let oref = cdb.controller_owner_ref(&()).unwrap();
labels.insert("app".to_owned(), "coredb".to_owned());
labels.insert("statefulset".to_owned(), name.to_owned());
pvc_requests.insert("storage".to_string(), Quantity("8Gi".to_string()));
let postgres_env = Some(vec![EnvVar {
name: "POSTGRES_PASSWORD".to_owned(),
value: Some("password".to_owned()),
value_from: None,
}]);
let postgres_volume_mounts = Some(vec![VolumeMount {
name: "data".to_owned(),
mount_path: "/var/lib/postgresql/data".to_owned(),
..VolumeMount::default()
}]);
let sts: StatefulSet = StatefulSet {
metadata: ObjectMeta {
name: Some(name.to_owned()),
namespace: Some(ns.to_owned()),
labels: Some(labels.clone()),
owner_references: Some(vec![oref]),
..ObjectMeta::default()
},
spec: Some(StatefulSetSpec {
replicas: Some(cdb.spec.replicas.clone()),
selector: LabelSelector {
match_expressions: None,
match_labels: Some(labels.clone()),
},
template: PodTemplateSpec {
spec: Some(PodSpec {
containers: vec![
Container {
env: postgres_env.clone(),
security_context: Some(SecurityContext {
run_as_user: Some(cdb.spec.uid.clone() as i64),
allow_privilege_escalation: Some(false),
..SecurityContext::default()
}),
name: "postgres".to_string(),
image: Some(cdb.spec.image.clone()),
ports: Some(vec![ContainerPort {
container_port: 5432,
..ContainerPort::default()
}]),
volume_mounts: postgres_volume_mounts.clone(),
readiness_probe: Some(Probe {
exec: Some(ExecAction {
command: Some(vec![String::from("pg_isready")]),
}),
initial_delay_seconds: Some(3),
..Probe::default()
}),
..Container::default()
},
],
init_containers: Option::from(vec![Container {
env: postgres_env.clone(),
name: "pg-directory-init".to_string(),
image: Some(cdb.spec.image.clone()),
volume_mounts: postgres_volume_mounts.clone(),
args: Some(vec![
"/bin/bash".to_string(),
"-c".to_string(),
"\
set -e
source /usr/local/bin/docker-entrypoint.sh
set -x
docker_setup_env
docker_create_db_directories
"
.to_string(),
]),
..Container::default()
}]),
..PodSpec::default()
}),
metadata: Some(ObjectMeta {
labels: Some(labels),
..ObjectMeta::default()
}),
},
volume_claim_templates: Some(vec![PersistentVolumeClaim {
metadata: ObjectMeta {
name: Some("data".to_string()),
..ObjectMeta::default()
},
spec: Some(PersistentVolumeClaimSpec {
access_modes: Some(vec!["ReadWriteOnce".to_owned()]),
resources: Some(ResourceRequirements {
limits: None,
requests: Some(pvc_requests),
}),
..PersistentVolumeClaimSpec::default()
}),
status: None,
}]),
..StatefulSetSpec::default()
}),
..StatefulSet::default()
};
return sts;
}
pub async fn reconcile_sts(cdb: &CoreDB, ctx: Arc<Context>) -> Result<(), Error> {
let client = ctx.client.clone();
let sts: StatefulSet = stateful_set_from_cdb(cdb);
let sts_api: Api<StatefulSet> = Api::namespaced(client, &sts.clone().metadata.namespace.unwrap());
let ps = PatchParams::apply("cntrlr").force();
let _o = sts_api
.patch(&sts.clone().metadata.name.unwrap(), &ps, &Patch::Apply(&sts))
.await
.map_err(Error::KubeError)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::{stateful_set_from_cdb, StatefulSet};
use crate::{CoreDB, CoreDBSpec};
use kube::Resource;
#[test]
fn test_user_specified_uid() {
let mut cdb_spec: CoreDBSpec = CoreDBSpec::default();
cdb_spec.uid = 1000;
let mut coredb: CoreDB = CoreDB::new("check-uid", cdb_spec);
coredb.meta_mut().namespace = Some("default".into());
coredb.meta_mut().uid = Some("752d59ef-2671-4890-9feb-0097459b18c8".into());
let sts: StatefulSet = stateful_set_from_cdb(&coredb);
assert_eq!(
sts.spec
.expect("StatefulSet does not have a spec")
.template
.spec
.expect("Did not have a pod spec")
.containers[0]
.clone()
.security_context
.expect("Did not have a security context")
.run_as_user
.unwrap(),
1000
);
}
}