Skip to content

Commit f8b3fb4

Browse files
committed
factor: add infallible rbac functions
1 parent ade8a87 commit f8b3fb4

8 files changed

Lines changed: 255 additions & 89 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,4 @@ tracing = "0.1"
3232

3333
[patch."https://github.com/stackabletech/operator-rs.git"]
3434
# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "smooth-operator" }
35-
# stackable-operator = { path = "../operator-rs/crates/stackable-operator" }
35+
stackable-operator = { path = "../operator-rs/crates/stackable-operator" }

rust/operator-binary/src/controller.rs

Lines changed: 55 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ use stackable_operator::{
1616
commons::{
1717
affinity::StackableAffinity,
1818
product_image_selection::ResolvedProductImage,
19-
rbac::build_rbac_resources,
2019
resources::{NoRuntimeLimits, Resources},
2120
},
2221
crd::{listener::v1alpha1::Listener, s3},
2322
database_connections::drivers::jdbc::JdbcDatabaseConnectionDetails,
2423
k8s_openapi::api::{
2524
apps::v1::StatefulSet,
26-
core::v1::{ConfigMap, Service},
25+
core::v1::{ConfigMap, Service, ServiceAccount},
2726
policy::v1::PodDisruptionBudget,
27+
rbac::v1::RoleBinding,
2828
},
2929
kube::{
30-
Resource, ResourceExt,
30+
Resource,
3131
api::ObjectMeta,
3232
core::{DeserializeGuard, error_boundary},
3333
runtime::controller::Action,
@@ -44,6 +44,7 @@ use stackable_operator::{
4444
cluster_resources::cluster_resources_new,
4545
kvp::label::{recommended_labels, role_group_selector},
4646
role_group_utils::ResourceNames,
47+
role_utils,
4748
types::{
4849
kubernetes::{ListenerClassName, ListenerName, SecretClassName},
4950
operator::{ControllerName, OperatorName, ProductName, ProductVersion, RoleName},
@@ -54,7 +55,7 @@ use strum::EnumDiscriminants;
5455

5556
use crate::{
5657
OPERATOR_NAME,
57-
controller::build::resource::discovery,
58+
controller::build::{UNVERSIONED_PRODUCT_VERSION, resource::discovery},
5859
crd::{APP_NAME, HdfsConnection, HiveClusterStatus, HiveRole, MetaStoreConfig, v1alpha1},
5960
};
6061

@@ -95,27 +96,6 @@ pub enum Error {
9596
source: stackable_operator::cluster_resources::Error,
9697
},
9798

98-
#[snafu(display("failed to patch service account"))]
99-
ApplyServiceAccount {
100-
source: stackable_operator::cluster_resources::Error,
101-
},
102-
103-
#[snafu(display("failed to patch role binding"))]
104-
ApplyRoleBinding {
105-
source: stackable_operator::cluster_resources::Error,
106-
},
107-
108-
#[snafu(display("failed to build RBAC resources"))]
109-
BuildRbacResources {
110-
source: stackable_operator::commons::rbac::Error,
111-
},
112-
113-
#[snafu(display("failed to get required Labels"))]
114-
GetRequiredLabels {
115-
source:
116-
stackable_operator::kvp::KeyValuePairError<stackable_operator::kvp::LabelValueError>,
117-
},
118-
11999
#[snafu(display("HiveCluster object is invalid"))]
120100
InvalidHiveCluster {
121101
source: error_boundary::InvalidObject,
@@ -253,6 +233,15 @@ impl ValidatedCluster {
253233
.expect("the metastore role name is a valid role name")
254234
}
255235

236+
/// Type-safe names for the per-cluster RBAC resources: the ServiceAccount shared by all
237+
/// Pods, its (namespaced) RoleBinding, and the operator-deployed ClusterRole it binds.
238+
pub fn rbac_resource_names(&self) -> role_utils::ResourceNames {
239+
role_utils::ResourceNames {
240+
cluster_name: self.name.clone(),
241+
product_name: product_name(),
242+
}
243+
}
244+
256245
/// Type-safe names for the resources of a given role group.
257246
pub(crate) fn resource_names(&self, role_group_name: &RoleGroupName) -> ResourceNames {
258247
ResourceNames {
@@ -262,10 +251,31 @@ impl ValidatedCluster {
262251
}
263252
}
264253

254+
/// Recommended labels for a resource that is not tied to a concrete,
255+
/// using a free-form role/role-group label value.
256+
pub fn recommended_labels_for(
257+
&self,
258+
role_name: &RoleName,
259+
role_group_name: &RoleGroupName,
260+
) -> Labels {
261+
self.recommended_labels_with(&self.product_version, role_name, role_group_name)
262+
}
263+
264+
/// Recommended labels with the constant [`UNVERSIONED_PRODUCT_VERSION`], for PVC templates
265+
/// that cannot be modified after deployment (keeps the labels stable across version upgrades).
266+
pub fn unversioned_recommended_labels(&self, role_group_name: &RoleGroupName) -> Labels {
267+
self.recommended_labels_with(
268+
&UNVERSIONED_PRODUCT_VERSION,
269+
&Self::role_name(),
270+
role_group_name,
271+
)
272+
}
273+
265274
/// Recommended labels for a role-group resource, using the given product version.
266-
fn recommended_labels_for(
275+
fn recommended_labels_with(
267276
&self,
268277
product_version: &ProductVersion,
278+
role_name: &RoleName,
269279
role_group_name: &RoleGroupName,
270280
) -> Labels {
271281
recommended_labels(
@@ -274,14 +284,14 @@ impl ValidatedCluster {
274284
product_version,
275285
&operator_name(),
276286
&controller_name(),
277-
&Self::role_name(),
287+
role_name,
278288
role_group_name,
279289
)
280290
}
281291

282292
/// Recommended labels for a role-group resource.
283293
pub fn recommended_labels(&self, role_group_name: &RoleGroupName) -> Labels {
284-
self.recommended_labels_for(&self.product_version, role_group_name)
294+
self.recommended_labels_for(&Self::role_name(), role_group_name)
285295
}
286296

287297
/// Selector labels matching the pods of a role group.
@@ -433,13 +443,15 @@ pub struct ValidatedRoleConfig {
433443
///
434444
/// The role-level discovery `ConfigMap` is deliberately absent: it is built from the *applied*
435445
/// role [`Listener`]'s ingress addresses, so it is assembled in the reconcile step after the
436-
/// Listener has been applied, not in the build step.
446+
/// Listener has been applied, not in the build step.
437447
pub struct KubernetesResources {
438448
pub stateful_sets: Vec<StatefulSet>,
439449
pub services: Vec<Service>,
440450
pub listeners: Vec<Listener>,
441451
pub config_maps: Vec<ConfigMap>,
442452
pub pod_disruption_budgets: Vec<PodDisruptionBudget>,
453+
pub service_accounts: Vec<ServiceAccount>,
454+
pub role_bindings: Vec<RoleBinding>,
443455
}
444456

445457
pub async fn reconcile_hive(
@@ -476,41 +488,26 @@ pub async fn reconcile_hive(
476488
&hive.spec.object_overrides,
477489
);
478490

479-
let (rbac_sa, rbac_rolebinding) = build_rbac_resources(
480-
hive,
481-
APP_NAME,
482-
cluster_resources
483-
.get_required_labels()
484-
.context(GetRequiredLabelsSnafu)?,
485-
)
486-
.context(BuildRbacResourcesSnafu)?;
487-
488-
let rbac_sa = cluster_resources
489-
.add(client, rbac_sa)
490-
.await
491-
.context(ApplyServiceAccountSnafu)?;
492-
493-
cluster_resources
494-
.add(client, rbac_rolebinding)
495-
.await
496-
.context(ApplyRoleBindingSnafu)?;
497-
498-
// The ServiceAccount name is deterministic on the built object, so the client-free build step
499-
// does not depend on the applied ServiceAccount.
500-
let service_account_name = rbac_sa.name_any();
501-
502-
let resources = build::build(
503-
&validated_cluster,
504-
&client.kubernetes_cluster_info,
505-
&service_account_name,
506-
)
507-
.context(BuildResourcesSnafu)?;
491+
let resources = build::build(&validated_cluster, &client.kubernetes_cluster_info)
492+
.context(BuildResourcesSnafu)?;
508493

509494
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
510495

511496
// Apply order: everything before StatefulSets, StatefulSets last. A StatefulSet must only be
512497
// applied after all ConfigMaps and Secrets it mounts, to prevent unnecessary Pod restarts.
513498
// See https://github.com/stackabletech/commons-operator/issues/111 for details.
499+
for service_account in resources.service_accounts {
500+
cluster_resources
501+
.add(client, service_account)
502+
.await
503+
.context(ApplyResourceSnafu)?;
504+
}
505+
for role_binding in resources.role_bindings {
506+
cluster_resources
507+
.add(client, role_binding)
508+
.await
509+
.context(ApplyResourceSnafu)?;
510+
}
514511
for service in resources.services {
515512
cluster_resources
516513
.add(client, service)

0 commit comments

Comments
 (0)