diff --git a/CHANGELOG.md b/CHANGELOG.md index aabff077e..7f6ad34fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ All notable changes to this project will be documented in this file. ### Added - Add Serde `Deserialize` and `Serialize` support for `CpuQuantity` and `MemoryQuantity` ([#724]). +- Add `DynamicValues` struct to work with operator `values.yaml` files during runtime ([#723]). +[#723]: https://github.com/stackabletech/operator-rs/pull/723 [#724]: https://github.com/stackabletech/operator-rs/pull/724 ### Changed diff --git a/fixtures/helm/input-additional.yaml b/fixtures/helm/input-additional.yaml new file mode 100644 index 000000000..72eb1fb31 --- /dev/null +++ b/fixtures/helm/input-additional.yaml @@ -0,0 +1,27 @@ +# Partly taken from https://github.com/stackabletech/zookeeper-operator/blob/main/deploy/helm/zookeeper-operator/values.yaml +--- +image: + repository: docker.stackable.tech/stackable/zookeeper-operator + pullPolicy: IfNotPresent + pullSecrets: [] +nameOverride: '' +fullnameOverride: '' +serviceAccount: + create: true + annotations: {} + name: '' +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi +labels: + stackable.tech/vendor: Stackable + stackable.tech/managed-by: stackablectl +podAnnotations: {} +podSecurityContext: {} +nodeSelector: {} +tolerations: [] +affinity: {} diff --git a/fixtures/helm/input-required.yaml b/fixtures/helm/input-required.yaml new file mode 100644 index 000000000..d8bd90f7d --- /dev/null +++ b/fixtures/helm/input-required.yaml @@ -0,0 +1,22 @@ +# Partly taken from https://github.com/stackabletech/zookeeper-operator/blob/main/deploy/helm/zookeeper-operator/values.yaml +--- +image: + repository: docker.stackable.tech/stackable/zookeeper-operator + pullPolicy: IfNotPresent + pullSecrets: [] +nameOverride: '' +fullnameOverride: '' +serviceAccount: + create: true + annotations: {} + name: '' +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi +labels: + stackable.tech/vendor: Stackable + stackable.tech/managed-by: stackablectl diff --git a/fixtures/helm/output.yaml b/fixtures/helm/output.yaml new file mode 100644 index 000000000..6ac527fe1 --- /dev/null +++ b/fixtures/helm/output.yaml @@ -0,0 +1,22 @@ +--- +image: + repository: docker.stackable.tech/stackable/zookeeper-operator + pullPolicy: IfNotPresent + pullSecrets: [] +nameOverride: '' +fullnameOverride: '' +serviceAccount: + create: true + annotations: {} + name: '' +resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi +labels: + stackable.tech/demo: logging + stackable.tech/managed-by: stackablectl + stackable.tech/vendor: Stackable diff --git a/src/commons/product_image_selection.rs b/src/commons/product_image_selection.rs index 17eb58c8d..a999febfa 100644 --- a/src/commons/product_image_selection.rs +++ b/src/commons/product_image_selection.rs @@ -85,6 +85,11 @@ pub struct ResolvedProductImage { #[derive(AsRefStr)] /// We default to `Always`, as we use floating tags for our release lines. /// This means the tag 23.4 starts of pointing to the same image 23.4.0 does, but switches to 23.4.1 after the releases of 23.4.1. +/// +/// ### See +/// +/// - +/// - pub enum PullPolicy { IfNotPresent, #[default] diff --git a/src/helm/mod.rs b/src/helm/mod.rs new file mode 100644 index 000000000..5e5db5d19 --- /dev/null +++ b/src/helm/mod.rs @@ -0,0 +1,126 @@ +use std::collections::BTreeMap; + +use k8s_openapi::api::core::v1::LocalObjectReference; +use serde::{Deserialize, Serialize}; + +use crate::{ + commons::product_image_selection::PullPolicy, cpu::CpuQuantity, memory::MemoryQuantity, +}; + +static EMPTY_MAP: BTreeMap = BTreeMap::new(); + +/// A dynamic representation of a Helm `values.yaml` file. +/// +/// This will work with any operator `values.yaml` file. It basically only +/// contains common fields which exist in all value files we use for our +/// operators. +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct DynamicValues { + pub image: ImageValues, + pub name_override: String, + pub fullname_override: String, + pub service_account: ServiceAccountValues, + pub resources: ResourceValues, + + // TODO(Techassi): Here we could use direct Serialize and Deserialize support + pub labels: Option>, + + /// All other keys and values. + #[serde(flatten)] + pub data: serde_yaml::Value, +} + +impl DynamicValues { + pub fn labels(&self) -> &BTreeMap { + self.labels.as_ref().unwrap_or(&EMPTY_MAP) + } + + pub fn labels_mut(&mut self) -> &mut BTreeMap { + self.labels.get_or_insert_with(BTreeMap::new) + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ImageValues { + /// Specify from which repository this image should get pulled from. + pub repository: String, + + /// Specify the pull policy of this image. + /// + /// See more at + /// + pub pull_policy: PullPolicy, + + /// Specify one or more pull secrets. + /// + /// See more at + /// + pub pull_secrets: Vec, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ServiceAccountValues { + /// Specify whether a service account should be created. + pub create: bool, + + /// Specify which annotations to add to the service account. + pub annotations: BTreeMap, + + /// Specify the name of the service account. + /// + /// If this is not set and `create` is true, a name is generated using the + /// fullname template. + pub name: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ResourceValues { + limits: ComputeResourceValues, + requests: ComputeResourceValues, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ComputeResourceValues { + cpu: CpuQuantity, + memory: MemoryQuantity, +} + +#[cfg(test)] +mod test { + use std::path::PathBuf; + + use rstest::rstest; + + use crate::yaml::serialize_to_explicit_document; + + use super::*; + + #[rstest] + fn test_deserialize(#[files("fixtures/helm/input-*.yaml")] path: PathBuf) { + let contents = std::fs::read_to_string(path).unwrap(); + let values: DynamicValues = serde_yaml::from_str(&contents).unwrap(); + assert_eq!(values.labels().len(), 2); + } + + #[rstest] + fn test_serialize(#[files("fixtures/helm/input-required.yaml")] input: PathBuf) { + let contents = std::fs::read_to_string(input).unwrap(); + let mut values: DynamicValues = serde_yaml::from_str(&contents).unwrap(); + + values + .labels_mut() + .insert("stackable.tech/demo".into(), "logging".into()); + + let expected = std::fs::read_to_string("fixtures/helm/output.yaml").unwrap(); + + let mut output = Vec::new(); + serialize_to_explicit_document(&mut output, &values).unwrap(); + + assert_eq!(std::str::from_utf8(&output).unwrap(), expected); + } +} diff --git a/src/lib.rs b/src/lib.rs index 4d3e5116c..86eb247b4 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,6 +7,7 @@ pub mod config; pub mod cpu; pub mod crd; pub mod error; +pub mod helm; pub mod iter; pub mod kvp; pub mod logging;