Skip to content

Commit

Permalink
Merge branch 'main' into chore/add-kerberos-auth-prov
Browse files Browse the repository at this point in the history
  • Loading branch information
adwk67 committed Sep 27, 2024
2 parents 61c45e1 + cb4460f commit f5019e1
Show file tree
Hide file tree
Showing 27 changed files with 654 additions and 191 deletions.
7 changes: 4 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ ecdsa = { version = "0.16.9", features = ["digest", "pem"] }
either = "1.13.0"
futures = "0.3.30"
futures-util = "0.3.30"
indexmap = "2.5"
hyper = { version = "1.4.1", features = ["full"] }
hyper-util = "0.1.8"
itertools = "0.13.0"
Expand Down
23 changes: 23 additions & 0 deletions crates/stackable-operator/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.77.1] - 2024-09-27

### Fixed

- Fix always returning an error stating that volumeMounts are colliding. Instead move the error
creation to the correct location within an `if` statement ([#879]).

[#879]: https://github.com/stackabletech/operator-rs/pull/879

## [0.77.0] - 2024-09-26

### Fixed

- Fix the logback configuration for logback versions from 1.3.6/1.4.6 to 1.3.11/1.4.11 ([#874]).
- BREAKING: Avoid colliding volumes and mounts by only adding volumes or mounts if they do not already exist. This makes functions such as `PodBuilder::add_volume` or `ContainerBuilder::add_volume_mount` as well as related ones fallible ([#871]).

### Changed

- BREAKING: Remove the `unique_identifier` argument from `ResolvedS3Connection::add_volumes_and_mounts`, `ResolvedS3Connection::volumes_and_mounts` and `ResolvedS3Connection::credentials_mount_paths` as it is not needed anymore ([#871]).

[#871]: https://github.com/stackabletech/operator-rs/pull/871
[#874]: https://github.com/stackabletech/operator-rs/pull/874

## [0.76.0] - 2024-09-19

### Added
Expand Down
3 changes: 2 additions & 1 deletion crates/stackable-operator/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "stackable-operator"
description = "Stackable Operator Framework"
version = "0.76.0"
version = "0.77.1"
authors.workspace = true
license.workspace = true
edition.workspace = true
Expand All @@ -21,6 +21,7 @@ derivative.workspace = true
dockerfile-parser.workspace = true
either.workspace = true
futures.workspace = true
indexmap.workspace = true
json-patch.workspace = true
k8s-openapi.workspace = true
kube.workspace = true
Expand Down
144 changes: 110 additions & 34 deletions crates/stackable-operator/src/builder/pod/container.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use std::fmt;

#[cfg(doc)]
use {k8s_openapi::api::core::v1::PodSpec, std::collections::BTreeMap};

use indexmap::IndexMap;
use k8s_openapi::api::core::v1::{
ConfigMapKeySelector, Container, ContainerPort, EnvVar, EnvVarSource, Lifecycle,
LifecycleHandler, ObjectFieldSelector, Probe, ResourceRequirements, SecretKeySelector,
SecurityContext, VolumeMount,
};
use snafu::{ResultExt as _, Snafu};
use tracing::instrument;

use crate::{
commons::product_image_selection::ResolvedProductImage,
Expand All @@ -21,11 +26,26 @@ pub enum Error {
source: validation::Errors,
container_name: String,
},

#[snafu(display(
"Colliding mountPath {colliding_mount_path:?} in volumeMounts with different content. \
Existing volume name {existing_volume_name:?}, new volume name {new_volume_name:?}"
))]
MountPathCollision {
colliding_mount_path: String,
existing_volume_name: String,
new_volume_name: String,
},
}

/// A builder to build [`Container`] objects.
///
/// This will automatically create the necessary volumes and mounts for each `ConfigMap` which is added.
///
/// This struct is often times using an [`IndexMap`] to have consistent ordering (so we don't produce reconcile loops).
/// We are also choosing it over a [`BTreeMap`], because it is easier to debug for users, as logically
/// grouped volumeMounts (e.g. all volumeMounts related to S3) are near each other in the list instead of "just" being
/// sorted alphabetically.
#[derive(Clone, Default)]
pub struct ContainerBuilder {
args: Option<Vec<String>>,
Expand All @@ -36,7 +56,9 @@ pub struct ContainerBuilder {
image_pull_policy: Option<String>,
name: String,
resources: Option<ResourceRequirements>,
volume_mounts: Option<Vec<VolumeMount>>,

/// The key is the volumeMount mountPath.
volume_mounts: IndexMap<String, VolumeMount>,
readiness_probe: Option<Probe>,
liveness_probe: Option<Probe>,
startup_probe: Option<Probe>,
Expand Down Expand Up @@ -188,29 +210,80 @@ impl ContainerBuilder {
self
}

/// Adds a new [`VolumeMount`] to the container while ensuring that no colliding [`VolumeMount`]
/// exists.
///
/// A colliding [`VolumeMount`] would have the same mountPath but a different content than
/// another [`VolumeMount`]. An appropriate error is returned when such a colliding mount path is
/// encountered.
///
/// ### Note
///
/// Previously, this function unconditionally added [`VolumeMount`]s, which resulted in invalid
/// [`PodSpec`]s.
#[instrument(skip(self))]
fn add_volume_mount_impl(&mut self, volume_mount: VolumeMount) -> Result<&mut Self> {
if let Some(existing_volume_mount) = self.volume_mounts.get(&volume_mount.mount_path) {
if existing_volume_mount != &volume_mount {
let colliding_mount_path = &volume_mount.mount_path;
// We don't want to include the details in the error message, but instead trace them
tracing::error!(
colliding_mount_path,
?existing_volume_mount,
"Colliding mountPath in volumeMounts with different content"
);

MountPathCollisionSnafu {
colliding_mount_path,
existing_volume_name: &existing_volume_mount.name,
new_volume_name: &volume_mount.name,
}
.fail()?;
}
} else {
self.volume_mounts
.insert(volume_mount.mount_path.clone(), volume_mount);
}

Ok(self)
}

/// Adds a new [`VolumeMount`] to the container while ensuring that no colliding [`VolumeMount`]
/// exists.
///
/// A colliding [`VolumeMount`] would have the same mountPath but a different content than
/// another [`VolumeMount`]. An appropriate error is returned when such a colliding mount path is
/// encountered.
///
/// ### Note
///
/// Previously, this function unconditionally added [`VolumeMount`]s, which resulted in invalid
/// [`PodSpec`]s.
pub fn add_volume_mount(
&mut self,
name: impl Into<String>,
path: impl Into<String>,
) -> &mut Self {
self.volume_mounts
.get_or_insert_with(Vec::new)
.push(VolumeMount {
name: name.into(),
mount_path: path.into(),
..VolumeMount::default()
});
self
) -> Result<&mut Self> {
self.add_volume_mount_impl(VolumeMount {
name: name.into(),
mount_path: path.into(),
..VolumeMount::default()
})
}

/// Adds new [`VolumeMount`]s to the container while ensuring that no colliding [`VolumeMount`]
/// exists.
///
/// See [`Self::add_volume_mount`] for details.
pub fn add_volume_mounts(
&mut self,
volume_mounts: impl IntoIterator<Item = VolumeMount>,
) -> &mut Self {
self.volume_mounts
.get_or_insert_with(Vec::new)
.extend(volume_mounts);
self
) -> Result<&mut Self> {
for volume_mount in volume_mounts {
self.add_volume_mount_impl(volume_mount)?;
}

Ok(self)
}

pub fn readiness_probe(&mut self, probe: Probe) -> &mut Self {
Expand Down Expand Up @@ -256,6 +329,12 @@ impl ContainerBuilder {
}

pub fn build(&self) -> Container {
let volume_mounts = if self.volume_mounts.is_empty() {
None
} else {
Some(self.volume_mounts.values().cloned().collect())
};

Container {
args: self.args.clone(),
command: self.command.clone(),
Expand All @@ -265,7 +344,7 @@ impl ContainerBuilder {
resources: self.resources.clone(),
name: self.name.clone(),
ports: self.container_ports.clone(),
volume_mounts: self.volume_mounts.clone(),
volume_mounts,
readiness_probe: self.readiness_probe.clone(),
liveness_probe: self.liveness_probe.clone(),
startup_probe: self.startup_probe.clone(),
Expand Down Expand Up @@ -388,6 +467,7 @@ mod tests {
.add_env_var_from_config_map("envFromConfigMap", "my-configmap", "my-key")
.add_env_var_from_secret("envFromSecret", "my-secret", "my-key")
.add_volume_mount("configmap", "/mount")
.expect("add volume mount")
.add_container_port(container_port_name, container_port)
.resources(resources.clone())
.add_container_ports(vec![ContainerPortBuilder::new(container_port_1)
Expand Down Expand Up @@ -491,20 +571,18 @@ mod tests {
"lengthexceededlengthexceededlengthexceededlengthexceededlengthex";
assert_eq!(long_container_name.len(), 64); // 63 characters is the limit for container names
let result = ContainerBuilder::new(long_container_name);
match result
if let Error::InvalidContainerName {
container_name,
source,
} = result
.err()
.expect("Container name exceeding 63 characters should cause an error")
{
Error::InvalidContainerName {
container_name,
source,
} => {
assert_eq!(container_name, long_container_name);
assert_eq!(
source.to_string(),
"input is 64 bytes long but must be no more than 63"
)
}
assert_eq!(container_name, long_container_name);
assert_eq!(
source.to_string(),
"input is 64 bytes long but must be no more than 63"
)
}
// One characters shorter name is valid
let max_len_container_name: String = long_container_name.chars().skip(1).collect();
Expand Down Expand Up @@ -568,16 +646,14 @@ mod tests {
result: Result<ContainerBuilder, Error>,
expected_err_contains: &str,
) {
match result
if let Error::InvalidContainerName {
container_name: _,
source,
} = result
.err()
.expect("Container name exceeding 63 characters should cause an error")
{
Error::InvalidContainerName {
container_name: _,
source,
} => {
assert!(dbg!(source.to_string()).contains(dbg!(expected_err_contains)));
}
assert!(dbg!(source.to_string()).contains(dbg!(expected_err_contains)));
}
}
}
Loading

0 comments on commit f5019e1

Please sign in to comment.