Skip to content

Commit 5de0e01

Browse files
committed
refactor rbac (service account and role binding), added tests
1 parent eab441f commit 5de0e01

9 files changed

Lines changed: 167 additions & 15456 deletions

File tree

Cargo.nix

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

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ tokio-zookeeper = "0.4"
3131
tracing = "0.1"
3232

3333
[patch."https://github.com/stackabletech/operator-rs.git"]
34-
# stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "smooth-operator" }
34+
stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "feat/smooth-operator/build-rbac" }
3535
# stackable-operator = { path = "../operator-rs/crates/stackable-operator" }
3636

3737
[patch.crates-io]

crate-hashes.json

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

rust/operator-binary/src/zk_controller.rs

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,18 @@ use snafu::{OptionExt, ResultExt, Snafu};
77
use stackable_operator::{
88
cli::OperatorEnvironmentOptions,
99
cluster_resources::ClusterResourceApplyStrategy,
10-
commons::rbac::build_rbac_resources,
1110
crd::listener::v1alpha1::Listener,
1211
k8s_openapi::api::{
1312
apps::v1::StatefulSet,
14-
core::v1::{ConfigMap, Service},
13+
core::v1::{ConfigMap, Service, ServiceAccount},
1514
policy::v1::PodDisruptionBudget,
15+
rbac::v1::RoleBinding,
1616
},
1717
kube::{
1818
api::DynamicObject,
1919
core::{DeserializeGuard, error_boundary},
2020
runtime::controller,
2121
},
22-
kvp::LabelError,
2322
logging::controller::ReconcilerError,
2423
shared::time::Duration,
2524
status::condition::{
@@ -31,7 +30,7 @@ use stackable_operator::{
3130
use strum::{EnumDiscriminants, IntoStaticStr};
3231

3332
use crate::{
34-
APP_NAME, OPERATOR_NAME, ObjectRef,
33+
OPERATOR_NAME, ObjectRef,
3534
crd::v1alpha1,
3635
zk_controller::{
3736
build::resource::discovery,
@@ -106,29 +105,11 @@ pub enum Error {
106105
source: stackable_operator::client::Error,
107106
},
108107

109-
#[snafu(display("failed to create RBAC service account"))]
110-
ApplyServiceAccount {
111-
source: stackable_operator::cluster_resources::Error,
112-
},
113-
114-
#[snafu(display("failed to create RBAC role binding"))]
115-
ApplyRoleBinding {
116-
source: stackable_operator::cluster_resources::Error,
117-
},
118-
119-
#[snafu(display("failed to build RBAC resources"))]
120-
BuildRbacResources {
121-
source: stackable_operator::commons::rbac::Error,
122-
},
123-
124108
#[snafu(display("failed to delete orphaned resources"))]
125109
DeleteOrphans {
126110
source: stackable_operator::cluster_resources::Error,
127111
},
128112

129-
#[snafu(display("failed to build label"))]
130-
BuildLabel { source: LabelError },
131-
132113
#[snafu(display("failed to build object meta data"))]
133114
ObjectMeta {
134115
source: stackable_operator::builder::meta::Error,
@@ -154,11 +135,7 @@ impl ReconcilerError for Error {
154135
Error::BuildDiscoveryConfig { .. } => None,
155136
Error::ApplyDiscoveryConfig { .. } => None,
156137
Error::ApplyStatus { .. } => None,
157-
Error::ApplyServiceAccount { .. } => None,
158-
Error::ApplyRoleBinding { .. } => None,
159-
Error::BuildRbacResources { .. } => None,
160138
Error::DeleteOrphans { .. } => None,
161-
Error::BuildLabel { .. } => None,
162139
Error::ObjectMeta { .. } => None,
163140
}
164141
}
@@ -173,6 +150,8 @@ pub struct KubernetesResources {
173150
pub listeners: Vec<Listener>,
174151
pub config_maps: Vec<ConfigMap>,
175152
pub pod_disruption_budgets: Vec<PodDisruptionBudget>,
153+
pub service_accounts: Vec<ServiceAccount>,
154+
pub role_bindings: Vec<RoleBinding>,
176155
}
177156

178157
pub async fn reconcile_zk(
@@ -209,30 +188,24 @@ pub async fn reconcile_zk(
209188
&validated_cluster.object_overrides,
210189
);
211190

212-
let (rbac_sa, rbac_rolebinding) = build_rbac_resources(
213-
zk,
214-
APP_NAME,
215-
cluster_resources
216-
.get_required_labels()
217-
.context(BuildLabelSnafu)?,
218-
)
219-
.context(BuildRbacResourcesSnafu)?;
220-
221-
cluster_resources
222-
.add(client, rbac_sa)
223-
.await
224-
.context(ApplyServiceAccountSnafu)?;
225-
226-
cluster_resources
227-
.add(client, rbac_rolebinding)
228-
.await
229-
.context(ApplyRoleBindingSnafu)?;
230-
231191
let resources = build::build(&validated_cluster, &client.kubernetes_cluster_info)
232192
.context(BuildResourcesSnafu)?;
233193

234194
let mut ss_cond_builder = StatefulSetConditionBuilder::default();
235195

196+
for service_account in resources.service_accounts {
197+
cluster_resources
198+
.add(client, service_account)
199+
.await
200+
.context(ApplyResourceSnafu)?;
201+
}
202+
for role_binding in resources.role_bindings {
203+
cluster_resources
204+
.add(client, role_binding)
205+
.await
206+
.context(ApplyResourceSnafu)?;
207+
}
208+
236209
for service in resources.services {
237210
cluster_resources
238211
.add(client, service)

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

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ use std::str::FromStr;
1313

1414
use snafu::{ResultExt, Snafu};
1515
use stackable_operator::{
16-
utils::cluster_info::KubernetesClusterInfo,
17-
v2::types::operator::{ProductVersion, RoleGroupName},
16+
utils::cluster_info::KubernetesClusterInfo, v2::types::operator::RoleGroupName,
1817
};
1918

2019
use crate::{
@@ -25,6 +24,7 @@ use crate::{
2524
config_map,
2625
listener::build_role_listener,
2726
pdb::build_pdb,
27+
rbac::{build_role_binding, build_service_account},
2828
service::{
2929
build_server_rolegroup_headless_service, build_server_rolegroup_metrics_service,
3030
},
@@ -42,10 +42,6 @@ stackable_operator::constant!(pub(crate) PLACEHOLDER_DISCOVERY_ROLE_GROUP: RoleG
4242
// (which is not tied to a single role group).
4343
stackable_operator::constant!(pub(crate) PLACEHOLDER_LISTENER_ROLE_GROUP: RoleGroupName = "none");
4444

45-
// Placeholder product version used for labels on PVC templates, which cannot be modified once
46-
// deployed. A constant value keeps the labels stable across version upgrades.
47-
stackable_operator::constant!(pub(crate) UNVERSIONED_PRODUCT_VERSION: ProductVersion = "none");
48-
4945
pub mod command;
5046
pub mod graceful_shutdown;
5147
pub mod jvm;
@@ -137,11 +133,15 @@ pub fn build(
137133
listeners,
138134
config_maps,
139135
pod_disruption_budgets,
136+
service_accounts: vec![build_service_account(cluster)],
137+
role_bindings: vec![build_role_binding(cluster)],
140138
})
141139
}
142140

143141
#[cfg(test)]
144142
mod tests {
143+
use std::collections::BTreeMap;
144+
145145
use stackable_operator::kube::Resource;
146146

147147
use super::build;
@@ -216,4 +216,68 @@ mod tests {
216216
["simple-zookeeper-server"]
217217
);
218218
}
219+
220+
/// Locks the RBAC resource names, the roleRef, and the recommended label set against
221+
/// accidental drift. The fixture's cluster name deliberately differs from the product name so
222+
/// that swapped `name`/`instance` label values cannot pass unnoticed.
223+
#[test]
224+
fn build_produces_rbac() {
225+
let zookeeper_yaml = r#"
226+
apiVersion: zookeeper.stackable.tech/v1alpha1
227+
kind: ZookeeperCluster
228+
metadata:
229+
name: simple-zookeeper
230+
spec:
231+
image:
232+
productVersion: "3.9.5"
233+
servers:
234+
roleGroups:
235+
default:
236+
replicas: 1
237+
"#;
238+
let zookeeper = minimal_zk(zookeeper_yaml);
239+
let cluster = validated_cluster(&zookeeper);
240+
241+
let resources = build(&cluster, &cluster_info()).expect("build succeeds");
242+
243+
assert_eq!(
244+
sorted_names(&resources.service_accounts),
245+
["simple-zookeeper-serviceaccount"]
246+
);
247+
assert_eq!(
248+
sorted_names(&resources.role_bindings),
249+
["simple-zookeeper-rolebinding"]
250+
);
251+
252+
let expected_labels = BTreeMap::from(
253+
[
254+
("app.kubernetes.io/component", "none"),
255+
("app.kubernetes.io/instance", "simple-zookeeper"),
256+
(
257+
"app.kubernetes.io/managed-by",
258+
"zookeeper.stackable.tech_zookeepercluster",
259+
),
260+
("app.kubernetes.io/name", "zookeeper"),
261+
("app.kubernetes.io/role-group", "none"),
262+
("app.kubernetes.io/version", "3.9.5-stackable0.0.0-dev"),
263+
("stackable.tech/vendor", "Stackable"),
264+
]
265+
.map(|(key, value)| (key.to_string(), value.to_string())),
266+
);
267+
let service_account = resources
268+
.service_accounts
269+
.first()
270+
.expect("a ServiceAccount is built");
271+
assert_eq!(
272+
service_account.metadata.labels,
273+
Some(expected_labels.clone())
274+
);
275+
276+
let role_binding = resources
277+
.role_bindings
278+
.first()
279+
.expect("a RoleBinding is built");
280+
assert_eq!(role_binding.metadata.labels, Some(expected_labels));
281+
assert_eq!(role_binding.role_ref.name, "zookeeper-clusterrole");
282+
}
219283
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ pub mod config_map;
55
pub mod discovery;
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::zk_controller::validate::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/zk_controller/build/resource/statefulset.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ use crate::{
5757
zk_controller::{
5858
LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME,
5959
build::{
60-
UNVERSIONED_PRODUCT_VERSION,
6160
command::create_init_container_command_args,
6261
graceful_shutdown::add_graceful_shutdown_config,
6362
jvm::{construct_non_heap_jvm_args, construct_zk_server_heap_env},
@@ -194,8 +193,7 @@ pub fn build_server_rolegroup_statefulset(
194193

195194
// Used for PVC templates that cannot be modified once they are deployed. A constant version
196195
// keeps the labels stable across version upgrades.
197-
let unversioned_recommended_labels =
198-
cluster.recommended_labels_for(&UNVERSIONED_PRODUCT_VERSION, role_group_name);
196+
let unversioned_recommended_labels = cluster.unversioned_recommended_labels(role_group_name);
199197

200198
let listener_pvc = build_role_listener_pvc(
201199
role_listener_name(cluster.name.as_ref(), &ZookeeperRole::Server).as_ref(),
@@ -383,7 +381,7 @@ pub fn build_server_rolegroup_statefulset(
383381
fs_group: Some(1000),
384382
..PodSecurityContext::default()
385383
})
386-
.service_account_name(cluster.rbac_service_account_name());
384+
.service_account_name(cluster.rbac_resource_names().service_account_name());
387385

388386
// Use the user-provided custom log ConfigMap if one is configured, otherwise fall back to the
389387
// rolegroup's own ConfigMap. This branches on the *validated* logging choice.

0 commit comments

Comments
 (0)