Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for DynamicValues struct #723

Merged
merged 10 commits into from
Jan 25, 2024
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
27 changes: 27 additions & 0 deletions fixtures/helm/input-additional.yaml
Original file line number Diff line number Diff line change
@@ -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: {}
22 changes: 22 additions & 0 deletions fixtures/helm/input-required.yaml
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions fixtures/helm/output.yaml
Original file line number Diff line number Diff line change
@@ -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
5 changes: 5 additions & 0 deletions src/commons/product_image_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
///
/// - <https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy>
/// - <https://github.com/kubernetes/kubernetes/blob/master/pkg/apis/core/types.go#L2291-L2300>
pub enum PullPolicy {
IfNotPresent,
#[default]
Expand Down
126 changes: 126 additions & 0 deletions src/helm/mod.rs
Original file line number Diff line number Diff line change
@@ -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<String, String> = 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<BTreeMap<String, String>>,

/// All other keys and values.
#[serde(flatten)]
pub data: serde_yaml::Value,
}

impl DynamicValues {
pub fn labels(&self) -> &BTreeMap<String, String> {
self.labels.as_ref().unwrap_or(&EMPTY_MAP)
}

pub fn labels_mut(&mut self) -> &mut BTreeMap<String, String> {
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
/// <https://kubernetes.io/docs/concepts/containers/images/#image-pull-policy>
pub pull_policy: PullPolicy,

/// Specify one or more pull secrets.
///
/// See more at
/// <https://kubernetes.io/docs/concepts/containers/images/#specifying-imagepullsecrets-on-a-pod>
pub pull_secrets: Vec<LocalObjectReference>,
}

#[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<String, String>,

/// 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();
Techassi marked this conversation as resolved.
Show resolved Hide resolved

let mut output = Vec::new();
serialize_to_explicit_document(&mut output, &values).unwrap();

assert_eq!(std::str::from_utf8(&output).unwrap(), expected);
}
}
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading