From 6930d2da107a99cfc3575a73e58067ec2143c12a Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 26 Aug 2026 18:20:58 +0200 Subject: [PATCH 1/7] use expect where possible and remove unecessary enums/Results --- .../src/controller/build/kerberos.rs | 50 +++++-------------- .../src/controller/build/mod.rs | 7 +-- .../controller/build/resource/config_map.rs | 14 ++---- .../controller/build/resource/discovery.rs | 15 +----- .../src/controller/build/resource/listener.rs | 37 ++++---------- .../controller/build/resource/statefulset.rs | 42 +++++----------- 6 files changed, 43 insertions(+), 122 deletions(-) diff --git a/rust/operator-binary/src/controller/build/kerberos.rs b/rust/operator-binary/src/controller/build/kerberos.rs index 002aa432..2069f7c4 100644 --- a/rust/operator-binary/src/controller/build/kerberos.rs +++ b/rust/operator-binary/src/controller/build/kerberos.rs @@ -1,14 +1,10 @@ use std::{collections::BTreeMap, str::FromStr}; -use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::{ - self, - pod::{ - PodBuilder, - container::ContainerBuilder, - volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, - }, + builder::pod::{ + PodBuilder, + container::ContainerBuilder, + volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, }, commons::secret_class::SecretClassVolumeProvisionParts, constant, @@ -37,27 +33,6 @@ constant!(KRB5_CONFIG_ENV: EnvVarName = "KRB5_CONFIG"); /// The RPC/data-transfer quality-of-protection level used when Kerberos is enabled. const PROTECTION_PRIVACY: &str = "privacy"; -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to build Kerberos secret volume"))] - BuildKerberosSecretVolume { - source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, - }, - - #[snafu(display("failed to build TLS secret volume"))] - BuildTlsSecretVolume { - source: stackable_operator::builder::pod::volume::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, - }, -} - /// The `hbase-site.xml` Kerberos properties for `cluster`, gated on Kerberos being enabled /// (empty when disabled). Derived in the build step from the validated cluster. pub fn hbase_site_kerberos_config( @@ -235,7 +210,7 @@ pub fn add_kerberos_pod_config( cb: &mut ContainerBuilder, pb: &mut PodBuilder, requested_secret_lifetime: Duration, -) -> Result<(), Error> { +) { if let Some(kerberos_secret_class) = &cluster.cluster_config.kerberos_secret_class { // Mount keytab let kerberos_secret_operator_volume = SecretOperatorVolumeSourceBuilder::new( @@ -247,15 +222,15 @@ pub fn add_kerberos_pod_config( .with_kerberos_service_name(kerberos_service_name()) .with_kerberos_service_name("HTTP") .build() - .context(BuildKerberosSecretVolumeSnafu)?; + .expect("The annotations are built from a validated secret class and static scopes."); 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."); } if let Some(https_secret_class) = &cluster.cluster_config.https_secret_class { @@ -277,15 +252,16 @@ pub fn add_kerberos_pod_config( .with_tls_pkcs12_password(TLS_STORE_PASSWORD) .with_auto_tls_cert_lifetime(requested_secret_lifetime) .build() - .context(BuildTlsSecretVolumeSnafu)?, + .expect( + "The annotations are built from a validated secret class and static scopes.", + ), ) .build(), ) - .context(AddVolumeSnafu)?; + .expect("The volume names are statically defined and there should be no duplicates."); cb.add_volume_mount(&*TLS_STORE_VOLUME_NAME, TLS_STORE_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 HBase container, or an diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index 396a38d9..551f7a55 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -21,7 +21,7 @@ use crate::{ ValidatedCluster, build::resource::{ config_map::{self, build_rolegroup_config_map}, - discovery::{self, build_discovery_config_map}, + discovery::build_discovery_config_map, pdb::build_pdb, rbac::{build_role_binding, build_service_account}, service::{build_rolegroup_metrics_service, build_rolegroup_service}, @@ -65,9 +65,6 @@ pub enum Error { hbase_role: HbaseRole, role_group: RoleGroupName, }, - - #[snafu(display("failed to build discovery ConfigMap"))] - Discovery { source: discovery::Error }, } /// Builds every Kubernetes resource for the given validated cluster. @@ -121,7 +118,7 @@ pub fn build( // The role-level discovery ConfigMap advertises the cluster's connection information; it is // deterministic (derived only from the validated cluster and static cluster info). - config_maps.push(build_discovery_config_map(cluster, cluster_info).context(DiscoverySnafu)?); + config_maps.push(build_discovery_config_map(cluster, cluster_info)); Ok(KubernetesResources { stateful_sets, diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 08d5a6f0..8e170489 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -38,13 +38,6 @@ pub enum Error { source: PropertiesWriterError, role_group: String, }, - - #[snafu(display("cannot build config map for role {role:?} and role group {role_group:?}"))] - Assemble { - source: stackable_operator::builder::configmap::Error, - role: String, - role_group: String, - }, } type Result = std::result::Result; @@ -148,8 +141,7 @@ pub fn build_rolegroup_config_map( ); } - builder.build().with_context(|_| AssembleSnafu { - role: role.to_string(), - role_group: role_group_name.to_string(), - }) + Ok(builder + .build() + .expect("The ConfigMap metadata is set in this function.")) } diff --git a/rust/operator-binary/src/controller/build/resource/discovery.rs b/rust/operator-binary/src/controller/build/resource/discovery.rs index 018b7ac3..486f846f 100644 --- a/rust/operator-binary/src/controller/build/resource/discovery.rs +++ b/rust/operator-binary/src/controller/build/resource/discovery.rs @@ -1,6 +1,5 @@ //! Build the discovery `ConfigMap` for the HbaseCluster. -use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::configmap::ConfigMapBuilder, k8s_openapi::api::core::v1::ConfigMap, utils::cluster_info::KubernetesClusterInfo, v2::config_file_writer::to_hadoop_xml, @@ -17,21 +16,11 @@ use crate::{ crd::HbaseRole, }; -type Result = std::result::Result; - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to build ConfigMap"))] - BuildConfigMap { - source: stackable_operator::builder::configmap::Error, - }, -} - /// Creates a discovery config map containing the `hbase-site.xml` for clients. pub fn build_discovery_config_map( cluster: &ValidatedCluster, cluster_info: &KubernetesClusterInfo, -) -> Result { +) -> ConfigMap { let cluster_config = &cluster.cluster_config; let mut hbase_site = cluster_config @@ -56,5 +45,5 @@ pub fn build_discovery_config_map( to_hadoop_xml(hbase_site.iter()), ) .build() - .context(BuildConfigMapSnafu) + .expect("The ConfigMap metadata is set in this function.") } diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index 873a8103..bfa6a069 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -1,13 +1,10 @@ //! Build the listener `Volume`/`PersistentVolumeClaim` exposing a rolegroup. -use std::{str::FromStr, sync::LazyLock}; +use std::str::FromStr; -use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::pod::volume::{ - ListenerOperatorVolumeSourceBuilder, ListenerOperatorVolumeSourceBuilderError, - ListenerReference, VolumeBuilder, - }, + builder::pod::volume::{ListenerOperatorVolumeSourceBuilder, ListenerReference, VolumeBuilder}, + constant, k8s_openapi::api::core::v1::{PersistentVolumeClaim, Volume}, kvp::Labels, v2::{ @@ -21,22 +18,7 @@ use stackable_operator::{ use crate::crd::{AnyServiceConfig, HbaseRole, LISTENER_VOLUME_NAME}; -/// The rest servers' listener `PersistentVolumeClaim` reuses the listener volume name -/// ([`LISTENER_VOLUME_NAME`]); the claim and the volume must share a name. -static LISTENER_PVC_NAME: LazyLock = LazyLock::new(|| { - PersistentVolumeClaimName::from_str(LISTENER_VOLUME_NAME) - .expect("LISTENER_VOLUME_NAME is a valid PersistentVolumeClaim name") -}); - -#[derive(Snafu, Debug)] -pub enum Error { - #[snafu(display("failed to build listener volume"))] - BuildListenerVolume { - source: ListenerOperatorVolumeSourceBuilderError, - }, -} - -type Result = std::result::Result; +constant!(pub LISTENER_PVC_NAME: PersistentVolumeClaimName = LISTENER_VOLUME_NAME); /// The ephemeral listener [`Volume`] for the masters and region servers, or `None` for the rest /// servers (which use a [`PersistentVolumeClaim`] instead, see [`build_listener_pvc`]). @@ -44,8 +26,8 @@ pub fn build_listener_volume( role: &HbaseRole, merged_config: &AnyServiceConfig, recommended_labels: &Labels, -) -> Result> { - let volume = match role { +) -> Option { + match role { // Master and regionservers should use ephemeral listener volumes // since clients pull the latest address from ZooKeeper HbaseRole::Master | HbaseRole::RegionServer => Some( @@ -64,13 +46,14 @@ pub fn build_listener_volume( recommended_labels, ) .build_ephemeral() - .context(BuildListenerVolumeSnafu)?, + .expect( + "The annotations are built from a validated listener class and validated labels.", + ), ) .build(), ), HbaseRole::RestServer => None, - }; - Ok(volume) + } } /// The listener [`PersistentVolumeClaim`] template for the rest servers, or `None` for the masters diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 144f4235..ed48b895 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -6,7 +6,6 @@ use indoc::formatdoc; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ - self, meta::ObjectMetaBuilder, pod::{PodBuilder, security::PodSecurityContextBuilder}, }, @@ -79,22 +78,8 @@ pub enum Error { #[snafu(display("missing secret lifetime"))] MissingSecretLifetime, - #[snafu(display("failed to add kerberos config"))] - AddKerberosConfig { source: kerberos::Error }, - #[snafu(display("failed to configure graceful shutdown"))] GracefulShutdown { source: graceful_shutdown::Error }, - - #[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, - }, - - #[snafu(display("failed to build listener volume"))] - ListenerVolume { source: super::listener::Error }, } type Result = std::result::Result; @@ -192,15 +177,15 @@ pub fn build_rolegroup_statefulset( }]) .add_env_vars(merged_env) .add_volume_mount(&*HBASE_CONFIG_VOLUME_NAME, HBASE_CONFIG_TMP_DIR) - .context(AddVolumeMountSnafu)? + .expect("The mount paths are statically defined and there should be no duplicates.") .add_volume_mount(&*HDFS_DISCOVERY_VOLUME_NAME, HDFS_DISCOVERY_TMP_DIR) - .context(AddVolumeMountSnafu)? + .expect("The mount paths are statically defined and there should be no duplicates.") .add_volume_mount(&*LOG_CONFIG_VOLUME_NAME, HBASE_LOG_CONFIG_TMP_DIR) - .context(AddVolumeMountSnafu)? + .expect("The mount paths are statically defined and there should be no duplicates.") .add_volume_mount(&*LOG_VOLUME_NAME, STACKABLE_LOG_DIR) - .context(AddVolumeMountSnafu)? + .expect("The mount paths are statically defined and there should be no duplicates.") .add_volume_mount(LISTENER_VOLUME_NAME, LISTENER_VOLUME_DIR) - .context(AddVolumeMountSnafu)? + .expect("The mount paths are statically defined and there should be no duplicates.") .add_container_ports(ports) .resources(merged_config.resources().clone().into()) .startup_probe(startup_probe) @@ -228,7 +213,7 @@ pub fn build_rolegroup_statefulset( }), ..Default::default() }) - .context(AddVolumeSnafu)? + .expect("The volume names are statically defined and there should be no duplicates.") .add_volume(Volume { name: HDFS_DISCOVERY_VOLUME_NAME.to_string(), config_map: Some(ConfigMapVolumeSource { @@ -237,14 +222,14 @@ pub fn build_rolegroup_statefulset( }), ..Default::default() }) - .context(AddVolumeSnafu)? + .expect("The volume names are statically defined and there should be no duplicates.") .add_empty_dir_volume( &*LOG_VOLUME_NAME, Some(product_logging::framework::calculate_log_volume_size_limit( &[MAX_HBASE_LOG_FILES_SIZE], )), ) - .context(AddVolumeSnafu)? + .expect("The volume names are statically defined and there should be no duplicates.") .service_account_name( cluster .cluster_resource_names() @@ -275,7 +260,7 @@ pub fn build_rolegroup_statefulset( }), ..Volume::default() }) - .context(AddVolumeSnafu)?; + .expect("The volume names are statically defined and there should be no duplicates."); add_graceful_shutdown_config(merged_config, &mut pod_builder).context(GracefulShutdownSnafu)?; if cluster.has_kerberos_enabled() { @@ -287,8 +272,7 @@ pub fn build_rolegroup_statefulset( merged_config .requested_secret_lifetime() .context(MissingSecretLifetimeSnafu)?, - ) - .context(AddKerberosConfigSnafu)?; + ); } pod_builder.add_container(hbase_container.build()); @@ -319,11 +303,10 @@ pub fn build_rolegroup_statefulset( if let Some(listener_volume) = super::listener::build_listener_volume(hbase_role, merged_config, &recommended_labels) - .context(ListenerVolumeSnafu)? { pod_builder .add_volume(listener_volume) - .context(AddVolumeSnafu)?; + .expect("The volume names are statically defined and there should be no duplicates."); }; let mut pod_template = pod_builder.build_template(); @@ -374,7 +357,7 @@ fn command() -> Vec { #[cfg(test)] mod tests { use super::*; - use crate::test_utils; + use crate::{controller::build::resource::listener::LISTENER_PVC_NAME, test_utils}; /// `envOverrides` are applied after every operator-set environment variable, so users can /// override any of them (previously the operator's value silently won for the variables set @@ -561,5 +544,6 @@ spec: let _ = *RUN_REGION_MOVER_ENV; let _ = *STACKABLE_LOG_DIR_ENV; let _ = *CONTAINERDEBUG_LOG_DIRECTORY_ENV; + let _ = *LISTENER_PVC_NAME; } } From 5aec6d4e93938d0ad14f39de8662976a1b800a5f Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 28 Aug 2026 16:08:39 +0200 Subject: [PATCH 2/7] changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60e24fb2..9b2aa455 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ the new labels ([#799]). - Environment variable overrides (`envOverrides`) are now applied after all environment variables set by the operator ([#799]). +- Make operations infallible where appropriate ([#803]). ### Fixed @@ -41,6 +42,7 @@ [#795]: https://github.com/stackabletech/hbase-operator/pull/795 [#797]: https://github.com/stackabletech/hbase-operator/pull/797 [#799]: https://github.com/stackabletech/hbase-operator/pull/799 +[#803]: https://github.com/stackabletech/hbase-operator/pull/803 ## [26.7.0] - 2026-07-21 From 59752d44900f716e5203a63e0807a41c35898966 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 28 Aug 2026 17:12:13 +0200 Subject: [PATCH 3/7] improve expect messages --- rust/operator-binary/src/controller/build/kerberos.rs | 4 ++-- .../operator-binary/src/controller/build/resource/listener.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/controller/build/kerberos.rs b/rust/operator-binary/src/controller/build/kerberos.rs index 2069f7c4..d704cf2f 100644 --- a/rust/operator-binary/src/controller/build/kerberos.rs +++ b/rust/operator-binary/src/controller/build/kerberos.rs @@ -222,7 +222,7 @@ pub fn add_kerberos_pod_config( .with_kerberos_service_name(kerberos_service_name()) .with_kerberos_service_name("HTTP") .build() - .expect("The annotations are built from a validated secret class and static scopes."); + .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) @@ -253,7 +253,7 @@ pub fn add_kerberos_pod_config( .with_auto_tls_cert_lifetime(requested_secret_lifetime) .build() .expect( - "The annotations are built from a validated secret class and static scopes.", + "The annotation keys are static and annotation values cannot be invalid.", ), ) .build(), diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index bfa6a069..aaa79038 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -47,7 +47,7 @@ pub fn build_listener_volume( ) .build_ephemeral() .expect( - "The annotations are built from a validated listener class and validated labels.", + "The annotation keys are static and annotation values cannot be invalid.", ), ) .build(), From 6bbaa9aed2f931cc6b5c303fe048b926c31b5175 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 7 Sep 2026 16:25:26 +0200 Subject: [PATCH 4/7] add panic docs to helper functions --- rust/operator-binary/src/controller/build/kerberos.rs | 8 ++++++++ .../src/controller/build/resource/listener.rs | 5 +++++ 2 files changed, 13 insertions(+) diff --git a/rust/operator-binary/src/controller/build/kerberos.rs b/rust/operator-binary/src/controller/build/kerberos.rs index d704cf2f..6e1642a0 100644 --- a/rust/operator-binary/src/controller/build/kerberos.rs +++ b/rust/operator-binary/src/controller/build/kerberos.rs @@ -204,6 +204,14 @@ pub fn kerberos_ssl_client_settings() -> BTreeMap { truststore_settings("client") } +/// Adds the Kerberos keytab and TLS keystore volumes to the [`PodBuilder`] and their mounts to the +/// [`ContainerBuilder`], for whichever of the two secret classes are configured. +/// +/// # Panics +/// +/// Panics if the volumes or volume mounts cannot be added to the builders. Only call this +/// on builders whose volume names and mount paths are still distinct from the ones added +/// here. pub fn add_kerberos_pod_config( cluster: &ValidatedCluster, metrics_service_name: &str, diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index aaa79038..fff3f6ae 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -22,6 +22,11 @@ constant!(pub LISTENER_PVC_NAME: PersistentVolumeClaimName = LISTENER_VOLUME_NAM /// The ephemeral listener [`Volume`] for the masters and region servers, or `None` for the rest /// servers (which use a [`PersistentVolumeClaim`] instead, see [`build_listener_pvc`]). +/// +/// # Panics +/// +/// Panics if the volume source cannot be built, which cannot happen because the annotation +/// keys are static and annotation values cannot be invalid. pub fn build_listener_volume( role: &HbaseRole, merged_config: &AnyServiceConfig, From 980fe1eb9011468bed637120e0934520c1b95402 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 7 Sep 2026 16:42:03 +0200 Subject: [PATCH 5/7] remove panics doc whaere it make no sense --- .../src/controller/build/resource/listener.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index fff3f6ae..aaa79038 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -22,11 +22,6 @@ constant!(pub LISTENER_PVC_NAME: PersistentVolumeClaimName = LISTENER_VOLUME_NAM /// The ephemeral listener [`Volume`] for the masters and region servers, or `None` for the rest /// servers (which use a [`PersistentVolumeClaim`] instead, see [`build_listener_pvc`]). -/// -/// # Panics -/// -/// Panics if the volume source cannot be built, which cannot happen because the annotation -/// keys are static and annotation values cannot be invalid. pub fn build_listener_volume( role: &HbaseRole, merged_config: &AnyServiceConfig, From e7d3a4f895cb43f88a697e1456acac5c649405b1 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 8 Sep 2026 14:47:51 +0200 Subject: [PATCH 6/7] move listener pvc name test to the correct place --- .../src/controller/build/resource/listener.rs | 13 ++++++++++++- .../src/controller/build/resource/statefulset.rs | 3 +-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index aaa79038..385fa8da 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -18,7 +18,7 @@ use stackable_operator::{ use crate::crd::{AnyServiceConfig, HbaseRole, LISTENER_VOLUME_NAME}; -constant!(pub LISTENER_PVC_NAME: PersistentVolumeClaimName = LISTENER_VOLUME_NAME); +constant!(LISTENER_PVC_NAME: PersistentVolumeClaimName = LISTENER_VOLUME_NAME); /// The ephemeral listener [`Volume`] for the masters and region servers, or `None` for the rest /// servers (which use a [`PersistentVolumeClaim`] instead, see [`build_listener_pvc`]). @@ -72,3 +72,14 @@ pub fn build_listener_pvc( )]), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_constants() { + // Test that dereferencing the constants does not panic. + let _ = *LISTENER_PVC_NAME; + } +} diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index ed48b895..2d83ff95 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -357,7 +357,7 @@ fn command() -> Vec { #[cfg(test)] mod tests { use super::*; - use crate::{controller::build::resource::listener::LISTENER_PVC_NAME, test_utils}; + use crate::test_utils; /// `envOverrides` are applied after every operator-set environment variable, so users can /// override any of them (previously the operator's value silently won for the variables set @@ -544,6 +544,5 @@ spec: let _ = *RUN_REGION_MOVER_ENV; let _ = *STACKABLE_LOG_DIR_ENV; let _ = *CONTAINERDEBUG_LOG_DIRECTORY_ENV; - let _ = *LISTENER_PVC_NAME; } } From 36220ec618d0da8fc2b72580e49b5657b492e334 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 8 Sep 2026 15:54:54 +0200 Subject: [PATCH 7/7] revert expects where they are dependent on user/calculated input or builder internals --- CHANGELOG.md | 2 +- .../src/controller/build/kerberos.rs | 46 +++++++++++++------ .../src/controller/build/mod.rs | 7 ++- .../controller/build/resource/config_map.rs | 14 ++++-- .../controller/build/resource/discovery.rs | 15 +++++- .../src/controller/build/resource/listener.rs | 27 ++++++++--- .../controller/build/resource/statefulset.rs | 24 +++++++--- 7 files changed, 100 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b2aa455..6462cd1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ the new labels ([#799]). - Environment variable overrides (`envOverrides`) are now applied after all environment variables set by the operator ([#799]). -- Make operations infallible where appropriate ([#803]). +- Make operations infallible where dependent on static inputs ([#803]). ### Fixed diff --git a/rust/operator-binary/src/controller/build/kerberos.rs b/rust/operator-binary/src/controller/build/kerberos.rs index 6e1642a0..17f9ca7c 100644 --- a/rust/operator-binary/src/controller/build/kerberos.rs +++ b/rust/operator-binary/src/controller/build/kerberos.rs @@ -1,10 +1,14 @@ use std::{collections::BTreeMap, str::FromStr}; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::pod::{ - PodBuilder, - container::ContainerBuilder, - volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, + builder::{ + self, + pod::{ + PodBuilder, + container::ContainerBuilder, + volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, + }, }, commons::secret_class::SecretClassVolumeProvisionParts, constant, @@ -33,6 +37,22 @@ constant!(KRB5_CONFIG_ENV: EnvVarName = "KRB5_CONFIG"); /// The RPC/data-transfer quality-of-protection level used when Kerberos is enabled. const PROTECTION_PRIVACY: &str = "privacy"; +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build Kerberos secret volume source"))] + BuildKerberosSecretVolumeSource { + source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, + }, + + #[snafu(display("failed to build TLS secret volume source"))] + BuildTlsSecretVolumeSource { + source: stackable_operator::builder::pod::volume::SecretOperatorVolumeSourceBuilderError, + }, + + #[snafu(display("failed to add needed volume"))] + AddVolume { source: builder::pod::Error }, +} + /// The `hbase-site.xml` Kerberos properties for `cluster`, gated on Kerberos being enabled /// (empty when disabled). Derived in the build step from the validated cluster. pub fn hbase_site_kerberos_config( @@ -209,16 +229,15 @@ pub fn kerberos_ssl_client_settings() -> BTreeMap { /// /// # Panics /// -/// Panics if the volumes or volume mounts cannot be added to the builders. Only call this -/// on builders whose volume names and mount paths are still distinct from the ones added -/// here. +/// Panics if the volume mounts cannot be added to the container builder. Only call this on a +/// container builder whose mount paths are still distinct from the ones added here. pub fn add_kerberos_pod_config( cluster: &ValidatedCluster, metrics_service_name: &str, cb: &mut ContainerBuilder, pb: &mut PodBuilder, requested_secret_lifetime: Duration, -) { +) -> Result<(), Error> { if let Some(kerberos_secret_class) = &cluster.cluster_config.kerberos_secret_class { // Mount keytab let kerberos_secret_operator_volume = SecretOperatorVolumeSourceBuilder::new( @@ -230,13 +249,13 @@ pub fn add_kerberos_pod_config( .with_kerberos_service_name(kerberos_service_name()) .with_kerberos_service_name("HTTP") .build() - .expect("The annotation keys are static and annotation values cannot be invalid."); + .context(BuildKerberosSecretVolumeSourceSnafu)?; pb.add_volume( VolumeBuilder::new(&*KERBEROS_VOLUME_NAME) .ephemeral(kerberos_secret_operator_volume) .build(), ) - .expect("The volume names are statically defined and there should be no duplicates."); + .context(AddVolumeSnafu)?; cb.add_volume_mount(&*KERBEROS_VOLUME_NAME, STACKABLE_KERBEROS_DIR) .expect("The mount paths are statically defined and there should be no duplicates."); } @@ -260,16 +279,15 @@ pub fn add_kerberos_pod_config( .with_tls_pkcs12_password(TLS_STORE_PASSWORD) .with_auto_tls_cert_lifetime(requested_secret_lifetime) .build() - .expect( - "The annotation keys are static and annotation values cannot be invalid.", - ), + .context(BuildTlsSecretVolumeSourceSnafu)?, ) .build(), ) - .expect("The volume names are statically defined and there should be no duplicates."); + .context(AddVolumeSnafu)?; cb.add_volume_mount(&*TLS_STORE_VOLUME_NAME, TLS_STORE_DIR) .expect("The mount paths are statically defined and there should be no duplicates."); } + Ok(()) } /// The environment variables the Kerberos configuration requires on the HBase container, or an diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index 551f7a55..396a38d9 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -21,7 +21,7 @@ use crate::{ ValidatedCluster, build::resource::{ config_map::{self, build_rolegroup_config_map}, - discovery::build_discovery_config_map, + discovery::{self, build_discovery_config_map}, pdb::build_pdb, rbac::{build_role_binding, build_service_account}, service::{build_rolegroup_metrics_service, build_rolegroup_service}, @@ -65,6 +65,9 @@ pub enum Error { hbase_role: HbaseRole, role_group: RoleGroupName, }, + + #[snafu(display("failed to build discovery ConfigMap"))] + Discovery { source: discovery::Error }, } /// Builds every Kubernetes resource for the given validated cluster. @@ -118,7 +121,7 @@ pub fn build( // The role-level discovery ConfigMap advertises the cluster's connection information; it is // deterministic (derived only from the validated cluster and static cluster info). - config_maps.push(build_discovery_config_map(cluster, cluster_info)); + config_maps.push(build_discovery_config_map(cluster, cluster_info).context(DiscoverySnafu)?); Ok(KubernetesResources { stateful_sets, diff --git a/rust/operator-binary/src/controller/build/resource/config_map.rs b/rust/operator-binary/src/controller/build/resource/config_map.rs index 8e170489..08d5a6f0 100644 --- a/rust/operator-binary/src/controller/build/resource/config_map.rs +++ b/rust/operator-binary/src/controller/build/resource/config_map.rs @@ -38,6 +38,13 @@ pub enum Error { source: PropertiesWriterError, role_group: String, }, + + #[snafu(display("cannot build config map for role {role:?} and role group {role_group:?}"))] + Assemble { + source: stackable_operator::builder::configmap::Error, + role: String, + role_group: String, + }, } type Result = std::result::Result; @@ -141,7 +148,8 @@ pub fn build_rolegroup_config_map( ); } - Ok(builder - .build() - .expect("The ConfigMap metadata is set in this function.")) + builder.build().with_context(|_| AssembleSnafu { + role: role.to_string(), + role_group: role_group_name.to_string(), + }) } diff --git a/rust/operator-binary/src/controller/build/resource/discovery.rs b/rust/operator-binary/src/controller/build/resource/discovery.rs index 486f846f..018b7ac3 100644 --- a/rust/operator-binary/src/controller/build/resource/discovery.rs +++ b/rust/operator-binary/src/controller/build/resource/discovery.rs @@ -1,5 +1,6 @@ //! Build the discovery `ConfigMap` for the HbaseCluster. +use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::configmap::ConfigMapBuilder, k8s_openapi::api::core::v1::ConfigMap, utils::cluster_info::KubernetesClusterInfo, v2::config_file_writer::to_hadoop_xml, @@ -16,11 +17,21 @@ use crate::{ crd::HbaseRole, }; +type Result = std::result::Result; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build ConfigMap"))] + BuildConfigMap { + source: stackable_operator::builder::configmap::Error, + }, +} + /// Creates a discovery config map containing the `hbase-site.xml` for clients. pub fn build_discovery_config_map( cluster: &ValidatedCluster, cluster_info: &KubernetesClusterInfo, -) -> ConfigMap { +) -> Result { let cluster_config = &cluster.cluster_config; let mut hbase_site = cluster_config @@ -45,5 +56,5 @@ pub fn build_discovery_config_map( to_hadoop_xml(hbase_site.iter()), ) .build() - .expect("The ConfigMap metadata is set in this function.") + .context(BuildConfigMapSnafu) } diff --git a/rust/operator-binary/src/controller/build/resource/listener.rs b/rust/operator-binary/src/controller/build/resource/listener.rs index 385fa8da..deb9c6fa 100644 --- a/rust/operator-binary/src/controller/build/resource/listener.rs +++ b/rust/operator-binary/src/controller/build/resource/listener.rs @@ -2,8 +2,12 @@ use std::str::FromStr; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ - builder::pod::volume::{ListenerOperatorVolumeSourceBuilder, ListenerReference, VolumeBuilder}, + builder::pod::volume::{ + ListenerOperatorVolumeSourceBuilder, ListenerOperatorVolumeSourceBuilderError, + ListenerReference, VolumeBuilder, + }, constant, k8s_openapi::api::core::v1::{PersistentVolumeClaim, Volume}, kvp::Labels, @@ -20,14 +24,24 @@ use crate::crd::{AnyServiceConfig, HbaseRole, LISTENER_VOLUME_NAME}; constant!(LISTENER_PVC_NAME: PersistentVolumeClaimName = LISTENER_VOLUME_NAME); +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build listener volume"))] + BuildListenerVolume { + source: ListenerOperatorVolumeSourceBuilderError, + }, +} + +type Result = std::result::Result; + /// The ephemeral listener [`Volume`] for the masters and region servers, or `None` for the rest /// servers (which use a [`PersistentVolumeClaim`] instead, see [`build_listener_pvc`]). pub fn build_listener_volume( role: &HbaseRole, merged_config: &AnyServiceConfig, recommended_labels: &Labels, -) -> Option { - match role { +) -> Result> { + let volume = match role { // Master and regionservers should use ephemeral listener volumes // since clients pull the latest address from ZooKeeper HbaseRole::Master | HbaseRole::RegionServer => Some( @@ -46,14 +60,13 @@ pub fn build_listener_volume( recommended_labels, ) .build_ephemeral() - .expect( - "The annotation keys are static and annotation values cannot be invalid.", - ), + .context(BuildListenerVolumeSnafu)?, ) .build(), ), HbaseRole::RestServer => None, - } + }; + Ok(volume) } /// The listener [`PersistentVolumeClaim`] template for the rest servers, or `None` for the masters diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 2d83ff95..3d50b39c 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -6,6 +6,7 @@ use indoc::formatdoc; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ + self, meta::ObjectMetaBuilder, pod::{PodBuilder, security::PodSecurityContextBuilder}, }, @@ -78,8 +79,17 @@ pub enum Error { #[snafu(display("missing secret lifetime"))] MissingSecretLifetime, + #[snafu(display("failed to add kerberos config"))] + AddKerberosConfig { source: kerberos::Error }, + #[snafu(display("failed to configure graceful shutdown"))] GracefulShutdown { source: graceful_shutdown::Error }, + + #[snafu(display("failed to add needed volume"))] + AddVolume { source: builder::pod::Error }, + + #[snafu(display("failed to build listener volume"))] + ListenerVolume { source: super::listener::Error }, } type Result = std::result::Result; @@ -213,7 +223,7 @@ pub fn build_rolegroup_statefulset( }), ..Default::default() }) - .expect("The volume names are statically defined and there should be no duplicates.") + .context(AddVolumeSnafu)? .add_volume(Volume { name: HDFS_DISCOVERY_VOLUME_NAME.to_string(), config_map: Some(ConfigMapVolumeSource { @@ -222,14 +232,14 @@ pub fn build_rolegroup_statefulset( }), ..Default::default() }) - .expect("The volume names are statically defined and there should be no duplicates.") + .context(AddVolumeSnafu)? .add_empty_dir_volume( &*LOG_VOLUME_NAME, Some(product_logging::framework::calculate_log_volume_size_limit( &[MAX_HBASE_LOG_FILES_SIZE], )), ) - .expect("The volume names are statically defined and there should be no duplicates.") + .context(AddVolumeSnafu)? .service_account_name( cluster .cluster_resource_names() @@ -260,7 +270,7 @@ pub fn build_rolegroup_statefulset( }), ..Volume::default() }) - .expect("The volume names are statically defined and there should be no duplicates."); + .context(AddVolumeSnafu)?; add_graceful_shutdown_config(merged_config, &mut pod_builder).context(GracefulShutdownSnafu)?; if cluster.has_kerberos_enabled() { @@ -272,7 +282,8 @@ pub fn build_rolegroup_statefulset( merged_config .requested_secret_lifetime() .context(MissingSecretLifetimeSnafu)?, - ); + ) + .context(AddKerberosConfigSnafu)?; } pod_builder.add_container(hbase_container.build()); @@ -303,10 +314,11 @@ pub fn build_rolegroup_statefulset( if let Some(listener_volume) = super::listener::build_listener_volume(hbase_role, merged_config, &recommended_labels) + .context(ListenerVolumeSnafu)? { pod_builder .add_volume(listener_volume) - .expect("The volume names are statically defined and there should be no duplicates."); + .context(AddVolumeSnafu)?; }; let mut pod_template = pod_builder.build_template();