Skip to content

Commit f1dc084

Browse files
committed
refactor: use ConfigMapName, SecretName, ListenerName in CRD
1 parent aa08d22 commit f1dc084

9 files changed

Lines changed: 77 additions & 59 deletions

File tree

extra/crds.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,9 @@ spec:
12731273
The name of the Secret object containing the admin user credentials. Read the
12741274
[getting started guide first steps](https://docs.stackable.tech/home/nightly/airflow/getting_started/first_steps)
12751275
to find out more.
1276+
maxLength: 253
1277+
minLength: 1
1278+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
12761279
type: string
12771280
dagsGitSync:
12781281
default: []
@@ -1494,7 +1497,10 @@ spec:
14941497
It must contain the key `ADDRESS` with the address of the Vector aggregator.
14951498
Follow the [logging tutorial](https://docs.stackable.tech/home/nightly/tutorials/logging-vector-aggregator)
14961499
to learn how to configure log aggregation with Vector.
1500+
maxLength: 253
1501+
minLength: 1
14971502
nullable: true
1503+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
14981504
type: string
14991505
volumeMounts:
15001506
default: []
@@ -5587,6 +5593,9 @@ spec:
55875593
listenerClass:
55885594
default: cluster-internal
55895595
description: This field controls which [ListenerClass](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass.html) is used to expose the webserver.
5596+
maxLength: 253
5597+
minLength: 1
5598+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
55905599
type: string
55915600
podDisruptionBudget:
55925601
default:
@@ -7419,6 +7428,9 @@ spec:
74197428
The name of the Secret object containing the admin user credentials. Read the
74207429
[getting started guide first steps](https://docs.stackable.tech/home/nightly/airflow/getting_started/first_steps)
74217430
to find out more.
7431+
maxLength: 253
7432+
minLength: 1
7433+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
74227434
type: string
74237435
dagsGitSync:
74247436
default: []
@@ -7616,7 +7628,10 @@ spec:
76167628
It must contain the key `ADDRESS` with the address of the Vector aggregator.
76177629
Follow the [logging tutorial](https://docs.stackable.tech/home/nightly/tutorials/logging-vector-aggregator)
76187630
to learn how to configure log aggregation with Vector.
7631+
maxLength: 253
7632+
minLength: 1
76197633
nullable: true
7634+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
76207635
type: string
76217636
volumeMounts:
76227637
default: []
@@ -11709,6 +11724,9 @@ spec:
1170911724
listenerClass:
1171011725
default: cluster-internal
1171111726
description: This field controls which [ListenerClass](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass.html) is used to expose the webserver.
11727+
maxLength: 253
11728+
minLength: 1
11729+
pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$
1171211730
type: string
1171311731
podDisruptionBudget:
1171411732
default:

rust/operator-binary/src/airflow_controller.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ pub async fn reconcile_airflow(
240240

241241
// TODO: Move secret creation to a dedicated apply step once it exists.
242242
random_secret_creation::create_random_secret_if_not_exists(
243-
&validated_cluster.internal_secret_name(),
243+
validated_cluster.internal_secret_name().as_ref(),
244244
INTERNAL_SECRET_SECRET_KEY,
245245
256,
246246
airflow,
@@ -250,7 +250,7 @@ pub async fn reconcile_airflow(
250250
.context(InternalSecretSnafu)?;
251251

252252
random_secret_creation::create_random_secret_if_not_exists(
253-
&validated_cluster.jwt_secret_name(),
253+
validated_cluster.jwt_secret_name().as_ref(),
254254
JWT_SECRET_SECRET_KEY,
255255
256,
256256
airflow,
@@ -264,7 +264,7 @@ pub async fn reconcile_airflow(
264264
// python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
265265
// which returns `jUm21LuA76YZmrIa9u4eXRg0h0P24MDC9IDOmDvJbfw=`, which has 44 characters, which makes 32 bytes.
266266
random_secret_creation::create_random_secret_if_not_exists(
267-
&validated_cluster.fernet_key_name(),
267+
validated_cluster.fernet_key_name().as_ref(),
268268
FERNET_KEY_SECRET_KEY,
269269
32,
270270
airflow,
@@ -335,7 +335,7 @@ pub async fn reconcile_airflow(
335335
let rg_group_listener = build_group_listener(
336336
&validated_cluster,
337337
airflow_role,
338-
listener_class.to_string(),
338+
listener_class.clone(),
339339
listener_group_name.clone(),
340340
);
341341
cluster_resources

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ pub fn build_airflow_statefulset_envs(
118118
"AIRFLOW__CORE__FERNET_KEY".into(),
119119
env_var_from_secret(
120120
"AIRFLOW__CORE__FERNET_KEY",
121-
&cluster.fernet_key_name(),
121+
cluster.fernet_key_name(),
122122
FERNET_KEY_SECRET_KEY,
123123
),
124124
);
@@ -488,7 +488,7 @@ fn add_version_specific_env_vars(
488488
"AIRFLOW__API_AUTH__JWT_SECRET".into(),
489489
env_var_from_secret(
490490
"AIRFLOW__API_AUTH__JWT_SECRET",
491-
&cluster.jwt_secret_name(),
491+
cluster.jwt_secret_name(),
492492
JWT_SECRET_SECRET_KEY,
493493
),
494494
);

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
use stackable_operator::{
2-
builder::meta::ObjectMetaBuilder, crd::listener,
3-
v2::builder::meta::ownerreference_from_resource,
2+
builder::meta::ObjectMetaBuilder,
3+
crd::listener,
4+
v2::{
5+
builder::meta::ownerreference_from_resource,
6+
types::kubernetes::{ListenerClassName, ListenerName},
7+
},
48
};
59

610
use crate::{
@@ -11,8 +15,8 @@ use crate::{
1115
pub fn build_group_listener(
1216
cluster: &ValidatedCluster,
1317
role: &AirflowRole,
14-
listener_class: String,
15-
listener_group_name: String,
18+
listener_class: ListenerClassName,
19+
listener_group_name: ListenerName,
1620
) -> listener::v1alpha1::Listener {
1721
listener::v1alpha1::Listener {
1822
metadata: ObjectMetaBuilder::new()
@@ -27,7 +31,7 @@ pub fn build_group_listener(
2731
))
2832
.build(),
2933
spec: listener::v1alpha1::ListenerSpec {
30-
class_name: Some(listener_class),
34+
class_name: Some(listener_class.to_string()),
3135
ports: Some(listener_ports()),
3236
..listener::v1alpha1::ListenerSpec::default()
3337
},

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,7 @@ pub fn build_server_rolegroup_statefulset(
241241
// be the case even when no class is set (and the value defaults to
242242
// cluster-internal) as the address should still be consistent.
243243
let pvc = ListenerOperatorVolumeSourceBuilder::new(
244-
&ListenerReference::ListenerName(listener_group_name),
244+
&ListenerReference::ListenerName(listener_group_name.to_string()),
245245
&unversioned_recommended_labels,
246246
)
247247
.build_pvc(LISTENER_VOLUME_NAME.to_string())

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

Lines changed: 16 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use stackable_operator::{
1515
product_logging::framework::{ValidatedContainerLogConfigChoice, VectorContainerLogConfig},
1616
role_group_utils::ResourceNames,
1717
types::{
18-
kubernetes::{ConfigMapName, Uid},
18+
kubernetes::{ConfigMapName, ListenerClassName, ListenerName, SecretName, Uid},
1919
operator::{
2020
ClusterName, ControllerName, OperatorName, ProductName, ProductVersion,
2121
RoleGroupName, RoleName,
@@ -41,8 +41,8 @@ pub mod validate;
4141
#[derive(Clone, Debug)]
4242
pub struct ValidatedRoleConfig {
4343
pub pdb: Option<stackable_operator::commons::pdb::PdbConfig>,
44-
pub listener_class: Option<String>,
45-
pub group_listener_name: Option<String>,
44+
pub listener_class: Option<ListenerClassName>,
45+
pub group_listener_name: Option<ListenerName>,
4646
}
4747

4848
/// Per-rolegroup configuration: the merged CRD config plus overrides.
@@ -91,7 +91,7 @@ pub struct ValidatedClusterConfig {
9191
pub authorization_config: AirflowAuthorizationResolved,
9292
/// The Git-sync definitions for the DAGs (`spec.clusterConfig.dagsGitSync`).
9393
pub dags_git_sync: Vec<git_sync::v1alpha2::GitSync>,
94-
pub credentials_secret_name: String,
94+
pub credentials_secret_name: SecretName,
9595
pub load_examples: bool,
9696
pub expose_config: bool,
9797
pub database_initialization_enabled: bool,
@@ -168,23 +168,27 @@ impl ValidatedCluster {
168168
}
169169

170170
/// The Secret holding the shared internal secret (`<cluster>-internal-secret`).
171-
pub fn internal_secret_name(&self) -> String {
172-
format!("{}-internal-secret", self.name_any())
171+
pub fn internal_secret_name(&self) -> SecretName {
172+
SecretName::from_str(&format!("{}-internal-secret", self.name_any()))
173+
.expect("the internal secret name is a valid Secret name")
173174
}
174175

175176
/// The Secret holding the shared JWT secret (`<cluster>-jwt-secret`).
176-
pub fn jwt_secret_name(&self) -> String {
177-
format!("{}-jwt-secret", self.name_any())
177+
pub fn jwt_secret_name(&self) -> SecretName {
178+
SecretName::from_str(&format!("{}-jwt-secret", self.name_any()))
179+
.expect("the JWT secret name is a valid Secret name")
178180
}
179181

180182
/// The Secret holding the shared Fernet key (`<cluster>-fernet-key`).
181-
pub fn fernet_key_name(&self) -> String {
182-
format!("{}-fernet-key", self.name_any())
183+
pub fn fernet_key_name(&self) -> SecretName {
184+
SecretName::from_str(&format!("{}-fernet-key", self.name_any()))
185+
.expect("the Fernet key secret name is a valid Secret name")
183186
}
184187

185188
/// The ConfigMap holding the Kubernetes-executor pod template (`<cluster>-executor-pod-template`).
186-
pub fn executor_template_configmap_name(&self) -> String {
187-
format!("{}-executor-pod-template", self.name_any())
189+
pub fn executor_template_configmap_name(&self) -> ConfigMapName {
190+
ConfigMapName::from_str(&format!("{}-executor-pod-template", self.name_any()))
191+
.expect("the executor pod-template ConfigMap name is a valid ConfigMap name")
188192
}
189193

190194
/// User-supplied extra Volumes (`spec.clusterConfig.volumes`).

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

Lines changed: 8 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,6 @@ pub enum Error {
7979
"the Vector aggregator discovery ConfigMap name must be set when the Vector agent is enabled"
8080
))]
8181
MissingVectorAggregatorConfigMapName,
82-
83-
#[snafu(display("invalid Vector aggregator discovery ConfigMap name"))]
84-
ParseVectorAggregatorConfigMapName {
85-
source: stackable_operator::v2::macros::attributed_string_type::Error,
86-
},
8782
}
8883

8984
pub fn validate_cluster(
@@ -106,9 +101,13 @@ pub fn validate_cluster(
106101
cluster_name: airflow.name_any(),
107102
})?;
108103

109-
// The Vector aggregator discovery ConfigMap name (validated here so an invalid name fails
110-
// up-front). It is only required when the Vector agent is enabled for a role group.
111-
let vector_aggregator_config_map_name = parse_vector_aggregator_config_map_name(airflow)?;
104+
// The Vector aggregator discovery ConfigMap name. Validity is already enforced by the
105+
// `ConfigMapName` type on the CRD; it is only required when the Vector agent is enabled.
106+
let vector_aggregator_config_map_name = airflow
107+
.spec
108+
.cluster_config
109+
.vector_aggregator_config_map_name
110+
.clone();
112111

113112
let mut role_groups = BTreeMap::new();
114113
let mut role_configs = BTreeMap::new();
@@ -126,7 +125,7 @@ pub fn validate_cluster(
126125
pdb: airflow
127126
.role_config(&role)
128127
.map(|rc| rc.pod_disruption_budget),
129-
listener_class: role.listener_class_name(airflow).map(|s| s.to_string()),
128+
listener_class: role.listener_class_name(airflow),
130129
group_listener_name: airflow.group_listener_name(&role),
131130
},
132131
);
@@ -293,21 +292,6 @@ fn validate_role_group(
293292
})
294293
}
295294

296-
/// Parses the optional Vector aggregator discovery ConfigMap name from the cluster spec into a
297-
/// typed [`ConfigMapName`], so an invalid name fails reconciliation up-front.
298-
pub(crate) fn parse_vector_aggregator_config_map_name(
299-
airflow: &v1alpha2::AirflowCluster,
300-
) -> Result<Option<ConfigMapName>, Error> {
301-
airflow
302-
.spec
303-
.cluster_config
304-
.vector_aggregator_config_map_name
305-
.as_deref()
306-
.map(ConfigMapName::from_str)
307-
.transpose()
308-
.context(ParseVectorAggregatorConfigMapNameSnafu)
309-
}
310-
311295
/// Validates the logging configuration for the product container and the (optional) Vector
312296
/// container.
313297
///

rust/operator-binary/src/crd/mod.rs

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::collections::BTreeSet;
1+
use std::{collections::BTreeSet, str::FromStr};
22

33
use serde::{Deserialize, Serialize};
44
use snafu::{ResultExt, Snafu};
@@ -40,6 +40,7 @@ use stackable_operator::{
4040
config_overrides::KeyValueConfigOverrides,
4141
flask_config_writer::{FlaskAppConfigOptions, PythonType},
4242
role_utils::GenericCommonConfig,
43+
types::kubernetes::{ConfigMapName, ListenerClassName, ListenerName, SecretName},
4344
},
4445
versioned::versioned,
4546
};
@@ -264,7 +265,7 @@ pub mod versioned {
264265
/// The name of the Secret object containing the admin user credentials. Read the
265266
/// [getting started guide first steps](DOCS_BASE_URL_PLACEHOLDER/airflow/getting_started/first_steps)
266267
/// to find out more.
267-
pub credentials_secret_name: String,
268+
pub credentials_secret_name: SecretName,
268269

269270
/// The `gitSync` settings allow configuring DAGs to mount via `git-sync`. Learn more in the
270271
/// [mounting DAGs documentation](DOCS_BASE_URL_PLACEHOLDER/airflow/usage-guide/mounting-dags#_via_git_sync).
@@ -301,7 +302,7 @@ pub mod versioned {
301302
/// Follow the [logging tutorial](DOCS_BASE_URL_PLACEHOLDER/tutorials/logging-vector-aggregator)
302303
/// to learn how to configure log aggregation with Vector.
303304
#[serde(skip_serializing_if = "Option::is_none")]
304-
pub vector_aggregator_config_map_name: Option<String>,
305+
pub vector_aggregator_config_map_name: Option<ConfigMapName>,
305306

306307
/// Additional volumes to define. Use together with `volumeMounts` to mount the volumes.
307308
#[serde(default)]
@@ -322,7 +323,7 @@ pub mod versioned {
322323

323324
/// This field controls which [ListenerClass](https://docs.stackable.tech/home/nightly/listener-operator/listenerclass.html) is used to expose the webserver.
324325
#[serde(default = "webserver_default_listener_class")]
325-
pub listener_class: String,
326+
pub listener_class: ListenerClassName,
326327
}
327328
}
328329

@@ -357,8 +358,9 @@ impl Default for v1alpha2::WebserverRoleConfig {
357358
}
358359
}
359360

360-
fn webserver_default_listener_class() -> String {
361-
"cluster-internal".to_string()
361+
fn webserver_default_listener_class() -> ListenerClassName {
362+
ListenerClassName::from_str("cluster-internal")
363+
.expect("the default listener class is a valid listener class name")
362364
}
363365

364366
#[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Eq, Serialize)]
@@ -381,9 +383,12 @@ impl v1alpha2::AirflowCluster {
381383
/// The name of the group-listener provided for a specific role.
382384
/// Webservers will use this group listener so that only one load balancer
383385
/// is needed for that role.
384-
pub fn group_listener_name(&self, role: &AirflowRole) -> Option<String> {
386+
pub fn group_listener_name(&self, role: &AirflowRole) -> Option<ListenerName> {
385387
match role {
386-
AirflowRole::Webserver => Some(role_service_name(&self.name_any(), &role.to_string())),
388+
AirflowRole::Webserver => Some(
389+
ListenerName::from_str(&role_service_name(&self.name_any(), &role.to_string()))
390+
.expect("the group listener name is a valid Listener name"),
391+
),
387392
AirflowRole::Scheduler
388393
| AirflowRole::Worker
389394
| AirflowRole::DagProcessor
@@ -733,7 +738,10 @@ impl AirflowRole {
733738
.expect("an AirflowRole serialises to a valid RoleName")
734739
}
735740

736-
pub fn listener_class_name(&self, airflow: &v1alpha2::AirflowCluster) -> Option<String> {
741+
pub fn listener_class_name(
742+
&self,
743+
airflow: &v1alpha2::AirflowCluster,
744+
) -> Option<ListenerClassName> {
737745
match self {
738746
Self::Webserver => airflow
739747
.spec

rust/operator-binary/src/util.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
use stackable_operator::k8s_openapi::api::core::v1::{EnvVar, EnvVarSource, SecretKeySelector};
22

3-
pub fn env_var_from_secret(var_name: &str, secret: &str, secret_key: &str) -> EnvVar {
3+
pub fn env_var_from_secret(var_name: &str, secret: impl AsRef<str>, secret_key: &str) -> EnvVar {
44
EnvVar {
55
name: String::from(var_name),
66
value_from: Some(EnvVarSource {
77
secret_key_ref: Some(SecretKeySelector {
8-
name: String::from(secret),
8+
name: String::from(secret.as_ref()),
99
key: String::from(secret_key),
1010
..Default::default()
1111
}),

0 commit comments

Comments
 (0)