Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ All notable changes to this project will be documented in this file.
StatefulSets created by older operator versions cannot be updated in place: after the
operator upgrade, delete each metastore StatefulSet so that the operator immediately recreates it with
the new labels ([#748]).
- Make operations infallible where appropriate ([#759]).

### Fixed

Expand All @@ -42,6 +43,7 @@ All notable changes to this project will be documented in this file.
[#741]: https://github.com/stackabletech/hive-operator/pull/741
[#748]: https://github.com/stackabletech/hive-operator/pull/748
[#754]: https://github.com/stackabletech/hive-operator/pull/754
[#759]: https://github.com/stackabletech/hive-operator/pull/759

## [26.7.0] - 2026-07-21

Expand Down
42 changes: 8 additions & 34 deletions rust/operator-binary/src/controller/build/kerberos.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,11 @@
use std::{collections::BTreeMap, str::FromStr};

use indoc::formatdoc;
use snafu::{ResultExt, Snafu};
use stackable_operator::{
builder::{
self,
pod::{
PodBuilder,
container::ContainerBuilder,
volume::{
SecretOperatorVolumeSourceBuilder, SecretOperatorVolumeSourceBuilderError,
VolumeBuilder,
},
},
builder::pod::{
PodBuilder,
container::ContainerBuilder,
volume::{SecretOperatorVolumeSourceBuilder, VolumeBuilder},
},
commons::secret_class::SecretClassVolumeProvisionParts,
constant,
Expand Down Expand Up @@ -40,29 +33,12 @@ constant!(KRB5_CONFIG: EnvVarName = "KRB5_CONFIG");
/// sub-paths are derived from this.
pub(crate) const STACKABLE_KERBEROS_DIR: &str = "/stackable/kerberos";

#[derive(Snafu, Debug)]
#[allow(clippy::enum_variant_names)] // all variants have the same prefix: `Add`
pub enum Error {
#[snafu(display("failed to add Kerberos secret volume"))]
AddKerberosSecretVolume {
source: SecretOperatorVolumeSourceBuilderError,
},

#[snafu(display("failed to add needed volume"))]
AddVolume { source: builder::pod::Error },

#[snafu(display("failed to add needed volumeMount"))]
AddVolumeMount {
source: builder::pod::container::Error,
},
}

pub fn add_kerberos_pod_config(
cluster: &ValidatedCluster,
role: &HiveRole,
cb: &mut ContainerBuilder,
pb: &mut PodBuilder,
) -> Result<(), Error> {
) {
if let Some(kerberos_secret_class) = &cluster.cluster_config.kerberos_secret_class {
// Mount keytab
let kerberos_secret_operator_volume = SecretOperatorVolumeSourceBuilder::new(
Expand All @@ -73,18 +49,16 @@ pub fn add_kerberos_pod_config(
.with_service_scope(cluster.name.to_string())
.with_kerberos_service_name(role.kerberos_service_name())
.build()
.context(AddKerberosSecretVolumeSnafu)?;
.expect("The annotation keys are static and annotation values cannot be invalid.");
pb.add_volume(
VolumeBuilder::new(&*KERBEROS_VOLUME_NAME)
.ephemeral(kerberos_secret_operator_volume)
.build(),
)
.context(AddVolumeSnafu)?;
.expect("The volume names are statically defined and there should be no duplicates.");
cb.add_volume_mount(&*KERBEROS_VOLUME_NAME, STACKABLE_KERBEROS_DIR)
.context(AddVolumeMountSnafu)?;
.expect("The mount paths are statically defined and there should be no duplicates.");
}

Ok(())
}

/// The environment variables the Kerberos configuration requires on the Hive container, or an
Expand Down
11 changes: 11 additions & 0 deletions rust/operator-binary/src/controller/build/opa.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,14 @@ pub fn build_opa_tls_ca_cert_mount_path(opa: &ResolvedOpaConfig) -> Option<Strin
.as_ref()
.map(|_| format!("/stackable/secrets/{}", *OPA_TLS_VOLUME_NAME))
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_constants() {
// Test that dereferencing the constants does not panic.
let _ = *OPA_TLS_VOLUME_NAME;
}
}
12 changes: 3 additions & 9 deletions rust/operator-binary/src/controller/build/resource/config_map.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,6 @@ pub enum Error {

#[snafu(display("failed to serialize {}", ConfigFileName::Security))]
WriteSecurityProperties { source: PropertiesWriterError },

#[snafu(display("failed to assemble ConfigMap for role group {role_group}"))]
Assemble {
source: stackable_operator::builder::configmap::Error,
role_group: RoleGroupName,
},
}

type Result<T, E = Error> = std::result::Result<T, E>;
Expand Down Expand Up @@ -120,7 +114,7 @@ pub fn build_metastore_rolegroup_config_map(
);
}

cm_builder.build().with_context(|_| AssembleSnafu {
role_group: role_group_name.clone(),
})
Ok(cm_builder
.build()
.expect("The ConfigMap metadata is set in this function."))
}
28 changes: 10 additions & 18 deletions rust/operator-binary/src/controller/build/resource/discovery.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use snafu::{ResultExt, Snafu};
use stackable_operator::{
builder::configmap::ConfigMapBuilder,
k8s_openapi::api::core::v1::ConfigMap,
kube::{api::ObjectMeta, runtime::reflector::ObjectRef},
kube::api::ObjectMeta,
v2::types::{kubernetes::ConfigMapName, operator::ClusterName},
};

Expand All @@ -16,40 +16,34 @@ use crate::{
resource::listener::build_listener_connection_string,
},
},
crd::{HiveRole, v1alpha1},
crd::HiveRole,
};

#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("could not build discovery config map for {obj_ref}"))]
DiscoveryConfigMap {
source: stackable_operator::builder::configmap::Error,
obj_ref: ObjectRef<v1alpha1::HiveCluster>,
},

#[snafu(display("failed to configure listener discovery configmap"))]
ListenerConfiguration {
source: crate::controller::build::resource::listener::Error,
},
}

/// An [`ObjectRef`] back to the owning [`v1alpha1::HiveCluster`], reconstructed from the validated
/// cluster identity for use in error messages.
fn cluster_object_ref(cluster: &ValidatedCluster) -> ObjectRef<v1alpha1::HiveCluster> {
ObjectRef::new(cluster.name.as_ref()).within(cluster.namespace.as_ref())
}

/// The name of the discovery [`ConfigMap`] -- the cluster name itself.
///
/// Takes the bare cluster name (not [`ValidatedCluster`]) so the dereference step, which runs
/// before validation, can derive the same name.
pub fn discovery_config_map_name(cluster_name: &ClusterName) -> ConfigMapName {
const _: () = assert!(
ClusterName::MAX_LENGTH <= ConfigMapName::MAX_LENGTH,
"The string `<cluster_name>` must not exceed the limit of ConfigMap names."
);
let _ = ClusterName::IS_RFC_1123_SUBDOMAIN_NAME;

ConfigMapName::from_str(cluster_name.as_ref())
.expect("a valid cluster name is a valid ConfigMap name")
}

/// Builds the discovery [`ConfigMap`] containing information about how to connect to a certain
/// [`v1alpha1::HiveCluster`].
/// [`crate::crd::v1alpha1::HiveCluster`].
///
/// The ConfigMap needs the role Listener's ingress address, which only the listener-operator
/// writes. While the dereferenced Listener is absent or still address-less (around the first
Expand Down Expand Up @@ -98,9 +92,7 @@ pub fn build_discovery_configmap(

let config_map = discovery_configmap
.build()
.with_context(|_| DiscoveryConfigMapSnafu {
obj_ref: cluster_object_ref(cluster),
})?;
.expect("The ConfigMap metadata is set in this function.");

Ok(Some(config_map))
}
Expand Down
18 changes: 12 additions & 6 deletions rust/operator-binary/src/controller/build/resource/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use stackable_operator::{
crd::listener::v1alpha1::{Listener, ListenerIngress, ListenerPort, ListenerSpec},
v2::types::{
kubernetes::{ListenerClassName, ListenerName},
operator::ClusterName,
operator::{ClusterName, RoleName},
},
};

Expand Down Expand Up @@ -48,11 +48,17 @@ pub fn build_listener_connection_string(
/// Takes the bare cluster name (not [`ValidatedCluster`]) so the dereference step, which runs
/// before validation, can derive the same name.
pub fn role_listener_name(cluster_name: &ClusterName, hive_role: &HiveRole) -> ListenerName {
ListenerName::from_str(&format!(
"{cluster_name}-{hive_role}",
hive_role = **hive_role
))
.expect("the role listener name is a valid Listener name")
const _: () = assert!(
ClusterName::MAX_LENGTH + 1 /* dash */ + RoleName::MAX_LENGTH <= ListenerName::MAX_LENGTH,
"The string `<cluster_name>-<role_name>` must not exceed the limit of Listener names."
);
// Both halves are RFC 1123 labels joined by a dash, which is a valid RFC 1123 subdomain.
let _ = ClusterName::IS_RFC_1123_SUBDOMAIN_NAME;
let _ = RoleName::IS_RFC_1123_LABEL_NAME;

let role_name: &RoleName = hive_role;
ListenerName::from_str(&format!("{cluster_name}-{role_name}"))
.expect("The role listener name is a valid Listener name.")
}

// Designed to build a listener per role
Expand Down
Loading
Loading