From 37dcb3091b3f96dc26ea905748886d97f84e4027 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 12:23:23 +0200 Subject: [PATCH 1/5] refactor: pass ServiceAccount name to StatefulSet builder --- .../src/controller/build/resource/statefulset.rs | 10 +++------- rust/operator-binary/src/hbase_controller.rs | 7 ++++++- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/rust/operator-binary/src/controller/build/resource/statefulset.rs b/rust/operator-binary/src/controller/build/resource/statefulset.rs index 06271f3e..1ae98a9c 100644 --- a/rust/operator-binary/src/controller/build/resource/statefulset.rs +++ b/rust/operator-binary/src/controller/build/resource/statefulset.rs @@ -15,14 +15,10 @@ use stackable_operator::{ DeepMerge, api::{ apps::v1::{StatefulSet, StatefulSetSpec}, - core::v1::{ - ConfigMapVolumeSource, ContainerPort, Probe, ServiceAccount, TCPSocketAction, - Volume, - }, + core::v1::{ConfigMapVolumeSource, ContainerPort, Probe, TCPSocketAction, Volume}, }, apimachinery::pkg::{apis::meta::v1::LabelSelector, util::intstr::IntOrString}, }, - kube::ResourceExt, product_logging, v2::{ builder::pod::container::{EnvVarName, EnvVarSet, new_container_builder}, @@ -110,7 +106,7 @@ pub fn build_rolegroup_statefulset( hbase_role: &HbaseRole, role_group_name: &RoleGroupName, validated_rg_config: &HbaseRoleGroupConfig, - service_account: &ServiceAccount, + service_account_name: &str, ) -> Result { let resolved_product_image = &cluster.image; let merged_config = &validated_rg_config.config.config; @@ -243,7 +239,7 @@ pub fn build_rolegroup_statefulset( )), ) .context(AddVolumeSnafu)? - .service_account_name(service_account.name_any()) + .service_account_name(service_account_name) .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); // The HBase container's log config ConfigMap: either the operator-generated one (the diff --git a/rust/operator-binary/src/hbase_controller.rs b/rust/operator-binary/src/hbase_controller.rs index 666d58d7..bd2ad761 100644 --- a/rust/operator-binary/src/hbase_controller.rs +++ b/rust/operator-binary/src/hbase_controller.rs @@ -13,6 +13,7 @@ use stackable_operator::{ cluster_resources::ClusterResourceApplyStrategy, commons::rbac::build_rbac_resources, kube::{ + ResourceExt, core::{DeserializeGuard, error_boundary}, runtime::controller::Action, }, @@ -199,6 +200,10 @@ pub async fn reconcile_hbase( .await .context(ApplyRoleBindingSnafu)?; + // The ServiceAccount name is deterministic on the built object, so the build step does not + // depend on the applied ServiceAccount. + let service_account_name = rbac_sa.name_any(); + let mut ss_cond_builder = StatefulSetConditionBuilder::default(); for (hbase_role, role_group_configs) in &validated_cluster.role_group_configs { @@ -221,7 +226,7 @@ pub async fn reconcile_hbase( hbase_role, role_group_name, validated_rg_config, - &rbac_sa, + &service_account_name, ) .with_context(|_| BuildRoleGroupStatefulSetSnafu { role_group: role_group_name.clone(), From fc5daf4f099a9533df2a40594f2b699cdfa95e3a Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 12:30:27 +0200 Subject: [PATCH 2/5] refactor: introduce the build aggregator --- .../src/controller/build/mod.rs | 97 +++++++++++- rust/operator-binary/src/controller/mod.rs | 20 ++- rust/operator-binary/src/hbase_controller.rs | 139 +++++------------- 3 files changed, 153 insertions(+), 103 deletions(-) diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index 41875e98..c95ad19b 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -3,12 +3,107 @@ use std::str::FromStr; -use stackable_operator::v2::types::operator::RoleGroupName; +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + utils::cluster_info::KubernetesClusterInfo, v2::types::operator::RoleGroupName, +}; + +use crate::controller::{ + KubernetesResources, ValidatedCluster, + build::resource::{ + config_map::{self, build_rolegroup_config_map}, + pdb::build_pdb, + service::{build_rolegroup_metrics_service, build_rolegroup_service}, + statefulset::{self, build_rolegroup_statefulset}, + }, +}; // Placeholder role-group name used for the recommended labels of the role-level discovery // `ConfigMap` (which is not tied to a single role group). stackable_operator::constant!(pub(crate) PLACEHOLDER_DISCOVERY_ROLE_GROUP: RoleGroupName = "discovery"); +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build ConfigMap for role group {role_group}"))] + ConfigMap { + source: config_map::Error, + role_group: RoleGroupName, + }, + + #[snafu(display("failed to build StatefulSet for role group {role_group}"))] + StatefulSet { + source: statefulset::Error, + role_group: RoleGroupName, + }, +} + +/// Builds every Kubernetes resource for the given validated cluster. +/// +/// Does not need a Kubernetes client: every reference to another Kubernetes resource is already +/// dereferenced and validated by this point, so the errors returned here are resource-assembly +/// failures only. `cluster_info` is static cluster metadata (not a client call), and +/// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under +/// (RBAC resources are built and applied separately, in the reconcile step). +/// +/// The role-level discovery `ConfigMap` is applied separately in the reconcile step and is not +/// part of this bundle. +pub fn build( + cluster: &ValidatedCluster, + cluster_info: &KubernetesClusterInfo, + service_account_name: &str, +) -> Result { + let mut stateful_sets = vec![]; + let mut services = vec![]; + let mut config_maps = vec![]; + let mut pod_disruption_budgets = vec![]; + + for (hbase_role, role_group_configs) in &cluster.role_group_configs { + for (role_group_name, rg_config) in role_group_configs { + services.push(build_rolegroup_service( + cluster, + hbase_role, + role_group_name, + )); + services.push(build_rolegroup_metrics_service( + cluster, + hbase_role, + role_group_name, + )); + config_maps.push( + build_rolegroup_config_map(cluster, cluster_info, hbase_role, role_group_name) + .context(ConfigMapSnafu { + role_group: role_group_name.clone(), + })?, + ); + stateful_sets.push( + build_rolegroup_statefulset( + cluster, + hbase_role, + role_group_name, + rg_config, + service_account_name, + ) + .context(StatefulSetSnafu { + role_group: role_group_name.clone(), + })?, + ); + } + + if let Some(role_config) = cluster.role_configs.get(hbase_role) + && let Some(pdb) = build_pdb(&role_config.pdb, cluster, hbase_role) + { + pod_disruption_budgets.push(pdb); + } + } + + Ok(KubernetesResources { + stateful_sets, + services, + config_maps, + pod_disruption_budgets, + }) +} + pub mod graceful_shutdown; pub mod jvm; pub mod kerberos; diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index e2ffc079..2cb2c9e6 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -10,7 +10,14 @@ pub use stackable_operator::v2::types::operator::RoleGroupName; use stackable_operator::{ builder::meta::ObjectMetaBuilder, commons::product_image_selection::ResolvedProductImage, - k8s_openapi::apimachinery::pkg::apis::meta::v1::ObjectMeta, + k8s_openapi::{ + api::{ + apps::v1::StatefulSet, + core::v1::{ConfigMap, Service}, + policy::v1::PodDisruptionBudget, + }, + apimachinery::pkg::apis::meta::v1::ObjectMeta, + }, kube::Resource, kvp::Labels, v2::{ @@ -51,6 +58,17 @@ pub(crate) fn controller_name() -> ControllerName { .expect("the controller name is a valid label value") } +/// The complete set of Kubernetes resources built for a [`ValidatedCluster`], ready to be applied. +/// +/// hbase exposes its listeners as volume/PVC sources inside the `StatefulSet` rather than as +/// top-level `Listener` objects, so (unlike some sibling operators) there is no `listeners` field. +pub struct KubernetesResources { + pub stateful_sets: Vec, + pub services: Vec, + pub config_maps: Vec, + pub pod_disruption_budgets: Vec, +} + /// The validated cluster: proves that config merging and validation succeeded for /// every role and role group before any resources are created. #[derive(Clone, Debug)] diff --git a/rust/operator-binary/src/hbase_controller.rs b/rust/operator-binary/src/hbase_controller.rs index bd2ad761..5f5e3632 100644 --- a/rust/operator-binary/src/hbase_controller.rs +++ b/rust/operator-binary/src/hbase_controller.rs @@ -30,14 +30,7 @@ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ controller::{ - RoleGroupName, - build::resource::{ - config_map::build_rolegroup_config_map, - discovery::build_discovery_config_map, - pdb::build_pdb, - service::{build_rolegroup_metrics_service, build_rolegroup_service}, - statefulset::build_rolegroup_statefulset, - }, + build::{self, resource::discovery::build_discovery_config_map}, controller_name, operator_name, product_name, }, crd::{APP_NAME, HbaseClusterStatus, OPERATOR_NAME, v1alpha1}, @@ -74,37 +67,11 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to apply Service for role group {role_group}"))] - ApplyRoleGroupService { - source: stackable_operator::cluster_resources::Error, - role_group: RoleGroupName, - }, - - #[snafu(display("failed to build rolegroup ConfigMap"))] - BuildRolegroupConfigMap { - source: crate::controller::build::resource::config_map::Error, - }, + #[snafu(display("failed to build cluster resources"))] + BuildResources { source: build::Error }, - #[snafu(display("failed to apply ConfigMap for role group {role_group}"))] - ApplyRoleGroupConfig { - source: stackable_operator::cluster_resources::Error, - role_group: RoleGroupName, - }, - - #[snafu(display("failed to build StatefulSet for role group {role_group}"))] - BuildRoleGroupStatefulSet { - source: crate::controller::build::resource::statefulset::Error, - role_group: RoleGroupName, - }, - - #[snafu(display("failed to apply StatefulSet for role group {role_group}"))] - ApplyRoleGroupStatefulSet { - source: stackable_operator::cluster_resources::Error, - role_group: RoleGroupName, - }, - - #[snafu(display("failed to apply PodDisruptionBudget"))] - ApplyPdb { + #[snafu(display("failed to apply cluster resource"))] + ApplyResource { source: stackable_operator::cluster_resources::Error, }, @@ -204,73 +171,43 @@ pub async fn reconcile_hbase( // depend on the applied ServiceAccount. let service_account_name = rbac_sa.name_any(); + let resources = build::build( + &validated_cluster, + &client.kubernetes_cluster_info, + &service_account_name, + ) + .context(BuildResourcesSnafu)?; + let mut ss_cond_builder = StatefulSetConditionBuilder::default(); - for (hbase_role, role_group_configs) in &validated_cluster.role_group_configs { - for (role_group_name, validated_rg_config) in role_group_configs { - let rg_service = - build_rolegroup_service(&validated_cluster, hbase_role, role_group_name); - - let rg_metrics_service = - build_rolegroup_metrics_service(&validated_cluster, hbase_role, role_group_name); - - let rg_configmap = build_rolegroup_config_map( - &validated_cluster, - &client.kubernetes_cluster_info, - hbase_role, - role_group_name, - ) - .context(BuildRolegroupConfigMapSnafu)?; - let rg_statefulset = build_rolegroup_statefulset( - &validated_cluster, - hbase_role, - role_group_name, - validated_rg_config, - &service_account_name, - ) - .with_context(|_| BuildRoleGroupStatefulSetSnafu { - role_group: role_group_name.clone(), - })?; - cluster_resources - .add(client, rg_service) - .await - .with_context(|_| ApplyRoleGroupServiceSnafu { - role_group: role_group_name.clone(), - })?; - cluster_resources - .add(client, rg_metrics_service) - .await - .with_context(|_| ApplyRoleGroupServiceSnafu { - role_group: role_group_name.clone(), - })?; - cluster_resources - .add(client, rg_configmap) - .await - .with_context(|_| ApplyRoleGroupConfigSnafu { - role_group: role_group_name.clone(), - })?; - - // Note: The StatefulSet needs to be applied after all ConfigMaps and Secrets it mounts - // to prevent unnecessary Pod restarts. - // See https://github.com/stackabletech/commons-operator/issues/111 for details. - ss_cond_builder.add( - cluster_resources - .add(client, rg_statefulset) - .await - .with_context(|_| ApplyRoleGroupStatefulSetSnafu { - role_group: role_group_name.clone(), - })?, - ); - } - - if let Some(role_config) = validated_cluster.role_configs.get(hbase_role) - && let Some(pdb) = build_pdb(&role_config.pdb, &validated_cluster, hbase_role) - { + // Apply order: everything before the StatefulSets, StatefulSets last. A changed ConfigMap or + // Secret a Pod mounts must exist before the Pod restarts, otherwise the Pod restarts again + // unnecessarily. See https://github.com/stackabletech/commons-operator/issues/111 for details. + for service in resources.services { + cluster_resources + .add(client, service) + .await + .context(ApplyResourceSnafu)?; + } + for config_map in resources.config_maps { + cluster_resources + .add(client, config_map) + .await + .context(ApplyResourceSnafu)?; + } + for pdb in resources.pod_disruption_budgets { + cluster_resources + .add(client, pdb) + .await + .context(ApplyResourceSnafu)?; + } + for statefulset in resources.stateful_sets { + ss_cond_builder.add( cluster_resources - .add(client, pdb) + .add(client, statefulset) .await - .context(ApplyPdbSnafu)?; - } + .context(ApplyResourceSnafu)?, + ); } // Discovery CM will fail to build until the rest of the cluster has been deployed, so do it last From 42f00763490fa96566a3b9c8f5f7c6b7f7c2e954 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 12:58:12 +0200 Subject: [PATCH 3/5] refactor: fold the discovery ConfigMap into the build aggregator --- .../src/controller/build/mod.rs | 11 +++++--- rust/operator-binary/src/hbase_controller.rs | 25 +------------------ 2 files changed, 9 insertions(+), 27 deletions(-) diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index c95ad19b..36700ed1 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -12,6 +12,7 @@ use crate::controller::{ KubernetesResources, ValidatedCluster, build::resource::{ config_map::{self, build_rolegroup_config_map}, + discovery::{self, build_discovery_config_map}, pdb::build_pdb, service::{build_rolegroup_metrics_service, build_rolegroup_service}, statefulset::{self, build_rolegroup_statefulset}, @@ -35,6 +36,9 @@ pub enum Error { source: statefulset::Error, role_group: RoleGroupName, }, + + #[snafu(display("failed to build discovery ConfigMap"))] + Discovery { source: discovery::Error }, } /// Builds every Kubernetes resource for the given validated cluster. @@ -44,9 +48,6 @@ pub enum Error { /// failures only. `cluster_info` is static cluster metadata (not a client call), and /// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under /// (RBAC resources are built and applied separately, in the reconcile step). -/// -/// The role-level discovery `ConfigMap` is applied separately in the reconcile step and is not -/// part of this bundle. pub fn build( cluster: &ValidatedCluster, cluster_info: &KubernetesClusterInfo, @@ -96,6 +97,10 @@ 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)?); + Ok(KubernetesResources { stateful_sets, services, diff --git a/rust/operator-binary/src/hbase_controller.rs b/rust/operator-binary/src/hbase_controller.rs index 5f5e3632..61d6ba46 100644 --- a/rust/operator-binary/src/hbase_controller.rs +++ b/rust/operator-binary/src/hbase_controller.rs @@ -29,10 +29,7 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - controller::{ - build::{self, resource::discovery::build_discovery_config_map}, - controller_name, operator_name, product_name, - }, + controller::{build, controller_name, operator_name, product_name}, crd::{APP_NAME, HbaseClusterStatus, OPERATOR_NAME, v1alpha1}, }; @@ -75,16 +72,6 @@ pub enum Error { source: stackable_operator::cluster_resources::Error, }, - #[snafu(display("failed to build discovery configmap"))] - BuildDiscoveryConfigMap { - source: crate::controller::build::resource::discovery::Error, - }, - - #[snafu(display("failed to apply discovery configmap"))] - ApplyDiscoveryConfigMap { - source: stackable_operator::cluster_resources::Error, - }, - #[snafu(display("failed to update status"))] ApplyStatus { source: stackable_operator::client::Error, @@ -210,16 +197,6 @@ pub async fn reconcile_hbase( ); } - // Discovery CM will fail to build until the rest of the cluster has been deployed, so do it last - // so that failure won't inhibit the rest of the cluster from booting up. - let discovery_cm = - build_discovery_config_map(&validated_cluster, &client.kubernetes_cluster_info) - .context(BuildDiscoveryConfigMapSnafu)?; - cluster_resources - .add(client, discovery_cm) - .await - .context(ApplyDiscoveryConfigMapSnafu)?; - let cluster_operation_cond_builder = ClusterOperationsConditionBuilder::new(&hbase.spec.cluster_operation); From e14ceb665816c1648ffa02639ba0e78d612e9f37 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Fri, 10 Jul 2026 13:15:36 +0200 Subject: [PATCH 4/5] test: cover the build aggregator's resource set --- .../src/controller/build/mod.rs | 63 +++++++++++++++++++ rust/operator-binary/src/test_utils.rs | 13 +++- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index 36700ed1..3124673b 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -117,3 +117,66 @@ pub mod properties; pub mod region_mover; pub mod resource; pub mod role; + +#[cfg(test)] +mod tests { + use stackable_operator::kube::Resource; + + use super::build; + use crate::test_utils; + + /// Collects the `.metadata.name`s of the given resources, sorted for stable comparison. + fn sorted_names(resources: &[impl Resource]) -> Vec<&str> { + let mut names: Vec<&str> = resources + .iter() + .filter_map(|resource| resource.meta().name.as_deref()) + .collect(); + names.sort(); + names + } + + #[test] + fn build_produces_expected_resource_names() { + let cluster = test_utils::validated_cluster(); + let cluster_info = test_utils::cluster_info(); + let resources = + build(&cluster, &cluster_info, "hbase-serviceaccount").expect("build succeeds"); + + // One StatefulSet per role group (one `default` group for each of the three roles). + assert_eq!( + sorted_names(&resources.stateful_sets), + [ + "hbase-master-default", + "hbase-regionserver-default", + "hbase-restserver-default", + ] + ); + // One headless and one metrics Service per role group. + assert_eq!( + sorted_names(&resources.services), + [ + "hbase-master-default-headless", + "hbase-master-default-metrics", + "hbase-regionserver-default-headless", + "hbase-regionserver-default-metrics", + "hbase-restserver-default-headless", + "hbase-restserver-default-metrics", + ] + ); + // One ConfigMap per role group plus the cluster-wide discovery ConfigMap (`hbase`). + assert_eq!( + sorted_names(&resources.config_maps), + [ + "hbase", + "hbase-master-default", + "hbase-regionserver-default", + "hbase-restserver-default", + ] + ); + // A default PodDisruptionBudget per role. + assert_eq!( + sorted_names(&resources.pod_disruption_budgets), + ["hbase-master", "hbase-regionserver", "hbase-restserver"] + ); + } +} diff --git a/rust/operator-binary/src/test_utils.rs b/rust/operator-binary/src/test_utils.rs index ad888f71..998e4eb3 100644 --- a/rust/operator-binary/src/test_utils.rs +++ b/rust/operator-binary/src/test_utils.rs @@ -7,7 +7,10 @@ use std::str::FromStr; -use stackable_operator::v2::types::operator::RoleGroupName; +use stackable_operator::{ + commons::networking::DomainName, utils::cluster_info::KubernetesClusterInfo, + v2::types::operator::RoleGroupName, +}; use crate::{ controller::{ @@ -80,6 +83,14 @@ pub fn role_group_name(name: &str) -> RoleGroupName { RoleGroupName::from_str(name).expect("valid role group name") } +/// A fixed [`KubernetesClusterInfo`] (`cluster.local` domain) for builders that need cluster +/// metadata such as the discovery `ConfigMap` and Kerberos principals. +pub fn cluster_info() -> KubernetesClusterInfo { + KubernetesClusterInfo { + cluster_domain: DomainName::from_str("cluster.local").expect("valid cluster domain"), + } +} + /// The merged [`AnyServiceConfig`] for the given `role` and `role_group`. pub fn merged_config_for<'a>( validated_cluster: &'a ValidatedCluster, From 29d45fb57595828d4fd7371d7bd8c175147d90da Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Tue, 14 Jul 2026 12:01:15 +0200 Subject: [PATCH 5/5] fix: add role to error variants --- .../src/controller/build/mod.rs | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs index 3124673b..1c5dac7a 100644 --- a/rust/operator-binary/src/controller/build/mod.rs +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -8,15 +8,18 @@ use stackable_operator::{ utils::cluster_info::KubernetesClusterInfo, v2::types::operator::RoleGroupName, }; -use crate::controller::{ - KubernetesResources, ValidatedCluster, - build::resource::{ - config_map::{self, build_rolegroup_config_map}, - discovery::{self, build_discovery_config_map}, - pdb::build_pdb, - service::{build_rolegroup_metrics_service, build_rolegroup_service}, - statefulset::{self, build_rolegroup_statefulset}, +use crate::{ + controller::{ + KubernetesResources, ValidatedCluster, + build::resource::{ + config_map::{self, build_rolegroup_config_map}, + discovery::{self, build_discovery_config_map}, + pdb::build_pdb, + service::{build_rolegroup_metrics_service, build_rolegroup_service}, + statefulset::{self, build_rolegroup_statefulset}, + }, }, + crd::HbaseRole, }; // Placeholder role-group name used for the recommended labels of the role-level discovery @@ -25,15 +28,17 @@ stackable_operator::constant!(pub(crate) PLACEHOLDER_DISCOVERY_ROLE_GROUP: RoleG #[derive(Snafu, Debug)] pub enum Error { - #[snafu(display("failed to build ConfigMap for role group {role_group}"))] + #[snafu(display("failed to build ConfigMap for role {hbase_role} role group {role_group}"))] ConfigMap { source: config_map::Error, + hbase_role: HbaseRole, role_group: RoleGroupName, }, - #[snafu(display("failed to build StatefulSet for role group {role_group}"))] + #[snafu(display("failed to build StatefulSet for role {hbase_role} role group {role_group}"))] StatefulSet { source: statefulset::Error, + hbase_role: HbaseRole, role_group: RoleGroupName, }, @@ -72,7 +77,8 @@ pub fn build( )); config_maps.push( build_rolegroup_config_map(cluster, cluster_info, hbase_role, role_group_name) - .context(ConfigMapSnafu { + .with_context(|_| ConfigMapSnafu { + hbase_role: hbase_role.clone(), role_group: role_group_name.clone(), })?, ); @@ -84,7 +90,8 @@ pub fn build( rg_config, service_account_name, ) - .context(StatefulSetSnafu { + .with_context(|_| StatefulSetSnafu { + hbase_role: hbase_role.clone(), role_group: role_group_name.clone(), })?, );