Skip to content

Commit c523263

Browse files
committed
factor: add infallible rbac functions
1 parent e712409 commit c523263

11 files changed

Lines changed: 225 additions & 261 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
@@ -29,5 +29,5 @@ tokio = { version = "1.52", features = ["full"] }
2929
tracing = "0.1"
3030

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

rust/operator-binary/src/controller.rs

Lines changed: 89 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,17 @@ use stackable_operator::{
2121
crd::listener,
2222
k8s_openapi::api::{
2323
apps::v1::StatefulSet,
24-
core::v1::{ConfigMap, Service},
24+
core::v1::{ConfigMap, Service, ServiceAccount},
2525
policy::v1::PodDisruptionBudget,
26+
rbac::v1::RoleBinding,
2627
},
2728
kube::{
28-
Resource, ResourceExt,
29+
Resource,
2930
api::{DynamicObject, ObjectMeta},
3031
core::{DeserializeGuard, error_boundary},
3132
runtime::{controller::Action, reflector::ObjectRef},
3233
},
34+
kvp::Labels,
3335
logging::controller::ReconcilerError,
3436
shared::time::Duration,
3537
status::condition::{
@@ -39,7 +41,9 @@ use stackable_operator::{
3941
v2::{
4042
HasName, HasUid, NameIsValidLabelValue,
4143
cluster_resources::cluster_resources_new,
44+
kvp::label::{recommended_labels, role_group_selector},
4245
role_group_utils::ResourceNames,
46+
role_utils,
4347
types::{
4448
kubernetes::{ConfigMapName, ListenerName, NamespaceName, Uid},
4549
operator::{ClusterName, ControllerName, OperatorName, ProductName, ProductVersion},
@@ -59,11 +63,7 @@ pub(crate) mod validate;
5963
pub use stackable_operator::v2::types::operator::{RoleGroupName, RoleName};
6064

6165
use crate::{
62-
controller::{
63-
build::resource::rbac::{build_rbac_role_binding, build_rbac_service_account},
64-
node_id_hasher::node_id_hash32_offset,
65-
security::ValidatedKafkaSecurity,
66-
},
66+
controller::{node_id_hasher::node_id_hash32_offset, security::ValidatedKafkaSecurity},
6767
crd::{
6868
APP_NAME, KafkaClusterStatus, KafkaPodDescriptor, MetadataManager, OPERATOR_NAME,
6969
authorization::KafkaAuthorizationConfig,
@@ -75,6 +75,9 @@ use crate::{
7575
pub const KAFKA_CONTROLLER_NAME: &str = "kafkacluster";
7676
pub const KAFKA_FULL_CONTROLLER_NAME: &str = concatcp!(KAFKA_CONTROLLER_NAME, '.', OPERATOR_NAME);
7777

78+
// Placeholder version label value for resources whose labels must not change after deployment.
79+
stackable_operator::constant!(UNVERSIONED_PRODUCT_VERSION: ProductVersion = "none");
80+
7881
#[derive(Snafu, Debug)]
7982
pub enum PodDescriptorsError {
8083
#[snafu(display(
@@ -99,6 +102,8 @@ pub struct KubernetesResources {
99102
pub listeners: Vec<listener::v1alpha1::Listener>,
100103
pub config_maps: Vec<ConfigMap>,
101104
pub pod_disruption_budgets: Vec<PodDisruptionBudget>,
105+
pub service_accounts: Vec<ServiceAccount>,
106+
pub role_bindings: Vec<RoleBinding>,
102107
}
103108

104109
/// The validated cluster. Carries everything the build steps need, resolved once
@@ -228,6 +233,15 @@ impl ValidatedCluster {
228233
RoleName::from_str(&role.to_string()).expect("a KafkaRole is a valid role name")
229234
}
230235

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+
231245
/// Type-safe names for the resources of a given role group.
232246
pub(crate) fn resource_names(
233247
&self,
@@ -241,6 +255,61 @@ impl ValidatedCluster {
241255
}
242256
}
243257

258+
/// Recommended labels for a role-group resource.
259+
pub fn recommended_labels(&self, role: &KafkaRole, role_group_name: &RoleGroupName) -> Labels {
260+
self.recommended_labels_for(&Self::role_name(role), role_group_name)
261+
}
262+
263+
/// Recommended labels for a resource that is not tied to a concrete [`KafkaRole`], using a free-form role/role-group label value.
264+
pub fn recommended_labels_for(
265+
&self,
266+
role_name: &RoleName,
267+
role_group_name: &RoleGroupName,
268+
) -> Labels {
269+
self.recommended_labels_with(&self.product_version, role_name, role_group_name)
270+
}
271+
272+
/// Recommended labels with the constant [`UNVERSIONED_PRODUCT_VERSION`], for PVC templates
273+
/// that cannot be modified after deployment (keeps the labels stable across version upgrades).
274+
pub fn unversioned_recommended_labels(
275+
&self,
276+
role: &KafkaRole,
277+
role_group_name: &RoleGroupName,
278+
) -> Labels {
279+
self.recommended_labels_with(
280+
&UNVERSIONED_PRODUCT_VERSION,
281+
&Self::role_name(role),
282+
role_group_name,
283+
)
284+
}
285+
286+
fn recommended_labels_with(
287+
&self,
288+
product_version: &ProductVersion,
289+
role_name: &RoleName,
290+
role_group_name: &RoleGroupName,
291+
) -> Labels {
292+
recommended_labels(
293+
self,
294+
&product_name(),
295+
product_version,
296+
&operator_name(),
297+
&controller_name(),
298+
role_name,
299+
role_group_name,
300+
)
301+
}
302+
303+
/// Selector labels matching the pods of a role group.
304+
pub fn role_group_selector(&self, role: &KafkaRole, role_group_name: &RoleGroupName) -> Labels {
305+
role_group_selector(
306+
self,
307+
&product_name(),
308+
&Self::role_name(role),
309+
role_group_name,
310+
)
311+
}
312+
244313
/// The name of the broker rolegroup's bootstrap [`Listener`](stackable_operator::crd::listener),
245314
/// `<cluster>-<role>-<role-group>-bootstrap`.
246315
pub fn bootstrap_listener_name(
@@ -423,27 +492,11 @@ pub enum Error {
423492
source: stackable_operator::cluster_resources::Error,
424493
},
425494

426-
#[snafu(display("failed to patch service account"))]
427-
ApplyServiceAccount {
428-
source: stackable_operator::cluster_resources::Error,
429-
},
430-
431-
#[snafu(display("failed to patch role binding"))]
432-
ApplyRoleBinding {
433-
source: stackable_operator::cluster_resources::Error,
434-
},
435-
436495
#[snafu(display("failed to update status"))]
437496
ApplyStatus {
438497
source: stackable_operator::client::Error,
439498
},
440499

441-
#[snafu(display("failed to get required Labels"))]
442-
GetRequiredLabels {
443-
source:
444-
stackable_operator::kvp::KeyValuePairError<stackable_operator::kvp::LabelValueError>,
445-
},
446-
447500
#[snafu(display("KafkaCluster object is invalid"))]
448501
InvalidKafkaCluster {
449502
source: error_boundary::InvalidObject,
@@ -465,10 +518,7 @@ impl ReconcilerError for Error {
465518
Error::BuildDiscoveryConfig { .. } => None,
466519
Error::ApplyDiscoveryConfig { .. } => None,
467520
Error::DeleteOrphans { .. } => None,
468-
Error::ApplyServiceAccount { .. } => None,
469-
Error::ApplyRoleBinding { .. } => None,
470521
Error::ApplyStatus { .. } => None,
471-
Error::GetRequiredLabels { .. } => None,
472522
Error::InvalidKafkaCluster { .. } => None,
473523
}
474524
}
@@ -519,34 +569,27 @@ pub async fn reconcile_kafka(
519569

520570
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
521571

522-
let required_labels = cluster_resources
523-
.get_required_labels()
524-
.context(GetRequiredLabelsSnafu)?;
525-
let rbac_sa = build_rbac_service_account(&validated_cluster, required_labels.clone());
526-
let rbac_rolebinding = build_rbac_role_binding(&validated_cluster, required_labels);
527-
528-
let rbac_sa = cluster_resources
529-
.add(client, rbac_sa.clone())
530-
.await
531-
.context(ApplyServiceAccountSnafu)?;
532-
// The ServiceAccount name is deterministic, so the statefulset builders only need the name,
533-
// not the applied object.
534-
let service_account_name = rbac_sa.name_any();
535-
cluster_resources
536-
.add(client, rbac_rolebinding)
537-
.await
538-
.context(ApplyRoleBindingSnafu)?;
539-
540572
// Build every Kubernetes resource up front (client-free). The discovery ConfigMap is not part
541573
// of this, as it depends on the applied bootstrap Listeners' status (see below).
542-
let resources =
543-
build::build(&validated_cluster, &service_account_name).context(BuildResourcesSnafu)?;
574+
let resources = build::build(&validated_cluster).context(BuildResourcesSnafu)?;
544575

545576
// Apply order: Services, then Listeners (collecting the applied bootstrap Listeners for the
546577
// discovery ConfigMap), then ConfigMaps, then PodDisruptionBudgets, and finally the
547578
// StatefulSets. The StatefulSets must be applied after all ConfigMaps and Secrets they mount to
548579
// prevent unnecessary Pod restarts.
549580
// See https://github.com/stackabletech/commons-operator/issues/111 for details.
581+
for service_account in resources.service_accounts {
582+
cluster_resources
583+
.add(client, service_account)
584+
.await
585+
.context(ApplyResourceSnafu)?;
586+
}
587+
for role_binding in resources.role_bindings {
588+
cluster_resources
589+
.add(client, role_binding)
590+
.await
591+
.context(ApplyResourceSnafu)?;
592+
}
550593
for service in resources.services {
551594
cluster_resources
552595
.add(client, service)

rust/operator-binary/src/controller/build/labels.rs

Lines changed: 0 additions & 71 deletions
This file was deleted.

0 commit comments

Comments
 (0)