Skip to content

Commit 20fb778

Browse files
committed
factor: add infallible rbac functions
1 parent 75728c5 commit 20fb778

8 files changed

Lines changed: 173 additions & 92 deletions

File tree

Cargo.lock

Lines changed: 0 additions & 9 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
@@ -35,4 +35,4 @@ url = { version = "2.5.7" }
3535

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

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

Lines changed: 64 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use crate::{
1414
config_map::build_rolegroup_config_map,
1515
listener::{build_group_listener, group_listener_name},
1616
pdb::build_pdb,
17+
rbac::{build_role_binding, build_service_account},
1718
service::{build_rolegroup_headless_service, build_rolegroup_metrics_service},
1819
statefulset::build_node_rolegroup_statefulset,
1920
},
@@ -69,10 +70,7 @@ pub enum Error {
6970
///
7071
/// `service_account_name` is the name of the RBAC `ServiceAccount` the role-group Pods run under
7172
/// (RBAC resources are built and applied separately, in the reconcile step).
72-
pub fn build(
73-
cluster: &ValidatedCluster,
74-
service_account_name: &str,
75-
) -> Result<KubernetesResources, Error> {
73+
pub fn build(cluster: &ValidatedCluster) -> Result<KubernetesResources, Error> {
7674
let mut stateful_sets = vec![];
7775
let mut services = vec![];
7876
let mut listeners = vec![];
@@ -109,16 +107,10 @@ pub fn build(
109107

110108
let effective_replicas = rg.replicas.map(i32::from);
111109
stateful_sets.push(
112-
build_node_rolegroup_statefulset(
113-
cluster,
114-
role_group_name,
115-
rg,
116-
effective_replicas,
117-
service_account_name,
118-
)
119-
.context(StatefulSetSnafu {
120-
role_group: role_group_name.clone(),
121-
})?,
110+
build_node_rolegroup_statefulset(cluster, role_group_name, rg, effective_replicas)
111+
.context(StatefulSetSnafu {
112+
role_group: role_group_name.clone(),
113+
})?,
122114
);
123115
}
124116

@@ -128,11 +120,15 @@ pub fn build(
128120
listeners,
129121
config_maps,
130122
pod_disruption_budgets,
123+
service_accounts: vec![build_service_account(cluster)],
124+
role_bindings: vec![build_role_binding(cluster)],
131125
})
132126
}
133127

134128
#[cfg(test)]
135129
mod tests {
130+
use std::collections::BTreeMap;
131+
136132
use stackable_operator::kube::Resource;
137133

138134
use super::{build, properties::test_support::minimal_validated_cluster};
@@ -149,7 +145,7 @@ mod tests {
149145
#[test]
150146
fn build_produces_expected_resources() {
151147
let cluster = minimal_validated_cluster();
152-
let resources = build(&cluster, "simple-nifi-serviceaccount").expect("build succeeds");
148+
let resources = build(&cluster).expect("build succeeds");
153149

154150
// The minimal fixture has a single `default` role group for the `node` role.
155151
assert_eq!(
@@ -169,4 +165,57 @@ mod tests {
169165
["simple-nifi-node"]
170166
);
171167
}
168+
169+
/// Locks the RBAC resource names, the roleRef, and the recommended label set against
170+
/// accidental drift. The fixture's cluster name deliberately differs from the product name so
171+
/// that swapped `name`/`instance` label values cannot pass unnoticed.
172+
///
173+
/// The version label is the bare `2.9.0` because the fixture hand-builds its
174+
/// [`ResolvedProductImage`](stackable_operator::commons::product_image_selection::ResolvedProductImage)
175+
/// instead of resolving it (which would append the `-stackable…` suffix).
176+
#[test]
177+
fn build_produces_rbac() {
178+
let cluster = minimal_validated_cluster();
179+
let resources = build(&cluster).expect("build succeeds");
180+
181+
assert_eq!(
182+
sorted_names(&resources.service_accounts),
183+
["simple-nifi-serviceaccount"]
184+
);
185+
assert_eq!(
186+
sorted_names(&resources.role_bindings),
187+
["simple-nifi-rolebinding"]
188+
);
189+
190+
let expected_labels = BTreeMap::from(
191+
[
192+
("app.kubernetes.io/component", "none"),
193+
("app.kubernetes.io/instance", "simple-nifi"),
194+
(
195+
"app.kubernetes.io/managed-by",
196+
"nifi.stackable.tech_nificluster",
197+
),
198+
("app.kubernetes.io/name", "nifi"),
199+
("app.kubernetes.io/role-group", "none"),
200+
("app.kubernetes.io/version", "2.9.0"),
201+
("stackable.tech/vendor", "Stackable"),
202+
]
203+
.map(|(key, value)| (key.to_string(), value.to_string())),
204+
);
205+
let service_account = resources
206+
.service_accounts
207+
.first()
208+
.expect("a ServiceAccount is built");
209+
assert_eq!(
210+
service_account.metadata.labels,
211+
Some(expected_labels.clone())
212+
);
213+
214+
let role_binding = resources
215+
.role_bindings
216+
.first()
217+
.expect("a RoleBinding is built");
218+
assert_eq!(role_binding.metadata.labels, Some(expected_labels));
219+
assert_eq!(role_binding.role_ref.name, "nifi-clusterrole");
220+
}
172221
}

rust/operator-binary/src/controller/build/resource/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@
55
pub mod config_map;
66
pub mod listener;
77
pub mod pdb;
8+
pub mod rbac;
89
pub mod service;
910
pub mod statefulset;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
//! Builds the RBAC resources (ServiceAccount + RoleBinding) shared by all role groups.
2+
3+
use std::str::FromStr;
4+
5+
use stackable_operator::{
6+
k8s_openapi::api::{core::v1::ServiceAccount, rbac::v1::RoleBinding},
7+
kvp::Labels,
8+
v2::{
9+
rbac,
10+
types::operator::{RoleGroupName, RoleName},
11+
},
12+
};
13+
14+
use crate::controller::ValidatedCluster;
15+
16+
stackable_operator::constant!(NONE_ROLE_NAME: RoleName = "none");
17+
stackable_operator::constant!(NONE_ROLE_GROUP_NAME: RoleGroupName = "none");
18+
19+
/// Builds the [`ServiceAccount`] that the role-group Pods run under.
20+
pub fn build_service_account(cluster: &ValidatedCluster) -> ServiceAccount {
21+
rbac::build_service_account(
22+
cluster,
23+
&cluster.rbac_resource_names(),
24+
rbac_labels(cluster),
25+
)
26+
}
27+
28+
/// Builds the [`RoleBinding`] that binds the [`ServiceAccount`] from [`build_service_account`] to
29+
/// the operator-deployed ClusterRole.
30+
pub fn build_role_binding(cluster: &ValidatedCluster) -> RoleBinding {
31+
rbac::build_role_binding(
32+
cluster,
33+
&cluster.rbac_resource_names(),
34+
rbac_labels(cluster),
35+
)
36+
}
37+
38+
/// Both resources are shared by the whole cluster rather than tied to a role or role group, so
39+
/// the recommended labels carry `none` for both values.
40+
fn rbac_labels(cluster: &ValidatedCluster) -> Labels {
41+
cluster.recommended_labels_for(&NONE_ROLE_NAME, &NONE_ROLE_GROUP_NAME)
42+
}

rust/operator-binary/src/controller/build/resource/statefulset.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,6 @@ pub(crate) fn build_node_rolegroup_statefulset(
169169
role_group_name: &RoleGroupName,
170170
rg: &NifiRoleGroupConfig,
171171
effective_replicas: Option<i32>,
172-
service_account_name: &str,
173172
) -> Result<StatefulSet> {
174173
tracing::debug!("Building statefulset");
175174

@@ -642,7 +641,12 @@ pub(crate) fn build_node_rolegroup_statefulset(
642641
..Volume::default()
643642
})
644643
.context(AddVolumeSnafu)?
645-
.service_account_name(service_account_name)
644+
.service_account_name(
645+
cluster
646+
.rbac_resource_names()
647+
.service_account_name()
648+
.to_string(),
649+
)
646650
.security_context(PodSecurityContextBuilder::new().fs_group(1000).build());
647651

648652
let mut pod_template = pod_builder.build_template();
@@ -709,7 +713,7 @@ fn get_volume_claim_templates(
709713

710714
// Used for PVC templates that cannot be modified once they are deployed, so the version label
711715
// is set to the placeholder `none` to keep the labels stable across version upgrades.
712-
let unversioned_recommended_labels = cluster.recommended_labels_unversioned(role_group_name);
716+
let unversioned_recommended_labels = cluster.unversioned_recommended_labels(role_group_name);
713717

714718
// listener endpoints will use persistent volumes
715719
// so that load balancers can hard-code the target addresses and

0 commit comments

Comments
 (0)