From 95be85292cc3a8e4788ebc78146c4b4f192945a9 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 09:28:01 +0200 Subject: [PATCH 01/35] feat: add merged_overrides() to AirflowCluster Computes the merged env and webserver_config.py overrides directly from the CRD (role <- role-group), replacing the product-config override path. Co-Authored-By: Claude Opus 4.8 --- rust/operator-binary/src/crd/mod.rs | 138 +++++++++++++++++++++++++++- 1 file changed, 134 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 83c54ef1..521a98f5 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet}; +use std::collections::{BTreeMap, BTreeSet, HashMap}; use product_config::flask_app_config_writer::{FlaskAppConfigOptions, PythonType}; use serde::{Deserialize, Serialize}; @@ -19,7 +19,7 @@ use stackable_operator::{ fragment::{self, Fragment, ValidationError}, merge::Merge, }, - config_overrides::KeyValueConfigOverrides, + config_overrides::{KeyValueConfigOverrides, KeyValueOverridesProvider}, crd::git_sync, deep_merger::ObjectOverrides, k8s_openapi::{ @@ -114,7 +114,7 @@ pub struct AirflowConfigOverrides { pub webserver_config_py: Option, } -impl stackable_operator::config_overrides::KeyValueOverridesProvider for AirflowConfigOverrides { +impl KeyValueOverridesProvider for AirflowConfigOverrides { fn get_key_value_overrides(&self, file: &str) -> BTreeMap> { match file { AIRFLOW_CONFIG_FILENAME => self @@ -127,6 +127,12 @@ impl stackable_operator::config_overrides::KeyValueOverridesProvider for Airflow } } +#[derive(Clone, Debug)] +pub struct MergedOverrides { + pub env_overrides: HashMap, + pub config_file_overrides: BTreeMap, +} + #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("Unknown Airflow role found {role}. Should be one of {roles:?}"))] @@ -450,6 +456,39 @@ impl v1alpha2::AirflowCluster { format!("{}-executor-pod-template", self.name_any()) } + pub fn merged_overrides( + &self, + role: &AirflowRole, + rolegroup_name: &str, + ) -> Result { + let role_config = role.role_config(self)?; + + let mut env_overrides = role_config.config.env_overrides.clone(); + let mut file_overrides = role_config + .config + .config_overrides + .get_key_value_overrides(AIRFLOW_CONFIG_FILENAME); + + if let Some(rg) = role_config.role_groups.get(rolegroup_name) { + env_overrides.extend(rg.config.env_overrides.clone()); + let rg_file = rg + .config + .config_overrides + .get_key_value_overrides(AIRFLOW_CONFIG_FILENAME); + file_overrides.extend(rg_file); + } + + let config_file_overrides = file_overrides + .into_iter() + .filter_map(|(k, v)| v.map(|v| (k, v))) + .collect(); + + Ok(MergedOverrides { + env_overrides, + config_file_overrides, + }) + } + /// Retrieve and merge resource configs for role and role groups pub fn merged_config( &self, @@ -1114,13 +1153,15 @@ pub fn build_recommended_labels<'a, T>( #[cfg(test)] mod tests { + use std::collections::BTreeMap; + use indoc::formatdoc; use stackable_operator::{ commons::product_image_selection::ResolvedProductImage, versioned::test_utils::RoundtripTestData, }; - use crate::{v1alpha1, v1alpha2}; + use crate::{crd::AirflowRole, v1alpha1, v1alpha2}; #[test] fn test_cluster_config() { @@ -1345,4 +1386,93 @@ mod tests { "# } } + + #[test] + fn merged_overrides_match_config_overrides_from_crd() { + let cluster_yaml = r#" + apiVersion: airflow.stackable.tech/v1alpha2 + kind: AirflowCluster + metadata: + name: airflow + spec: + image: + productVersion: 3.1.6 + clusterConfig: + loadExamples: false + exposeConfig: false + credentialsSecretName: airflow-admin-credentials + metadataDatabase: + postgresql: + host: airflow-postgresql + database: airflow + credentialsSecretName: airflow-postgresql-credentials + webservers: + config: {} + configOverrides: + webserver_config.py: + AUTH_TYPE: "AUTH_OID" + ROLE_ONLY_KEY: "role-value" + envOverrides: + ROLE_ENV_VAR: "role-env-value" + roleGroups: + default: + config: {} + configOverrides: + webserver_config.py: + AUTH_TYPE: "AUTH_DB" + GROUP_ONLY_KEY: "group-value" + envOverrides: + GROUP_ENV_VAR: "group-env-value" + schedulers: + config: {} + roleGroups: + default: + config: {} + kubernetesExecutors: + config: {} + "#; + + let deserializer = serde_yaml::Deserializer::from_str(cluster_yaml); + let cluster: v1alpha2::AirflowCluster = + serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); + + // Webservers/default: role-group overrides merge with (and override) role-level ones + let overrides = cluster + .merged_overrides(&AirflowRole::Webserver, "default") + .expect("merged_overrides should succeed"); + + // configOverrides: group AUTH_TYPE overrides role AUTH_TYPE, both unique keys kept + assert_eq!( + overrides.config_file_overrides, + BTreeMap::from([ + ("AUTH_TYPE".into(), "AUTH_DB".into()), + ("ROLE_ONLY_KEY".into(), "role-value".into()), + ("GROUP_ONLY_KEY".into(), "group-value".into()), + ]) + ); + + // envOverrides: both role and group env vars present + assert_eq!(overrides.env_overrides.len(), 2); + assert_eq!( + overrides.env_overrides.get("ROLE_ENV_VAR").unwrap(), + "role-env-value" + ); + assert_eq!( + overrides.env_overrides.get("GROUP_ENV_VAR").unwrap(), + "group-env-value" + ); + + // Schedulers/default: no overrides configured → both maps empty + let overrides = cluster + .merged_overrides(&AirflowRole::Scheduler, "default") + .expect("merged_overrides should succeed"); + assert!( + overrides.config_file_overrides.is_empty(), + "scheduler should have no config file overrides" + ); + assert!( + overrides.env_overrides.is_empty(), + "scheduler should have no env overrides" + ); + } } From 5ff22fb7e7d39c38fdceb5dd722ca8f6a5ade704 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 10:57:18 +0200 Subject: [PATCH 02/35] refactor: remove product-config validation from reconcile Replaces transform_all_roles_to_config / validate_all_roles_and_groups_config with direct role iteration plus merged_overrides(). Folds the dereferenced authentication/authorization objects into ValidatedAirflowCluster so downstream build steps read them from the validated cluster. The product-config crate is still used for the Flask config writer (removed in a later step). Co-Authored-By: Claude Opus 4.8 --- .../operator-binary/src/airflow_controller.rs | 48 ++++---- .../src/controller/validate.rs | 112 +++++++----------- rust/operator-binary/src/crd/authorization.rs | 2 + rust/operator-binary/src/crd/mod.rs | 30 ----- rust/operator-binary/src/env_vars.rs | 26 ++-- rust/operator-binary/src/main.rs | 8 +- 6 files changed, 73 insertions(+), 153 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index ea7dd9ad..7e6112f4 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -6,11 +6,7 @@ use std::{ }; use const_format::concatcp; -use product_config::{ - ProductConfigManager, - flask_app_config_writer::{self, FlaskAppConfigWriterError}, - types::PropertyNameKind, -}; +use product_config::flask_app_config_writer::{self, FlaskAppConfigWriterError}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ @@ -61,10 +57,7 @@ use stackable_operator::{ }, kvp::{Annotation, Label, LabelError, Labels, ObjectLabels}, logging::controller::ReconcilerError, - product_config_utils::{ - CONFIG_OVERRIDE_FILE_FOOTER_KEY, CONFIG_OVERRIDE_FILE_HEADER_KEY, env_vars_from, - env_vars_from_rolegroup_config, - }, + product_config_utils::env_vars_from, product_logging::{ self, framework::LoggingError, @@ -118,9 +111,11 @@ pub const CONTAINER_IMAGE_BASE_NAME: &str = "airflow"; pub const AIRFLOW_FULL_CONTROLLER_NAME: &str = concatcp!(AIRFLOW_CONTROLLER_NAME, '.', OPERATOR_NAME); +const CONFIG_OVERRIDE_FILE_HEADER_KEY: &str = "FILE_HEADER"; +const CONFIG_OVERRIDE_FILE_FOOTER_KEY: &str = "FILE_FOOTER"; + pub struct Ctx { pub client: stackable_operator::client::Client, - pub product_config: ProductConfigManager, pub operator_environment: OperatorEnvironmentOptions, } @@ -386,7 +381,7 @@ pub async fn reconcile_airflow( CONTAINER_IMAGE_BASE_NAME, &ctx.operator_environment.image_repository, crate::built_info::PKG_VERSION, - &ctx.product_config, + dereferenced, ) .context(ValidateSnafu)?; @@ -464,8 +459,8 @@ pub async fn reconcile_airflow( common_configuration, &metadata_database_connection_details, &validated.image, - &dereferenced.authentication_config, - &dereferenced.authorization_config, + &validated.authentication_config, + &validated.authorization_config, &mut cluster_resources, client, &rbac_sa, @@ -515,7 +510,7 @@ pub async fn reconcile_airflow( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, &validated.image, - &env_vars_from_rolegroup_config(&validated_rg_config.product_config_properties), + &env_vars_from(&validated_rg_config.overrides.env_overrides), &airflow.volume_mounts(), LOG_VOLUME_NAME, &validated_rg_config @@ -574,9 +569,9 @@ pub async fn reconcile_airflow( airflow, &validated.image, &rolegroup, - &validated_rg_config.product_config_properties, - &dereferenced.authentication_config, - &dereferenced.authorization_config, + &validated_rg_config.overrides.config_file_overrides, + &validated.authentication_config, + &validated.authorization_config, &validated_rg_config.merged_config.logging, &Container::Airflow, )?; @@ -592,9 +587,9 @@ pub async fn reconcile_airflow( &validated.image, airflow_role, &rolegroup, - &validated_rg_config.product_config_properties, - &dereferenced.authentication_config, - &dereferenced.authorization_config, + &validated_rg_config.overrides.env_overrides, + &validated.authentication_config, + &validated.authorization_config, &metadata_database_connection_details, &celery_database_connection_details, &rbac_sa, @@ -659,7 +654,7 @@ async fn build_executor_template( airflow, resolved_product_image, &rolegroup, - &HashMap::new(), + &BTreeMap::new(), authentication_config, authorization_config, &merged_executor_config.logging, @@ -709,7 +704,7 @@ fn build_rolegroup_config_map( airflow: &v1alpha2::AirflowCluster, resolved_product_image: &ResolvedProductImage, rolegroup: &RoleGroupRef, - rolegroup_config: &HashMap>, + config_file_overrides: &BTreeMap, authentication_config: &AirflowClientAuthenticationDetailsResolved, authorization_config: &AirflowAuthorizationResolved, logging: &Logging, @@ -732,10 +727,7 @@ fn build_rolegroup_config_map( config ); - let mut file_config = rolegroup_config - .get(&PropertyNameKind::File(AIRFLOW_CONFIG_FILENAME.to_string())) - .cloned() - .unwrap_or_default(); + let mut file_config = config_file_overrides.clone(); tracing::debug!( "Config overrides for {}: {:?}", @@ -884,7 +876,7 @@ fn build_server_rolegroup_statefulset( resolved_product_image: &ResolvedProductImage, airflow_role: &AirflowRole, rolegroup_ref: &RoleGroupRef, - rolegroup_config: &HashMap>, + env_overrides: &HashMap, authentication_config: &AirflowClientAuthenticationDetailsResolved, authorization_config: &AirflowAuthorizationResolved, metadata_database_connection_details: &SqlAlchemyDatabaseConnectionDetails, @@ -974,7 +966,7 @@ fn build_server_rolegroup_statefulset( env_vars::build_airflow_statefulset_envs( airflow, airflow_role, - rolegroup_config, + env_overrides, executor, authentication_config, authorization_config, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 6eb2a16c..f58da226 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -1,18 +1,18 @@ -use std::{ - collections::{BTreeMap, HashMap}, - str::FromStr, -}; +use std::collections::BTreeMap; -use product_config::{ProductConfigManager, types::PropertyNameKind}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, - product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config}, role_utils::RoleGroupRef, }; use strum::IntoEnumIterator; -use crate::crd::{AIRFLOW_CONFIG_FILENAME, AirflowConfig, AirflowExecutor, AirflowRole, v1alpha2}; +use super::dereference::DereferencedObjects; +use crate::crd::{ + AirflowConfig, AirflowExecutor, AirflowRole, MergedOverrides, + authentication::AirflowClientAuthenticationDetailsResolved, + authorization::AirflowAuthorizationResolved, v1alpha2, +}; #[derive(Snafu, Debug)] pub enum Error { @@ -21,24 +21,8 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("invalid product config"))] - InvalidProductConfig { - source: stackable_operator::product_config_utils::Error, - }, - - #[snafu(display("failed to generate product config"))] - GenerateProductConfig { - source: stackable_operator::product_config_utils::Error, - }, - #[snafu(display("failed to resolve and merge config for role and role group"))] FailedToResolveConfig { source: crate::crd::Error }, - - #[snafu(display("could not parse Airflow role [{role}]"))] - UnidentifiedAirflowRole { - source: strum::ParseError, - role: String, - }, } /// Per-role configuration extracted during validation. @@ -49,21 +33,24 @@ pub struct ValidatedRoleConfig { pub group_listener_name: Option, } -/// Per-rolegroup configuration: the merged CRD config plus the product-config properties. +/// Per-rolegroup configuration: the merged CRD config plus overrides. #[derive(Clone, Debug)] pub struct ValidatedRoleGroupConfig { pub merged_config: AirflowConfig, - pub product_config_properties: HashMap>, + pub overrides: MergedOverrides, } -/// The validated cluster: proves that product-config validation and config merging -/// succeeded for every role and role group before any resources are created. +/// The validated cluster: proves that config merging succeeded for every role and +/// role group before any resources are created. It also carries the dereferenced +/// external references, so every downstream build step reads them from here. #[derive(Clone, Debug)] pub struct ValidatedAirflowCluster { pub image: ResolvedProductImage, pub role_groups: BTreeMap>, pub role_configs: BTreeMap, pub executor: AirflowExecutor, + pub authentication_config: AirflowClientAuthenticationDetailsResolved, + pub authorization_config: AirflowAuthorizationResolved, } pub fn validate_cluster( @@ -71,7 +58,7 @@ pub fn validate_cluster( image_base_name: &str, image_repository: &str, pkg_version: &str, - product_config_manager: &ProductConfigManager, + dereferenced: DereferencedObjects, ) -> Result { let resolved_product_image = airflow .spec @@ -79,85 +66,66 @@ pub fn validate_cluster( .resolve(image_base_name, image_repository, pkg_version) .context(ResolveProductImageSnafu)?; - let mut roles = HashMap::new(); + let mut role_groups = BTreeMap::new(); + let mut role_configs = BTreeMap::new(); // if the kubernetes executor is specified there will be no worker role as the pods // are provisioned by airflow as defined by the task (default: one pod per task) for role in AirflowRole::iter() { - if let Some(resolved_role) = airflow.get_role(&role) { - roles.insert( - role.to_string(), - ( - vec![ - PropertyNameKind::Env, - PropertyNameKind::File(AIRFLOW_CONFIG_FILENAME.into()), - ], - resolved_role.clone(), - ), - ); - } - } - - let role_config = transform_all_roles_to_config(airflow, &roles); - let validated_role_config = validate_all_roles_and_groups_config( - &resolved_product_image.product_version, - &role_config.context(GenerateProductConfigSnafu)?, - product_config_manager, - false, - false, - ) - .context(InvalidProductConfigSnafu)?; - - let mut role_groups = BTreeMap::new(); - let mut role_configs = BTreeMap::new(); - - for (role_name, rolegroup_configs) in validated_role_config.iter() { - let airflow_role = - AirflowRole::from_str(role_name).context(UnidentifiedAirflowRoleSnafu { - role: role_name.to_string(), - })?; + let Some(resolved_role) = airflow.get_role(&role) else { + continue; + }; role_configs.insert( - airflow_role.clone(), + role.clone(), ValidatedRoleConfig { pdb: airflow - .role_config(&airflow_role) + .role_config(&role) .map(|rc| rc.pod_disruption_budget), - listener_class: airflow_role - .listener_class_name(airflow) - .map(|s| s.to_string()), - group_listener_name: airflow.group_listener_name(&airflow_role), + listener_class: role.listener_class_name(airflow).map(|s| s.to_string()), + group_listener_name: airflow.group_listener_name(&role), }, ); let mut group_configs = BTreeMap::new(); - for (rolegroup_name, rolegroup_config) in rolegroup_configs.iter() { + for rolegroup_name in resolved_role.role_groups.keys() { let rolegroup_ref = RoleGroupRef { cluster: stackable_operator::kube::runtime::reflector::ObjectRef::from_obj(airflow), - role: role_name.into(), + role: role.to_string(), role_group: rolegroup_name.into(), }; let merged_config = airflow - .merged_config(&airflow_role, &rolegroup_ref) + .merged_config(&role, &rolegroup_ref) + .context(FailedToResolveConfigSnafu)?; + + let overrides = airflow + .merged_overrides(&role, rolegroup_name) .context(FailedToResolveConfigSnafu)?; group_configs.insert( rolegroup_name.clone(), ValidatedRoleGroupConfig { merged_config, - product_config_properties: rolegroup_config.clone(), + overrides, }, ); } - role_groups.insert(airflow_role, group_configs); + role_groups.insert(role, group_configs); } + let DereferencedObjects { + authentication_config, + authorization_config, + } = dereferenced; + Ok(ValidatedAirflowCluster { image: resolved_product_image, role_groups, role_configs, executor: airflow.spec.executor.clone(), + authentication_config, + authorization_config, }) } diff --git a/rust/operator-binary/src/crd/authorization.rs b/rust/operator-binary/src/crd/authorization.rs index 2e15912c..0fc0726c 100644 --- a/rust/operator-binary/src/crd/authorization.rs +++ b/rust/operator-binary/src/crd/authorization.rs @@ -2,6 +2,7 @@ use stackable_operator::{client::Client, commons::opa::OpaApiVersion, shared::ti use crate::crd::{AirflowAuthorization, AirflowOpaConfig, v1alpha2}; +#[derive(Clone, Debug)] pub struct AirflowAuthorizationResolved { pub opa: Option, } @@ -24,6 +25,7 @@ impl AirflowAuthorizationResolved { } } +#[derive(Clone, Debug)] pub struct OpaConfigResolved { pub connection_string: String, pub cache_entry_time_to_live: Duration, diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 521a98f5..c3a416a9 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -29,7 +29,6 @@ use stackable_operator::{ kube::{CustomResource, ResourceExt}, kvp::ObjectLabels, memory::{BinaryMultiple, MemoryQuantity}, - product_config_utils::{self, Configuration}, product_logging::{ self, framework::{create_vector_shutdown_file_command, remove_vector_shutdown_file_command}, @@ -1042,35 +1041,6 @@ impl AirflowConfig { } } -impl Configuration for AirflowConfigFragment { - type Configurable = v1alpha2::AirflowCluster; - - fn compute_env( - &self, - _cluster: &Self::Configurable, - _role_name: &str, - ) -> Result>, product_config_utils::Error> { - Ok(BTreeMap::new()) - } - - fn compute_cli( - &self, - _cluster: &Self::Configurable, - _role_name: &str, - ) -> Result>, product_config_utils::Error> { - Ok(BTreeMap::new()) - } - - fn compute_files( - &self, - _cluster: &Self::Configurable, - _role_name: &str, - _file: &str, - ) -> Result>, product_config_utils::Error> { - Ok(BTreeMap::new()) - } -} - fn default_resources(role: &AirflowRole) -> ResourcesFragment { let (cpu, memory) = match role { AirflowRole::Worker => ( diff --git a/rust/operator-binary/src/env_vars.rs b/rust/operator-binary/src/env_vars.rs index b4828dbe..595eb1ec 100644 --- a/rust/operator-binary/src/env_vars.rs +++ b/rust/operator-binary/src/env_vars.rs @@ -3,7 +3,6 @@ use std::{ path::PathBuf, }; -use product_config::types::PropertyNameKind; use snafu::Snafu; use stackable_operator::{ commons::product_image_selection::ResolvedProductImage, @@ -79,7 +78,7 @@ pub enum Error { pub fn build_airflow_statefulset_envs( airflow: &v1alpha2::AirflowCluster, airflow_role: &AirflowRole, - rolegroup_config: &HashMap>, + env_overrides: &HashMap, executor: &AirflowExecutor, auth_config: &AirflowClientAuthenticationDetailsResolved, authorization_config: &AirflowAuthorizationResolved, @@ -96,9 +95,6 @@ pub fn build_airflow_statefulset_envs( env.extend(static_envs(git_sync_resources)); - // environment variables - let env_vars = rolegroup_config.get(&PropertyNameKind::Env); - add_version_specific_env_vars(airflow, airflow_role, resolved_product_image, &mut env); // N.B. this has been deprecated and replaced with AIRFLOW__API__SECRET_KEY since 3.0.2. Can be removed when 3.0.1 is no longer supported. @@ -266,17 +262,15 @@ pub fn build_airflow_statefulset_envs( } // apply overrides last of all with a fixed ordering - if let Some(env_vars) = env_vars { - for (k, v) in env_vars.iter().collect::>() { - env.insert( - k.into(), - EnvVar { - name: k.to_string(), - value: Some(v.to_string()), - ..Default::default() - }, - ); - } + for (k, v) in env_overrides.iter().collect::>() { + env.insert( + k.into(), + EnvVar { + name: k.to_string(), + value: Some(v.to_string()), + ..Default::default() + }, + ); } // Needed for the `containerdebug` process to log it's tracing information to. diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index dee5603b..469a7240 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -71,11 +71,11 @@ async fn main() -> anyhow::Result<()> { .print_yaml_schema(built_info::PKG_VERSION, &SerializeOptions::default())?; } Command::Run(RunArguments { - product_config, watch_namespace, operator_environment, maintenance, common, + .. }) => { // NOTE (@NickLarsenNZ): Before stackable-telemetry was used: // - The console log level was set by `AIRFLOW_OPERATOR_LOG`, and is now `CONSOLE_LOG` (when using Tracing::pre_configured). @@ -103,11 +103,6 @@ async fn main() -> anyhow::Result<()> { .run(sigterm_watcher.handle()) .map(anyhow::Ok); - let product_config = product_config.load(&[ - "deploy/config-spec/properties.yaml", - "/etc/stackable/airflow-operator/config-spec/properties.yaml", - ])?; - let client = stackable_operator::client::initialize_operator( Some(OPERATOR_NAME.to_string()), &common.cluster_info, @@ -183,7 +178,6 @@ async fn main() -> anyhow::Result<()> { Arc::new(airflow_controller::Ctx { client: client.clone(), operator_environment, - product_config, }), ) // We can let the reporting happen in the background From 1f4e3513db9614d236feba316dace59fd7b88eec Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 11:06:22 +0200 Subject: [PATCH 03/35] docs: remove product-config CLI parameter Drops the defunct --product-config section from the commandline reference and notes the removal of product-config validation in the changelog. Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 1 + .../pages/reference/commandline-parameters.adoc | 13 ------------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e46705f..c6093e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,7 @@ The results backend `spec.celeryExecutors.resultBackend` is now `spec.clusterConfig.celeryResultsBackend`. The broker `spec.celeryExecutors.broker` is now `spec.clusterConfig.celeryBroker`. - Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#795]). +- Removed the product-config based configuration validation. Config and environment overrides are now merged directly from the CRD, and the dereferenced objects are carried on the validated cluster. The `--product-config` CLI flag is now a no-op ([#XXX]). - test: Bump vector-aggregator to 0.55.0, replace /graphql call with gRPC call ([#801]). ### Fixed diff --git a/docs/modules/airflow/pages/reference/commandline-parameters.adoc b/docs/modules/airflow/pages/reference/commandline-parameters.adoc index 7a44b610..b41a07bf 100644 --- a/docs/modules/airflow/pages/reference/commandline-parameters.adoc +++ b/docs/modules/airflow/pages/reference/commandline-parameters.adoc @@ -2,19 +2,6 @@ This operator accepts the following command line parameters: -== product-config - -*Default value*: `/etc/stackable/airflow-operator/config-spec/properties.yaml` - -*Required*: false - -*Multiple values:* false - -[source] ----- -stackable-airflow-operator run --product-config /foo/bar/properties.yaml ----- - == watch-namespace *Default value*: All namespaces From 6667f36358cac0073ad0e05d4b9badb6cbee026c Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 11:33:15 +0200 Subject: [PATCH 04/35] feat: remove product-config crate dependency Vendors the Flask Python-config writer locally (flask_config_writer) and drops the airflow-operator's direct product-config dependency. The rendered webserver_config.py is unchanged (the vendored writer is byte-for-byte faithful to the crate). product-config remains a transitive dependency via stackable-operator. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 1 - Cargo.nix | 4 - Cargo.toml | 1 - rust/operator-binary/Cargo.toml | 1 - .../operator-binary/src/airflow_controller.rs | 4 +- rust/operator-binary/src/crd/mod.rs | 2 +- .../src/flask_config_writer.rs | 325 ++++++++++++++++++ rust/operator-binary/src/main.rs | 1 + 8 files changed, 329 insertions(+), 10 deletions(-) create mode 100644 rust/operator-binary/src/flask_config_writer.rs diff --git a/Cargo.lock b/Cargo.lock index d6e8310c..64a01dc3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2905,7 +2905,6 @@ dependencies = [ "fnv", "futures 0.3.32", "indoc", - "product-config", "rstest", "serde", "serde_json", diff --git a/Cargo.nix b/Cargo.nix index 5ecf9a54..3ea6cbdc 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -9575,10 +9575,6 @@ rec { name = "indoc"; packageId = "indoc"; } - { - name = "product-config"; - packageId = "product-config"; - } { name = "serde"; packageId = "serde"; diff --git a/Cargo.toml b/Cargo.toml index d04c1f1e..d686760b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ edition = "2021" repository = "https://github.com/stackabletech/airflow-operator" [workspace.dependencies] -product-config = { git = "https://github.com/stackabletech/product-config.git", tag = "0.8.0" } stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = [ "crds", "webhook", diff --git a/rust/operator-binary/Cargo.toml b/rust/operator-binary/Cargo.toml index 2734bb4e..92feeb34 100644 --- a/rust/operator-binary/Cargo.toml +++ b/rust/operator-binary/Cargo.toml @@ -9,7 +9,6 @@ repository.workspace = true publish = false [dependencies] -product-config.workspace = true stackable-operator.workspace = true anyhow.workspace = true diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 7e6112f4..c803b705 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -6,7 +6,6 @@ use std::{ }; use const_format::concatcp; -use product_config::flask_app_config_writer::{self, FlaskAppConfigWriterError}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ builder::{ @@ -93,6 +92,7 @@ use crate::{ v1alpha2, }, env_vars::{self, build_airflow_template_envs}, + flask_config_writer::{self, FlaskAppConfigWriterError}, operations::{ graceful_shutdown::{ add_airflow_graceful_shutdown_config, add_executor_graceful_shutdown_config, @@ -754,7 +754,7 @@ fn build_rolegroup_config_map( let temp_file_footer: Option = config.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); - flask_app_config_writer::write::( + flask_config_writer::write::( &mut config_file, config.iter(), PYTHON_IMPORTS, diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index c3a416a9..ea8867a7 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -1,6 +1,5 @@ use std::collections::{BTreeMap, BTreeSet, HashMap}; -use product_config::flask_app_config_writer::{FlaskAppConfigOptions, PythonType}; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt, Snafu}; use stackable_operator::{ @@ -56,6 +55,7 @@ use crate::{ CeleryBrokerConnection, CeleryResultBackendConnection, MetadataDatabaseConnection, }, }, + flask_config_writer::{FlaskAppConfigOptions, PythonType}, util::role_service_name, }; diff --git a/rust/operator-binary/src/flask_config_writer.rs b/rust/operator-binary/src/flask_config_writer.rs new file mode 100644 index 00000000..e2639c28 --- /dev/null +++ b/rust/operator-binary/src/flask_config_writer.rs @@ -0,0 +1,325 @@ +//! Writer for Flask App configurations (Python config files). +//! +//! Vendored from `product_config::flask_app_config_writer` so airflow-operator can +//! drop the `product-config` crate dependency. Applications based on the Flask App +//! Builder (e.g. Apache Airflow) use configuration files written in Python. This +//! writer only covers top-level assignments of a few primitive types and +//! expressions — it is not a general Python code generator. +//! +//! Primitive types are escaped accordingly. Python expressions are written as-is; +//! invalid expressions produce invalid configuration files. Config overrides that do +//! not map to a known option are treated as plain expressions. + +use std::{ + io::{self, Write}, + num::ParseIntError, + str::{FromStr, ParseBoolError}, +}; + +use snafu::{ResultExt, Snafu}; + +/// Errors which can occur when using this module +// Variant names share the `Error` suffix; kept as-is from the vendored +// `product_config::flask_app_config_writer` source. +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Snafu)] +pub enum FlaskAppConfigWriterError { + #[snafu(display("failed to convert '{value}' into a identifier"))] + ConvertIdentifierError { value: String }, + + #[snafu(display("failed to convert '{value}' into a boolean literal"))] + ConvertBoolLiteralError { + value: String, + source: ParseBoolError, + }, + + #[snafu(display("failed to convert '{value}' into an integer literal"))] + ConvertIntLiteralError { + value: String, + source: ParseIntError, + }, + + #[snafu(display("failed to convert '{value}' into an ASCII string literal"))] + ConvertStringLiteralError { value: String }, + + #[snafu(display("failed to convert '{value}' into a Python expression"))] + ConvertExpressionError { value: String }, + + #[snafu(display("Configuration cannot be written."))] + WriteConfigError { source: io::Error }, +} + +/// Mapping from configuration options to Python types. +pub trait FlaskAppConfigOptions { + fn python_type(&self) -> PythonType; +} + +/// All supported Python types +pub enum PythonType { + /// Python identifier + Identifier, + /// Boolean literal + BoolLiteral, + /// Integer literal + IntLiteral, + /// ASCII string literal + StringLiteral, + /// Python expression + Expression, +} + +impl PythonType { + /// Converts the given string to Python. + fn convert_to_python(&self, value: &str) -> Result { + let convert = match self { + PythonType::Identifier => PythonType::convert_to_python_identifier, + PythonType::BoolLiteral => PythonType::convert_to_python_bool_literal, + PythonType::IntLiteral => PythonType::convert_to_python_int_literal, + PythonType::StringLiteral => PythonType::convert_to_python_string_literal, + PythonType::Expression => PythonType::convert_to_python_expression, + }; + + convert(value) + } + + fn convert_to_python_identifier(value: &str) -> Result { + if value.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') + && value + .chars() + .next() + .filter(|c| !c.is_ascii_digit()) + .is_some() + { + Ok(value.to_string()) + } else { + ConvertIdentifierSnafu { value }.fail() + } + } + + fn convert_to_python_bool_literal(value: &str) -> Result { + value + .parse::() + .map(|b| if b { "True".into() } else { "False".into() }) + .context(ConvertBoolLiteralSnafu { value }) + } + + fn convert_to_python_int_literal(value: &str) -> Result { + value + .parse::() + .map(|i| i.to_string()) + .context(ConvertIntLiteralSnafu { value }) + } + + fn convert_to_python_string_literal(value: &str) -> Result { + if value.is_ascii() { + Ok(format!("\"{}\"", value.escape_default())) + } else { + ConvertStringLiteralSnafu { value }.fail() + } + } + + fn convert_to_python_expression(value: &str) -> Result { + if !value.trim().is_empty() { + Ok(value.to_string()) + } else { + ConvertExpressionSnafu { value }.fail() + } + } +} + +/// Writes a configuration file according to the given `FlaskAppConfigOptions` type. +pub fn write<'a, O, P, W>( + writer: &mut W, + properties: P, + imports: &[&str], +) -> Result<(), FlaskAppConfigWriterError> +where + O: FlaskAppConfigOptions + FromStr, + P: Iterator, + W: Write, +{ + for import in imports { + writeln!(writer, "{import}").context(WriteConfigSnafu)?; + } + + writeln!(writer).context(WriteConfigSnafu)?; + + for (name, value) in properties { + let variable = PythonType::Identifier.convert_to_python(name)?; + + // If an option cannot be mapped to a Python type then it is a config override and treated + // as Python expression. + let content = O::from_str(name) + .map(|option| option.python_type()) + .unwrap_or(PythonType::Expression) + .convert_to_python(value)?; + + writeln!(writer, "{variable} = {content}").context(WriteConfigSnafu)?; + } + + Ok(()) +} + +#[cfg(test)] +mod tests { + use std::{ + collections::BTreeMap, + str::{FromStr, from_utf8}, + }; + + use rstest::*; + + use super::{FlaskAppConfigOptions, FlaskAppConfigWriterError, PythonType, write}; + + #[rstest] + #[case::valid_identifiers_are_converted_to_python( + PythonType::Identifier, &[ + ("_", "_"), + ("a", "a"), + ("A", "A"), + ("__", "__"), + ("_a", "_a"), + ("_A", "_A"), + ("_0", "_0"), + ("SECRET_KEY", "SECRET_KEY"), + ] + )] + #[case::valid_booleans_are_converted_to_python( + PythonType::BoolLiteral, &[ + ("False", "false"), + ("True", "true"), + ] + )] + #[case::valid_integers_are_converted_to_python( + PythonType::IntLiteral, &[ + ("-9223372036854775808", "-9223372036854775808"), + ("0", "0"), + ("9223372036854775807", "9223372036854775807"), + ] + )] + #[case::valid_strings_are_converted_to_python( + PythonType::StringLiteral, &[ + (r#""""#, ""), + (r#"" ~""#, " ~"), + (r#""\t\r\n\'\"\\""#, "\t\r\n'\"\\"), + ] + )] + #[case::valid_expressions_are_converted_to_python( + PythonType::Expression, &[ + ("os.environ[\"HOME\"]", "os.environ[\"HOME\"]"), + ] + )] + fn valid_values_are_converted_to_python( + #[case] python_type: PythonType, + #[case] values: &[(&str, &str)], + ) -> Result<(), FlaskAppConfigWriterError> { + for (expected, input) in values { + assert_eq!(*expected, python_type.convert_to_python(input)?); + } + + Ok(()) + } + + #[rstest] + #[case::invalid_identifiers_are_not_converted_to_python( + PythonType::Identifier, &[ + "", "0", "-", "\n", "_-", "_\n", + ] + )] + #[case::invalid_booleans_are_not_converted_to_python( + PythonType::BoolLiteral, &[ + "", "False", "True", "0", "1", + ] + )] + #[case::invalid_integers_are_not_converted_to_python( + PythonType::IntLiteral, &[ + "", "a", "0x10", "inf", + ] + )] + #[case::invalid_strings_are_not_converted_to_python( + PythonType::StringLiteral, &[ + "ä", "❤" + ] + )] + #[case::invalid_expressions_are_not_converted_to_python( + PythonType::Expression, &[ + "" + ] + )] + fn invalid_values_are_converted_to_python( + #[case] python_type: PythonType, + #[case] values: &[&str], + ) { + for input in values { + assert!(python_type.convert_to_python(input).is_err()); + } + } + + #[test] + fn valid_options_are_written_into_a_configuration() -> Result<(), FlaskAppConfigWriterError> { + #[allow(clippy::enum_variant_names)] + enum Options { + BoolOption, + IntOption, + StringOption, + ExpressionOption, + _UnusedOption, + } + + impl FromStr for Options { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "BOOL_OPTION" => Ok(Options::BoolOption), + "INT_OPTION" => Ok(Options::IntOption), + "STRING_OPTION" => Ok(Options::StringOption), + "EXPRESSION_OPTION" => Ok(Options::ExpressionOption), + _ => Err("unknown option"), + } + } + } + + impl FlaskAppConfigOptions for Options { + fn python_type(&self) -> PythonType { + match self { + Options::BoolOption => PythonType::BoolLiteral, + Options::IntOption => PythonType::IntLiteral, + Options::StringOption => PythonType::StringLiteral, + Options::ExpressionOption => PythonType::Expression, + Options::_UnusedOption => PythonType::Expression, + } + } + } + + let config: BTreeMap<_, _> = [ + ("BOOL_OPTION", "true"), + ("INT_OPTION", "0"), + ("STRING_OPTION", ""), + ("EXPRESSION_OPTION", "{ \"key\": \"value\" }"), + ("OVERRIDDEN_OPTION", "None"), + ] + .map(|(k, v)| (k.to_string(), v.to_string())) + .into(); + + let imports = ["import module", "from module import member"]; + + let mut config_file = Vec::new(); + write::(&mut config_file, config.iter(), &imports)?; + + assert_eq!( + r#"import module +from module import member + +BOOL_OPTION = True +EXPRESSION_OPTION = { "key": "value" } +INT_OPTION = 0 +OVERRIDDEN_OPTION = None +STRING_OPTION = "" +"#, + from_utf8(&config_file).unwrap() + ); + + Ok(()) + } +} diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 469a7240..add0ad9c 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -44,6 +44,7 @@ mod controller; mod controller_commons; mod crd; mod env_vars; +mod flask_config_writer; mod operations; mod product_logging; mod service; From 62c00548235a9b6b9539382e2776208ba7663000 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 12:09:21 +0200 Subject: [PATCH 05/35] refactor: move vendored Flask writer to framework/flask_app_config_writer Renames the vendored writer back to its upstream name and groups it under a `framework` module (mirroring the convention in trino-operator), signalling it as vendored code that is a candidate for a shared crate. The same writer is still used by superset-operator via the product-config crate. No behaviour change; the writer body is unchanged from the crate source. Co-Authored-By: Claude Opus 4.8 --- rust/operator-binary/src/airflow_controller.rs | 4 ++-- rust/operator-binary/src/crd/mod.rs | 2 +- rust/operator-binary/src/framework.rs | 8 ++++++++ .../flask_app_config_writer.rs} | 16 +++++++++++----- rust/operator-binary/src/main.rs | 2 +- 5 files changed, 23 insertions(+), 9 deletions(-) create mode 100644 rust/operator-binary/src/framework.rs rename rust/operator-binary/src/{flask_config_writer.rs => framework/flask_app_config_writer.rs} (92%) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index c803b705..8ea806fb 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -92,7 +92,7 @@ use crate::{ v1alpha2, }, env_vars::{self, build_airflow_template_envs}, - flask_config_writer::{self, FlaskAppConfigWriterError}, + framework::flask_app_config_writer::{self, FlaskAppConfigWriterError}, operations::{ graceful_shutdown::{ add_airflow_graceful_shutdown_config, add_executor_graceful_shutdown_config, @@ -754,7 +754,7 @@ fn build_rolegroup_config_map( let temp_file_footer: Option = config.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); - flask_config_writer::write::( + flask_app_config_writer::write::( &mut config_file, config.iter(), PYTHON_IMPORTS, diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index ea8867a7..662e4826 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -55,7 +55,7 @@ use crate::{ CeleryBrokerConnection, CeleryResultBackendConnection, MetadataDatabaseConnection, }, }, - flask_config_writer::{FlaskAppConfigOptions, PythonType}, + framework::flask_app_config_writer::{FlaskAppConfigOptions, PythonType}, util::role_service_name, }; diff --git a/rust/operator-binary/src/framework.rs b/rust/operator-binary/src/framework.rs new file mode 100644 index 00000000..bf913815 --- /dev/null +++ b/rust/operator-binary/src/framework.rs @@ -0,0 +1,8 @@ +//! Local helpers vendored from upstream crates, grouped here as candidates for a +//! future shared home (e.g. operator-rs) once one exists. +//! +//! Mirrors the `framework` module convention used by other Stackable operators +//! (e.g. trino-operator) for code that is intentionally duplicated until it can +//! be upstreamed. + +pub mod flask_app_config_writer; diff --git a/rust/operator-binary/src/flask_config_writer.rs b/rust/operator-binary/src/framework/flask_app_config_writer.rs similarity index 92% rename from rust/operator-binary/src/flask_config_writer.rs rename to rust/operator-binary/src/framework/flask_app_config_writer.rs index e2639c28..2af7efe0 100644 --- a/rust/operator-binary/src/flask_config_writer.rs +++ b/rust/operator-binary/src/framework/flask_app_config_writer.rs @@ -1,14 +1,20 @@ //! Writer for Flask App configurations (Python config files). //! -//! Vendored from `product_config::flask_app_config_writer` so airflow-operator can -//! drop the `product-config` crate dependency. Applications based on the Flask App -//! Builder (e.g. Apache Airflow) use configuration files written in Python. This -//! writer only covers top-level assignments of a few primitive types and -//! expressions — it is not a general Python code generator. +//! Vendored verbatim from `product_config::flask_app_config_writer` so +//! airflow-operator can drop the `product-config` crate dependency. Applications +//! based on the Flask App Builder (e.g. Apache Airflow, Apache Superset) use +//! configuration files written in Python. This writer only covers top-level +//! assignments of a few primitive types and expressions — it is not a general +//! Python code generator. //! //! Primitive types are escaped accordingly. Python expressions are written as-is; //! invalid expressions produce invalid configuration files. Config overrides that do //! not map to a known option are treated as plain expressions. +//! +// TODO: This is vendored, not airflow-specific. superset-operator still depends on +// `product_config::flask_app_config_writer`; this writer is a candidate for a shared +// crate (e.g. operator-rs) so both operators can drop the product-config crate. +// Until such a home exists it is duplicated here, kept identical to the upstream source. use std::{ io::{self, Write}, diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index add0ad9c..0caa3796 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -44,7 +44,7 @@ mod controller; mod controller_commons; mod crd; mod env_vars; -mod flask_config_writer; +mod framework; mod operations; mod product_logging; mod service; From 4885d36f0d8406a44bc77e78dd7c026519d986fd Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 13:37:12 +0200 Subject: [PATCH 06/35] replace usage of product_config_utils::env_vars_from with inline calls, as done with trino --- .../operator-binary/src/airflow_controller.rs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 8ea806fb..81dcf8f8 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -42,7 +42,7 @@ use stackable_operator::{ api::{ apps::v1::{StatefulSet, StatefulSetSpec}, core::v1::{ - ConfigMap, PersistentVolumeClaim, PodTemplateSpec, Probe, ServiceAccount, + ConfigMap, EnvVar, PersistentVolumeClaim, PodTemplateSpec, Probe, ServiceAccount, TCPSocketAction, }, }, @@ -56,7 +56,6 @@ use stackable_operator::{ }, kvp::{Annotation, Label, LabelError, Labels, ObjectLabels}, logging::controller::ReconcilerError, - product_config_utils::env_vars_from, product_logging::{ self, framework::LoggingError, @@ -510,7 +509,16 @@ pub async fn reconcile_airflow( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, &validated.image, - &env_vars_from(&validated_rg_config.overrides.env_overrides), + &validated_rg_config + .overrides + .env_overrides + .iter() + .map(|(k, v)| EnvVar { + name: k.clone(), + value: Some(v.clone()), + ..EnvVar::default() + }) + .collect::>(), &airflow.volume_mounts(), LOG_VOLUME_NAME, &validated_rg_config @@ -670,7 +678,15 @@ async fn build_executor_template( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, resolved_product_image, - &env_vars_from(&common_config.env_overrides), + &common_config + .env_overrides + .iter() + .map(|(k, v)| EnvVar { + name: k.clone(), + value: Some(v.clone()), + ..EnvVar::default() + }) + .collect::>(), &airflow.volume_mounts(), LOG_VOLUME_NAME, &merged_executor_config From 2bd1f79364406f90da4676491581a3f6537ccd78 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 13:41:57 +0200 Subject: [PATCH 07/35] changelog update --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6093e94..15fab1eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,8 +23,10 @@ The results backend `spec.celeryExecutors.resultBackend` is now `spec.clusterConfig.celeryResultsBackend`. The broker `spec.celeryExecutors.broker` is now `spec.clusterConfig.celeryBroker`. - Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#795]). -- Removed the product-config based configuration validation. Config and environment overrides are now merged directly from the CRD, and the dereferenced objects are carried on the validated cluster. The `--product-config` CLI flag is now a no-op ([#XXX]). - test: Bump vector-aggregator to 0.55.0, replace /graphql call with gRPC call ([#801]). +- Removed the product-config based configuration validation. + Config and environment overrides are now merged directly from the CRD, and dereferenced objects are added to the validated cluster. + The `--product-config` CLI flag is now a no-op ([#804]). ### Fixed @@ -44,6 +46,7 @@ [#795]: https://github.com/stackabletech/airflow-operator/pull/795 [#800]: https://github.com/stackabletech/airflow-operator/pull/800 [#801]: https://github.com/stackabletech/airflow-operator/pull/801 +[#804]: https://github.com/stackabletech/airflow-operator/pull/804 ## [26.3.0] - 2026-03-16 From 18f138fbb5452ef1ded601813e4e5461caee62bd Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 15:10:50 +0200 Subject: [PATCH 08/35] refactor: move vendored writer to config/writer, drop framework module Relocates the vendored Flask config writer from framework/flask_app_config_writer to config/writer, matching hdfs-operator's config/writer.rs convention (no operator keeps a vendored writer under framework/, which trino reserves for v2 upstream mirrors). Converts config.rs into a config/ module. No behaviour change. Co-Authored-By: Claude Opus 4.8 --- rust/operator-binary/src/airflow_controller.rs | 18 ++++++++---------- .../src/{config.rs => config/mod.rs} | 2 ++ .../writer.rs} | 0 rust/operator-binary/src/crd/mod.rs | 2 +- rust/operator-binary/src/framework.rs | 8 -------- rust/operator-binary/src/main.rs | 1 - 6 files changed, 11 insertions(+), 20 deletions(-) rename rust/operator-binary/src/{config.rs => config/mod.rs} (99%) rename rust/operator-binary/src/{framework/flask_app_config_writer.rs => config/writer.rs} (100%) delete mode 100644 rust/operator-binary/src/framework.rs diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 81dcf8f8..655b665f 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -72,7 +72,10 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - config::{self, PYTHON_IMPORTS}, + config::{ + self, PYTHON_IMPORTS, + writer::{self, FlaskAppConfigWriterError}, + }, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ self, AIRFLOW_CONFIG_FILENAME, APP_NAME, AirflowClusterStatus, AirflowConfig, @@ -91,7 +94,6 @@ use crate::{ v1alpha2, }, env_vars::{self, build_airflow_template_envs}, - framework::flask_app_config_writer::{self, FlaskAppConfigWriterError}, operations::{ graceful_shutdown::{ add_airflow_graceful_shutdown_config, add_executor_graceful_shutdown_config, @@ -770,14 +772,10 @@ fn build_rolegroup_config_map( let temp_file_footer: Option = config.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); - flask_app_config_writer::write::( - &mut config_file, - config.iter(), - PYTHON_IMPORTS, - ) - .with_context(|_| BuildRoleGroupConfigFileSnafu { - rolegroup: rolegroup.clone(), - })?; + writer::write::(&mut config_file, config.iter(), PYTHON_IMPORTS) + .with_context(|_| BuildRoleGroupConfigFileSnafu { + rolegroup: rolegroup.clone(), + })?; if let Some(footer) = temp_file_footer { writeln!(config_file, "{}", footer).context(WriteToConfigFileStringSnafu)?; diff --git a/rust/operator-binary/src/config.rs b/rust/operator-binary/src/config/mod.rs similarity index 99% rename from rust/operator-binary/src/config.rs rename to rust/operator-binary/src/config/mod.rs index a3cd15ab..3792f4ba 100644 --- a/rust/operator-binary/src/config.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -1,3 +1,5 @@ +pub mod writer; + use std::collections::BTreeMap; use indoc::formatdoc; diff --git a/rust/operator-binary/src/framework/flask_app_config_writer.rs b/rust/operator-binary/src/config/writer.rs similarity index 100% rename from rust/operator-binary/src/framework/flask_app_config_writer.rs rename to rust/operator-binary/src/config/writer.rs diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 662e4826..919a879c 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -45,6 +45,7 @@ use stackable_operator::{ use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; use crate::{ + config::writer::{FlaskAppConfigOptions, PythonType}, crd::{ affinity::{get_affinity, get_executor_affinity}, authentication::{ @@ -55,7 +56,6 @@ use crate::{ CeleryBrokerConnection, CeleryResultBackendConnection, MetadataDatabaseConnection, }, }, - framework::flask_app_config_writer::{FlaskAppConfigOptions, PythonType}, util::role_service_name, }; diff --git a/rust/operator-binary/src/framework.rs b/rust/operator-binary/src/framework.rs deleted file mode 100644 index bf913815..00000000 --- a/rust/operator-binary/src/framework.rs +++ /dev/null @@ -1,8 +0,0 @@ -//! Local helpers vendored from upstream crates, grouped here as candidates for a -//! future shared home (e.g. operator-rs) once one exists. -//! -//! Mirrors the `framework` module convention used by other Stackable operators -//! (e.g. trino-operator) for code that is intentionally duplicated until it can -//! be upstreamed. - -pub mod flask_app_config_writer; diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 0caa3796..469a7240 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -44,7 +44,6 @@ mod controller; mod controller_commons; mod crd; mod env_vars; -mod framework; mod operations; mod product_logging; mod service; From 9ab5f01d274a0dc30ab86134f97349df12485d0a Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 15:23:28 +0200 Subject: [PATCH 09/35] refactor: extract webserver_config.py builder into config/webserver_config Moves the webserver_config.py rendering (defaults + config overrides + the FILE_HEADER/FILE_FOOTER python blocks + the Flask writer call) out of build_rolegroup_config_map into a dedicated config::webserver_config::build(). The header/footer key constants and the related error variants move with it. Drops three debug! traces of intermediate config maps that no longer have a call site after the extraction; the rendered webserver_config.py is unchanged. Co-Authored-By: Claude Opus 4.8 --- .../operator-binary/src/airflow_controller.rs | 89 +++---------------- rust/operator-binary/src/config/mod.rs | 1 + .../src/config/webserver_config.rs | 76 ++++++++++++++++ 3 files changed, 91 insertions(+), 75 deletions(-) create mode 100644 rust/operator-binary/src/config/webserver_config.rs diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 655b665f..b7d2ee49 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -1,7 +1,6 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha2::AirflowCluster`] use std::{ collections::{BTreeMap, BTreeSet, HashMap}, - io::Write, sync::Arc, }; @@ -72,17 +71,14 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - config::{ - self, PYTHON_IMPORTS, - writer::{self, FlaskAppConfigWriterError}, - }, + config, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ self, AIRFLOW_CONFIG_FILENAME, APP_NAME, AirflowClusterStatus, AirflowConfig, - AirflowConfigOptions, AirflowExecutor, AirflowExecutorCommonConfiguration, AirflowRole, - CONFIG_PATH, Container, ExecutorConfig, HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, - LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, - STACKABLE_LOG_DIR, TEMPLATE_LOCATION, TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, + AirflowExecutor, AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, + ExecutorConfig, HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, + LOG_CONFIG_DIR, METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, + TEMPLATE_LOCATION, TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, authentication::{ AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved, }, @@ -112,9 +108,6 @@ pub const CONTAINER_IMAGE_BASE_NAME: &str = "airflow"; pub const AIRFLOW_FULL_CONTROLLER_NAME: &str = concatcp!(AIRFLOW_CONTROLLER_NAME, '.', OPERATOR_NAME); -const CONFIG_OVERRIDE_FILE_HEADER_KEY: &str = "FILE_HEADER"; -const CONFIG_OVERRIDE_FILE_FOOTER_KEY: &str = "FILE_FOOTER"; - pub struct Ctx { pub client: stackable_operator::client::Client, pub operator_environment: OperatorEnvironmentOptions, @@ -164,9 +157,9 @@ pub enum Error { source: stackable_operator::commons::rbac::Error, }, - #[snafu(display("failed to build config file for {rolegroup}"))] - BuildRoleGroupConfigFile { - source: FlaskAppConfigWriterError, + #[snafu(display("failed to build webserver config for {rolegroup}"))] + BuildWebserverConfig { + source: config::webserver_config::Error, rolegroup: RoleGroupRef, }, @@ -257,14 +250,6 @@ pub enum Error { source: stackable_operator::builder::meta::Error, }, - #[snafu(display("failed to construct config"))] - ConstructConfig { source: config::Error }, - - #[snafu(display( - "failed to write to String (Vec to be precise) containing Airflow config" - ))] - WriteToConfigFileString { source: std::io::Error }, - #[snafu(display("failed to configure logging"))] ConfigureLogging { source: LoggingError }, @@ -728,58 +713,15 @@ fn build_rolegroup_config_map( logging: &Logging, container: &Container, ) -> Result { - let mut config: BTreeMap = BTreeMap::new(); - - // this will call default values from AirflowClientAuthenticationDetails - config::add_airflow_config( - &mut config, + let config_file = config::webserver_config::build( authentication_config, authorization_config, &resolved_product_image.product_version, + config_file_overrides, ) - .context(ConstructConfigSnafu)?; - - tracing::debug!( - "Default config for {}: {:?}", - rolegroup.object_name(), - config - ); - - let mut file_config = config_file_overrides.clone(); - - tracing::debug!( - "Config overrides for {}: {:?}", - rolegroup.object_name(), - file_config - ); - - // now add any overrides, replacing any defaults - config.append(&mut file_config); - - tracing::debug!( - "Merged config for {}: {:?}", - rolegroup.object_name(), - config - ); - - let mut config_file = Vec::new(); - - // By removing the keys from `config_properties`, we avoid pasting the Python code into a Python variable as well - // (which would be bad) - if let Some(header) = config.remove(CONFIG_OVERRIDE_FILE_HEADER_KEY) { - writeln!(config_file, "{}", header).context(WriteToConfigFileStringSnafu)?; - } - - let temp_file_footer: Option = config.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); - - writer::write::(&mut config_file, config.iter(), PYTHON_IMPORTS) - .with_context(|_| BuildRoleGroupConfigFileSnafu { - rolegroup: rolegroup.clone(), - })?; - - if let Some(footer) = temp_file_footer { - writeln!(config_file, "{}", footer).context(WriteToConfigFileStringSnafu)?; - } + .with_context(|_| BuildWebserverConfigSnafu { + rolegroup: rolegroup.clone(), + })?; let mut cm_builder = ConfigMapBuilder::new(); @@ -800,10 +742,7 @@ fn build_rolegroup_config_map( .context(ObjectMetaSnafu)? .build(), ) - .add_data( - AIRFLOW_CONFIG_FILENAME, - String::from_utf8(config_file).unwrap(), - ); + .add_data(AIRFLOW_CONFIG_FILENAME, config_file); extend_config_map_with_log_config( rolegroup, diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index 3792f4ba..49899f42 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -1,3 +1,4 @@ +pub mod webserver_config; pub mod writer; use std::collections::BTreeMap; diff --git a/rust/operator-binary/src/config/webserver_config.rs b/rust/operator-binary/src/config/webserver_config.rs new file mode 100644 index 00000000..17311f0c --- /dev/null +++ b/rust/operator-binary/src/config/webserver_config.rs @@ -0,0 +1,76 @@ +//! Builds the `webserver_config.py` Flask configuration file from the resolved +//! authentication/authorization config plus user-provided config overrides. + +use std::{collections::BTreeMap, io::Write}; + +use snafu::{ResultExt, Snafu}; + +use super::{PYTHON_IMPORTS, add_airflow_config, writer}; +use crate::crd::{ + AirflowConfigOptions, authentication::AirflowClientAuthenticationDetailsResolved, + authorization::AirflowAuthorizationResolved, +}; + +/// Marks arbitrary Python code to prepend verbatim to the generated file. +const CONFIG_OVERRIDE_FILE_HEADER_KEY: &str = "FILE_HEADER"; +/// Marks arbitrary Python code to append verbatim to the generated file. +const CONFIG_OVERRIDE_FILE_FOOTER_KEY: &str = "FILE_FOOTER"; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to construct the webserver config"))] + ConstructConfig { source: super::Error }, + + #[snafu(display("failed to write the webserver config file"))] + WriteConfigFile { + source: writer::FlaskAppConfigWriterError, + }, + + #[snafu(display("failed to write the header/footer to the webserver config file"))] + WriteHeaderFooter { source: std::io::Error }, +} + +/// Renders the `webserver_config.py` contents: operator defaults (derived from the +/// resolved authentication/authorization config) with the user's `config_overrides` +/// applied last, wrapped by the optional `FILE_HEADER`/`FILE_FOOTER` Python blocks. +pub fn build( + authentication_config: &AirflowClientAuthenticationDetailsResolved, + authorization_config: &AirflowAuthorizationResolved, + product_version: &str, + config_file_overrides: &BTreeMap, +) -> Result { + let mut config: BTreeMap = BTreeMap::new(); + + // this will call default values from AirflowClientAuthenticationDetails + add_airflow_config( + &mut config, + authentication_config, + authorization_config, + product_version, + ) + .context(ConstructConfigSnafu)?; + + let mut file_config = config_file_overrides.clone(); + + // now add any overrides, replacing any defaults + config.append(&mut file_config); + + let mut config_file = Vec::new(); + + // By removing the keys from `config`, we avoid pasting the Python code into a Python variable as well + // (which would be bad) + if let Some(header) = config.remove(CONFIG_OVERRIDE_FILE_HEADER_KEY) { + writeln!(config_file, "{}", header).context(WriteHeaderFooterSnafu)?; + } + + let temp_file_footer: Option = config.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); + + writer::write::(&mut config_file, config.iter(), PYTHON_IMPORTS) + .context(WriteConfigFileSnafu)?; + + if let Some(footer) = temp_file_footer { + writeln!(config_file, "{}", footer).context(WriteHeaderFooterSnafu)?; + } + + Ok(String::from_utf8(config_file).expect("the Flask config writer only emits valid UTF-8")) +} From fc96e05ac7136254071cc91356aff34ff04d2762 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 15:26:17 +0200 Subject: [PATCH 10/35] use constants directly when resolving image --- .../operator-binary/src/airflow_controller.rs | 2 -- .../src/controller/validate.rs | 19 ++++++++++++------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index b7d2ee49..468bded9 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -364,9 +364,7 @@ pub async fn reconcile_airflow( let validated = crate::controller::validate::validate_cluster( airflow, - CONTAINER_IMAGE_BASE_NAME, &ctx.operator_environment.image_repository, - crate::built_info::PKG_VERSION, dereferenced, ) .context(ValidateSnafu)?; diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index f58da226..c0ce093f 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -8,10 +8,13 @@ use stackable_operator::{ use strum::IntoEnumIterator; use super::dereference::DereferencedObjects; -use crate::crd::{ - AirflowConfig, AirflowExecutor, AirflowRole, MergedOverrides, - authentication::AirflowClientAuthenticationDetailsResolved, - authorization::AirflowAuthorizationResolved, v1alpha2, +use crate::{ + airflow_controller::CONTAINER_IMAGE_BASE_NAME, + crd::{ + AirflowConfig, AirflowExecutor, AirflowRole, MergedOverrides, + authentication::AirflowClientAuthenticationDetailsResolved, + authorization::AirflowAuthorizationResolved, v1alpha2, + }, }; #[derive(Snafu, Debug)] @@ -55,15 +58,17 @@ pub struct ValidatedAirflowCluster { pub fn validate_cluster( airflow: &v1alpha2::AirflowCluster, - image_base_name: &str, image_repository: &str, - pkg_version: &str, dereferenced: DereferencedObjects, ) -> Result { let resolved_product_image = airflow .spec .image - .resolve(image_base_name, image_repository, pkg_version) + .resolve( + CONTAINER_IMAGE_BASE_NAME, + image_repository, + crate::built_info::PKG_VERSION, + ) .context(ResolveProductImageSnafu)?; let mut role_groups = BTreeMap::new(); From 0fed7d5c4a609e0d459042013b79f1203e3c814e Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 15:27:50 +0200 Subject: [PATCH 11/35] remove mention of product-config from env-var docs --- .../reference/environment-variables.adoc | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/docs/modules/airflow/pages/reference/environment-variables.adoc b/docs/modules/airflow/pages/reference/environment-variables.adoc index 95824a7f..ea718826 100644 --- a/docs/modules/airflow/pages/reference/environment-variables.adoc +++ b/docs/modules/airflow/pages/reference/environment-variables.adoc @@ -33,32 +33,6 @@ docker run \ oci.stackable.tech/sdp/airflow-operator:0.0.0-dev ---- -== PRODUCT_CONFIG - -*Default value*: `/etc/stackable/airflow-operator/config-spec/properties.yaml` - -*Required*: false - -*Multiple values*: false - -[source] ----- -export PRODUCT_CONFIG=/foo/bar/properties.yaml -stackable-airflow-operator run ----- - -or via docker: - ----- -docker run \ - --name airflow-operator \ - --network host \ - --env KUBECONFIG=/home/stackable/.kube/config \ - --env PRODUCT_CONFIG=/my/product/config.yaml \ - --mount type=bind,source="$HOME/.kube/config",target="/home/stackable/.kube/config" \ - oci.stackable.tech/sdp/airflow-operator:0.0.0-dev ----- - == WATCH_NAMESPACE *Default value*: All namespaces From 58c9d03f907710e8877f1e72315be6bebef95e61 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 15:50:43 +0200 Subject: [PATCH 12/35] refactor: move rolegroup ConfigMap build into controller/build/config_map Extracts build_rolegroup_config_map out of airflow_controller into a dedicated controller/build/config_map module with its own error enum, matching the controller/build/config_map.rs layout in hdfs- and trino-operator. The controller now wraps it via a single BuildConfigMap error variant; the ConfigMap-only error variants move into the new module. No behaviour change. Co-Authored-By: Claude Opus 4.8 --- .../operator-binary/src/airflow_controller.rs | 110 +++-------------- .../src/controller/build/config_map.rs | 115 ++++++++++++++++++ .../src/controller/build/mod.rs | 3 + rust/operator-binary/src/controller/mod.rs | 1 + 4 files changed, 135 insertions(+), 94 deletions(-) create mode 100644 rust/operator-binary/src/controller/build/config_map.rs create mode 100644 rust/operator-binary/src/controller/build/mod.rs diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 468bded9..fea8052b 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -55,11 +55,7 @@ use stackable_operator::{ }, kvp::{Annotation, Label, LabelError, Labels, ObjectLabels}, logging::controller::ReconcilerError, - product_logging::{ - self, - framework::LoggingError, - spec::{ContainerLogConfig, Logging}, - }, + product_logging::{self, framework::LoggingError, spec::ContainerLogConfig}, role_utils::RoleGroupRef, shared::time::Duration, status::condition::{ @@ -71,14 +67,14 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - config, + controller::build::config_map, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ - self, AIRFLOW_CONFIG_FILENAME, APP_NAME, AirflowClusterStatus, AirflowConfig, - AirflowExecutor, AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, - ExecutorConfig, HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, - LOG_CONFIG_DIR, METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, - TEMPLATE_LOCATION, TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, + self, APP_NAME, AirflowClusterStatus, AirflowConfig, AirflowExecutor, + AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, ExecutorConfig, + HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, + METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, TEMPLATE_LOCATION, + TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, authentication::{ AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved, }, @@ -96,7 +92,6 @@ use crate::{ }, pdb::add_pdbs, }, - product_logging::extend_config_map_with_log_config, service::{ build_rolegroup_headless_service, build_rolegroup_metrics_service, stateful_set_service_name, @@ -157,16 +152,9 @@ pub enum Error { source: stackable_operator::commons::rbac::Error, }, - #[snafu(display("failed to build webserver config for {rolegroup}"))] - BuildWebserverConfig { - source: config::webserver_config::Error, - rolegroup: RoleGroupRef, - }, - - #[snafu(display("failed to build ConfigMap for {rolegroup}"))] - BuildRoleGroupConfig { - source: stackable_operator::builder::configmap::Error, - rolegroup: RoleGroupRef, + #[snafu(display("failed to build rolegroup ConfigMap"))] + BuildConfigMap { + source: crate::controller::build::config_map::Error, }, #[snafu(display("failed to resolve and merge config for role and role group"))] @@ -193,12 +181,6 @@ pub enum Error { #[snafu(display("vector agent is enabled but vector aggregator ConfigMap is missing"))] VectorAggregatorConfigMapMissing, - #[snafu(display("failed to add the logging configuration to the ConfigMap [{cm_name}]"))] - InvalidLoggingConfig { - source: crate::product_logging::Error, - cm_name: String, - }, - #[snafu(display("failed to update status"))] ApplyStatus { source: stackable_operator::client::Error, @@ -558,7 +540,7 @@ pub async fn reconcile_airflow( rolegroup: rolegroup.clone(), })?; - let rg_configmap = build_rolegroup_config_map( + let rg_configmap = config_map::build_rolegroup_config_map( airflow, &validated.image, &rolegroup, @@ -567,7 +549,8 @@ pub async fn reconcile_airflow( &validated.authorization_config, &validated_rg_config.merged_config.logging, &Container::Airflow, - )?; + ) + .context(BuildConfigMapSnafu)?; cluster_resources .add(client, rg_configmap) .await @@ -643,7 +626,7 @@ async fn build_executor_template( role_group: "kubernetes".into(), }; - let rg_configmap = build_rolegroup_config_map( + let rg_configmap = config_map::build_rolegroup_config_map( airflow, resolved_product_image, &rolegroup, @@ -652,7 +635,8 @@ async fn build_executor_template( authorization_config, &merged_executor_config.logging, &Container::Base, - )?; + ) + .context(BuildConfigMapSnafu)?; cluster_resources .add(client, rg_configmap) .await @@ -699,68 +683,6 @@ async fn build_executor_template( Ok(()) } -/// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator -#[allow(clippy::too_many_arguments)] -fn build_rolegroup_config_map( - airflow: &v1alpha2::AirflowCluster, - resolved_product_image: &ResolvedProductImage, - rolegroup: &RoleGroupRef, - config_file_overrides: &BTreeMap, - authentication_config: &AirflowClientAuthenticationDetailsResolved, - authorization_config: &AirflowAuthorizationResolved, - logging: &Logging, - container: &Container, -) -> Result { - let config_file = config::webserver_config::build( - authentication_config, - authorization_config, - &resolved_product_image.product_version, - config_file_overrides, - ) - .with_context(|_| BuildWebserverConfigSnafu { - rolegroup: rolegroup.clone(), - })?; - - let mut cm_builder = ConfigMapBuilder::new(); - - cm_builder - .metadata( - ObjectMetaBuilder::new() - .name_and_namespace(airflow) - .name(rolegroup.object_name()) - .ownerreference_from_resource(airflow, None, Some(true)) - .context(ObjectMissingMetadataForOwnerRefSnafu)? - .with_recommended_labels(&build_recommended_labels( - airflow, - AIRFLOW_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, - &rolegroup.role, - &rolegroup.role_group, - )) - .context(ObjectMetaSnafu)? - .build(), - ) - .add_data(AIRFLOW_CONFIG_FILENAME, config_file); - - extend_config_map_with_log_config( - rolegroup, - logging, - container, - &Container::Vector, - &mut cm_builder, - resolved_product_image, - ) - .context(InvalidLoggingConfigSnafu { - cm_name: rolegroup.object_name(), - })?; - - cm_builder - .build() - .with_context(|_| BuildRoleGroupConfigSnafu { - rolegroup: rolegroup.clone(), - }) -} - fn build_rolegroup_metadata( airflow: &v1alpha2::AirflowCluster, resolved_product_image: &&ResolvedProductImage, diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs new file mode 100644 index 00000000..d6351cfb --- /dev/null +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -0,0 +1,115 @@ +//! Builds the rolegroup [`ConfigMap`]: the rendered `webserver_config.py` plus the +//! logging/vector configuration. + +use std::collections::BTreeMap; + +use snafu::{ResultExt, Snafu}; +use stackable_operator::{ + builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, + commons::product_image_selection::ResolvedProductImage, + k8s_openapi::api::core::v1::ConfigMap, + product_logging::spec::Logging, + role_utils::RoleGroupRef, +}; + +use crate::{ + airflow_controller::AIRFLOW_CONTROLLER_NAME, + config::webserver_config, + crd::{ + AIRFLOW_CONFIG_FILENAME, Container, + authentication::AirflowClientAuthenticationDetailsResolved, + authorization::AirflowAuthorizationResolved, build_recommended_labels, v1alpha2, + }, + product_logging::extend_config_map_with_log_config, +}; + +#[derive(Snafu, Debug)] +pub enum Error { + #[snafu(display("failed to build webserver config for {rolegroup}"))] + BuildWebserverConfig { + source: webserver_config::Error, + rolegroup: RoleGroupRef, + }, + + #[snafu(display("object is missing metadata to build owner reference"))] + ObjectMissingMetadataForOwnerRef { + source: stackable_operator::builder::meta::Error, + }, + + #[snafu(display("failed to build object meta"))] + ObjectMeta { + source: stackable_operator::builder::meta::Error, + }, + + #[snafu(display("failed to add the logging configuration to the ConfigMap [{cm_name}]"))] + InvalidLoggingConfig { + source: crate::product_logging::Error, + cm_name: String, + }, + + #[snafu(display("failed to build ConfigMap for {rolegroup}"))] + BuildConfigMap { + source: stackable_operator::builder::configmap::Error, + rolegroup: RoleGroupRef, + }, +} + +/// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator +#[allow(clippy::too_many_arguments)] +pub fn build_rolegroup_config_map( + airflow: &v1alpha2::AirflowCluster, + resolved_product_image: &ResolvedProductImage, + rolegroup: &RoleGroupRef, + config_file_overrides: &BTreeMap, + authentication_config: &AirflowClientAuthenticationDetailsResolved, + authorization_config: &AirflowAuthorizationResolved, + logging: &Logging, + container: &Container, +) -> Result { + let config_file = webserver_config::build( + authentication_config, + authorization_config, + &resolved_product_image.product_version, + config_file_overrides, + ) + .with_context(|_| BuildWebserverConfigSnafu { + rolegroup: rolegroup.clone(), + })?; + + let mut cm_builder = ConfigMapBuilder::new(); + + cm_builder + .metadata( + ObjectMetaBuilder::new() + .name_and_namespace(airflow) + .name(rolegroup.object_name()) + .ownerreference_from_resource(airflow, None, Some(true)) + .context(ObjectMissingMetadataForOwnerRefSnafu)? + .with_recommended_labels(&build_recommended_labels( + airflow, + AIRFLOW_CONTROLLER_NAME, + &resolved_product_image.app_version_label_value, + &rolegroup.role, + &rolegroup.role_group, + )) + .context(ObjectMetaSnafu)? + .build(), + ) + .add_data(AIRFLOW_CONFIG_FILENAME, config_file); + + extend_config_map_with_log_config( + rolegroup, + logging, + container, + &Container::Vector, + &mut cm_builder, + resolved_product_image, + ) + .context(InvalidLoggingConfigSnafu { + cm_name: rolegroup.object_name(), + })?; + + cm_builder.build().with_context(|_| BuildConfigMapSnafu { + rolegroup: rolegroup.clone(), + }) +} diff --git a/rust/operator-binary/src/controller/build/mod.rs b/rust/operator-binary/src/controller/build/mod.rs new file mode 100644 index 00000000..881a36f4 --- /dev/null +++ b/rust/operator-binary/src/controller/build/mod.rs @@ -0,0 +1,3 @@ +//! Builders that assemble Kubernetes resources from the validated cluster. + +pub mod config_map; diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index 1b261dfe..a1196d5d 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -1,2 +1,3 @@ +pub mod build; pub mod dereference; pub mod validate; From a66bc67e6fdeb80ab1703fa0f90617cd3ffe7e31 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 16:43:04 +0200 Subject: [PATCH 13/35] pass validated cluster instead of individual parameters --- .../operator-binary/src/airflow_controller.rs | 46 ++++++++----------- .../src/controller/build/config_map.rs | 22 ++++----- 2 files changed, 27 insertions(+), 41 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index fea8052b..ca1fcf65 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -67,7 +67,7 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - controller::build::config_map, + controller::{build::config_map, validate::ValidatedAirflowCluster}, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ self, APP_NAME, AirflowClusterStatus, AirflowConfig, AirflowExecutor, @@ -344,7 +344,7 @@ pub async fn reconcile_airflow( None }; - let validated = crate::controller::validate::validate_cluster( + let validated_cluster = crate::controller::validate::validate_cluster( airflow, &ctx.operator_environment.image_repository, dereferenced, @@ -418,15 +418,13 @@ pub async fn reconcile_airflow( // collection there will be a pod template created to be used for pod provisioning if let AirflowExecutor::KubernetesExecutors { common_configuration, - } = &validated.executor + } = &validated_cluster.executor { build_executor_template( airflow, common_configuration, &metadata_database_connection_details, - &validated.image, - &validated.authentication_config, - &validated.authorization_config, + &validated_cluster, &mut cluster_resources, client, &rbac_sa, @@ -434,10 +432,10 @@ pub async fn reconcile_airflow( .await?; } - for (airflow_role, role_group_configs) in &validated.role_groups { + for (airflow_role, role_group_configs) in &validated_cluster.role_groups { let role_name = airflow_role.to_string(); - if let Some(role_config) = validated.role_configs.get(airflow_role) { + if let Some(role_config) = validated_cluster.role_configs.get(airflow_role) { if let Some(pdb) = &role_config.pdb { add_pdbs(pdb, airflow, airflow_role, client, &mut cluster_resources) .await @@ -451,7 +449,7 @@ pub async fn reconcile_airflow( build_recommended_labels( airflow, AIRFLOW_CONTROLLER_NAME, - &validated.image.app_version_label_value, + &validated_cluster.image.app_version_label_value, &role_name, "none", ), @@ -475,7 +473,7 @@ pub async fn reconcile_airflow( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, - &validated.image, + &validated_cluster.image, &validated_rg_config .overrides .env_overrides @@ -498,7 +496,7 @@ pub async fn reconcile_airflow( let role_group_service_recommended_labels = build_recommended_labels( airflow, AIRFLOW_CONTROLLER_NAME, - &validated.image.app_version_label_value, + &validated_cluster.image.app_version_label_value, &rolegroup.role, &rolegroup.role_group, ); @@ -542,11 +540,9 @@ pub async fn reconcile_airflow( let rg_configmap = config_map::build_rolegroup_config_map( airflow, - &validated.image, + &validated_cluster, &rolegroup, &validated_rg_config.overrides.config_file_overrides, - &validated.authentication_config, - &validated.authorization_config, &validated_rg_config.merged_config.logging, &Container::Airflow, ) @@ -560,17 +556,17 @@ pub async fn reconcile_airflow( let rg_statefulset = build_server_rolegroup_statefulset( airflow, - &validated.image, + &validated_cluster.image, airflow_role, &rolegroup, &validated_rg_config.overrides.env_overrides, - &validated.authentication_config, - &validated.authorization_config, + &validated_cluster.authentication_config, + &validated_cluster.authorization_config, &metadata_database_connection_details, &celery_database_connection_details, &rbac_sa, &validated_rg_config.merged_config, - &validated.executor, + &validated_cluster.executor, &git_sync_resources, )?; @@ -610,9 +606,7 @@ async fn build_executor_template( airflow: &v1alpha2::AirflowCluster, common_config: &AirflowExecutorCommonConfiguration, metadata_database_connection_details: &SqlAlchemyDatabaseConnectionDetails, - resolved_product_image: &ResolvedProductImage, - authentication_config: &AirflowClientAuthenticationDetailsResolved, - authorization_config: &AirflowAuthorizationResolved, + validated_cluster: &ValidatedAirflowCluster, cluster_resources: &mut ClusterResources<'_>, client: &stackable_operator::client::Client, rbac_sa: &stackable_operator::k8s_openapi::api::core::v1::ServiceAccount, @@ -628,11 +622,9 @@ async fn build_executor_template( let rg_configmap = config_map::build_rolegroup_config_map( airflow, - resolved_product_image, + validated_cluster, &rolegroup, &BTreeMap::new(), - authentication_config, - authorization_config, &merged_executor_config.logging, &Container::Base, ) @@ -646,7 +638,7 @@ async fn build_executor_template( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, - resolved_product_image, + &validated_cluster.image, &common_config .env_overrides .iter() @@ -666,8 +658,8 @@ async fn build_executor_template( let worker_pod_template_config_map = build_executor_template_config_map( airflow, - resolved_product_image, - authentication_config, + &validated_cluster.image, + &validated_cluster.authentication_config, metadata_database_connection_details, &rbac_sa.name_unchecked(), &merged_executor_config, diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index d6351cfb..b1bc88ab 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -6,7 +6,6 @@ use std::collections::BTreeMap; use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, - commons::product_image_selection::ResolvedProductImage, k8s_openapi::api::core::v1::ConfigMap, product_logging::spec::Logging, role_utils::RoleGroupRef, @@ -15,11 +14,8 @@ use stackable_operator::{ use crate::{ airflow_controller::AIRFLOW_CONTROLLER_NAME, config::webserver_config, - crd::{ - AIRFLOW_CONFIG_FILENAME, Container, - authentication::AirflowClientAuthenticationDetailsResolved, - authorization::AirflowAuthorizationResolved, build_recommended_labels, v1alpha2, - }, + controller::validate::ValidatedAirflowCluster, + crd::{AIRFLOW_CONFIG_FILENAME, Container, build_recommended_labels, v1alpha2}, product_logging::extend_config_map_with_log_config, }; @@ -58,18 +54,16 @@ pub enum Error { #[allow(clippy::too_many_arguments)] pub fn build_rolegroup_config_map( airflow: &v1alpha2::AirflowCluster, - resolved_product_image: &ResolvedProductImage, + validated_cluster: &ValidatedAirflowCluster, rolegroup: &RoleGroupRef, config_file_overrides: &BTreeMap, - authentication_config: &AirflowClientAuthenticationDetailsResolved, - authorization_config: &AirflowAuthorizationResolved, logging: &Logging, container: &Container, ) -> Result { let config_file = webserver_config::build( - authentication_config, - authorization_config, - &resolved_product_image.product_version, + &validated_cluster.authentication_config, + &validated_cluster.authorization_config, + &validated_cluster.image.product_version, config_file_overrides, ) .with_context(|_| BuildWebserverConfigSnafu { @@ -88,7 +82,7 @@ pub fn build_rolegroup_config_map( .with_recommended_labels(&build_recommended_labels( airflow, AIRFLOW_CONTROLLER_NAME, - &resolved_product_image.app_version_label_value, + &validated_cluster.image.app_version_label_value, &rolegroup.role, &rolegroup.role_group, )) @@ -103,7 +97,7 @@ pub fn build_rolegroup_config_map( container, &Container::Vector, &mut cm_builder, - resolved_product_image, + &validated_cluster.image, ) .context(InvalidLoggingConfigSnafu { cm_name: rolegroup.object_name(), From 7a370cfe1e58f8939603fa3f808fda2ca782ca80 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 3 Jun 2026 17:22:13 +0200 Subject: [PATCH 14/35] feat: adopt v2 config_overrides; build against the smooth-operator branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Patches operator-rs to the smooth-operator branch (matching trino- and hdfs-operator) and actually consumes it: AirflowConfigOverrides.webserver_config_py now uses stackable_operator::v2::config_overrides::KeyValueConfigOverrides — the Merge-capable variant trino/hdfs use — instead of the v1 type. Drops the v1 KeyValueOverridesProvider impl and the as_product_config_overrides() call; merged_overrides reads the override map directly (role <- role-group extend), so the rendered webserver_config.py is unchanged (39 tests pass). The CRD gains `nullable: true` on the webserver_config.py override values (v2 allows null to delete a key). Regenerated extra/crds.yaml, Cargo.nix, and crate-hashes.json. Co-Authored-By: Claude Opus 4.8 --- Cargo.lock | 32 +++++--- Cargo.nix | 114 +++++++++++++++++++++++----- Cargo.toml | 1 + crate-hashes.json | 18 ++--- extra/crds.yaml | 22 ++++++ rust/operator-binary/src/crd/mod.rs | 30 +++----- 6 files changed, 158 insertions(+), 59 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64a01dc3..3f1c1cfb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1481,6 +1481,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" dependencies = [ "jsonptr", + "schemars", "serde", "serde_json", "thiserror 1.0.69", @@ -1525,7 +1526,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "darling", "regex", @@ -2919,7 +2920,7 @@ dependencies = [ [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "const-oid", "ecdsa", @@ -2942,8 +2943,8 @@ dependencies = [ [[package]] name = "stackable-operator" -version = "0.111.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +version = "0.111.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "base64", "clap", @@ -2979,12 +2980,13 @@ dependencies = [ "tracing-appender", "tracing-subscriber", "url", + "uuid", ] [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "darling", "proc-macro2", @@ -2995,7 +2997,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "jiff", "k8s-openapi", @@ -3012,7 +3014,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.3" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "axum", "clap", @@ -3036,7 +3038,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "kube", "schemars", @@ -3050,7 +3052,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "convert_case", "convert_case_extras", @@ -3068,7 +3070,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" dependencies = [ "arc-swap", "async-trait", @@ -3649,6 +3651,16 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "valuable" version = "0.1.1" diff --git a/Cargo.nix b/Cargo.nix index 3ea6cbdc..41b1e39f 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4714,6 +4714,11 @@ rec { name = "jsonptr"; packageId = "jsonptr"; } + { + name = "schemars"; + packageId = "schemars"; + optional = true; + } { name = "serde"; packageId = "serde"; @@ -4729,6 +4734,10 @@ rec { } ]; devDependencies = [ + { + name = "schemars"; + packageId = "schemars"; + } { name = "serde_json"; packageId = "serde_json"; @@ -4740,7 +4749,7 @@ rec { "schemars" = [ "dep:schemars" ]; "utoipa" = [ "dep:utoipa" ]; }; - resolvedDefaultFeatures = [ "default" "diff" ]; + resolvedDefaultFeatures = [ "default" "diff" "schemars" ]; }; "jsonpath-rust" = rec { crateName = "jsonpath-rust"; @@ -4866,8 +4875,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "k8s_version"; @@ -9637,8 +9646,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_certs"; @@ -9736,12 +9745,12 @@ rec { }; "stackable-operator" = rec { crateName = "stackable-operator"; - version = "0.111.0"; + version = "0.111.1"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_operator"; @@ -9799,6 +9808,7 @@ rec { { name = "json-patch"; packageId = "json-patch"; + features = [ "schemars" ]; } { name = "k8s-openapi"; @@ -9902,6 +9912,10 @@ rec { packageId = "url"; features = [ "serde" ]; } + { + name = "uuid"; + packageId = "uuid"; + } ]; features = { "certs" = [ "dep:stackable-certs" ]; @@ -9920,8 +9934,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; procMacro = true; @@ -9955,8 +9969,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_shared"; @@ -10036,8 +10050,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_telemetry"; @@ -10146,8 +10160,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_versioned"; @@ -10196,8 +10210,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; procMacro = true; @@ -10264,8 +10278,8 @@ rec { edition = "2024"; workspace_member = null; src = pkgs.fetchgit { - url = "https://github.com/stackabletech/operator-rs.git"; - rev = "b7c8a3a5483b4d35d0abfa11f6db6c153bda8a51"; + url = "https://github.com/stackabletech//operator-rs.git"; + rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_webhook"; @@ -12289,6 +12303,66 @@ rec { }; resolvedDefaultFeatures = [ "default" ]; }; + "uuid" = rec { + crateName = "uuid"; + version = "1.23.2"; + edition = "2021"; + sha256 = "1xy942s4z0bi8p3441wvd4ry3hx6ry1c7s6fgrr38462xqybhn6j"; + authors = [ + "Ashley Mannix" + "Dylan DPC" + "Hunar Roop Kahlon" + ]; + dependencies = [ + { + name = "js-sys"; + packageId = "js-sys"; + optional = true; + usesDefaultFeatures = false; + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)) && (builtins.elem "atomics" targetFeatures)); + } + { + name = "wasm-bindgen"; + packageId = "wasm-bindgen"; + optional = true; + usesDefaultFeatures = false; + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); + } + ]; + devDependencies = [ + { + name = "wasm-bindgen"; + packageId = "wasm-bindgen"; + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); + } + ]; + features = { + "arbitrary" = [ "dep:arbitrary" ]; + "atomic" = [ "dep:atomic" ]; + "borsh" = [ "dep:borsh" "dep:borsh-derive" ]; + "bytemuck" = [ "dep:bytemuck" ]; + "default" = [ "std" ]; + "fast-rng" = [ "rng" "dep:rand" ]; + "js" = [ "dep:wasm-bindgen" "dep:js-sys" ]; + "md5" = [ "dep:md-5" ]; + "rng" = [ "dep:getrandom" ]; + "rng-getrandom" = [ "rng" "dep:getrandom" "uuid-rng-internal-lib" "uuid-rng-internal-lib/getrandom" ]; + "rng-rand" = [ "rng" "dep:rand" "uuid-rng-internal-lib" "uuid-rng-internal-lib/rand" ]; + "serde" = [ "dep:serde_core" ]; + "sha1" = [ "dep:sha1_smol" ]; + "slog" = [ "dep:slog" ]; + "std" = [ "wasm-bindgen?/std" "js-sys?/std" ]; + "uuid-rng-internal-lib" = [ "dep:uuid-rng-internal-lib" ]; + "v1" = [ "atomic" ]; + "v3" = [ "md5" ]; + "v4" = [ "rng" ]; + "v5" = [ "sha1" ]; + "v6" = [ "atomic" ]; + "v7" = [ "rng" ]; + "zerocopy" = [ "dep:zerocopy" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "valuable" = rec { crateName = "valuable"; version = "0.1.1"; diff --git a/Cargo.toml b/Cargo.toml index d686760b..b11fdea9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -33,5 +33,6 @@ tokio = { version = "1.40", features = ["full"] } tracing = "0.1" [patch."https://github.com/stackabletech/operator-rs.git"] +stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "smooth-operator" } # stackable-operator = { git = "https://github.com/stackabletech//operator-rs.git", branch = "main" } # stackable-operator = { path = "../operator-rs/crates/stackable-operator" } diff --git a/crate-hashes.json b/crate-hashes.json index 71fbc1c3..362b463b 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,12 @@ { - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#k8s-version@0.1.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-certs@0.4.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-operator-derive@0.3.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-operator@0.111.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-shared@0.1.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-telemetry@0.6.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-versioned-macros@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-versioned@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech/operator-rs.git?tag=stackable-operator-0.111.0#stackable-webhook@0.9.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file diff --git a/extra/crds.yaml b/extra/crds.yaml index 9ec7978a..691e3350 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -483,6 +483,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -1001,6 +1002,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -1998,6 +2000,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -2516,6 +2519,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -3069,6 +3073,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -3566,6 +3571,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -4084,6 +4090,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -4575,6 +4582,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -5093,6 +5101,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -5584,6 +5593,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -6107,6 +6117,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -6673,6 +6684,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -7191,6 +7203,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -8164,6 +8177,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -8682,6 +8696,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -9235,6 +9250,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -9732,6 +9748,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -10250,6 +10267,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -10741,6 +10759,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -11259,6 +11278,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -11750,6 +11770,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. @@ -12273,6 +12294,7 @@ spec: properties: webserver_config.py: additionalProperties: + nullable: true type: string description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 919a879c..c28543e4 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -18,7 +18,6 @@ use stackable_operator::{ fragment::{self, Fragment, ValidationError}, merge::Merge, }, - config_overrides::{KeyValueConfigOverrides, KeyValueOverridesProvider}, crd::git_sync, deep_merger::ObjectOverrides, k8s_openapi::{ @@ -40,6 +39,7 @@ use stackable_operator::{ shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, utils::{COMMON_BASH_TRAP_FUNCTIONS, crds::raw_object_list_schema}, + v2::config_overrides::KeyValueConfigOverrides, versioned::versioned, }; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; @@ -113,19 +113,6 @@ pub struct AirflowConfigOverrides { pub webserver_config_py: Option, } -impl KeyValueOverridesProvider for AirflowConfigOverrides { - fn get_key_value_overrides(&self, file: &str) -> BTreeMap> { - match file { - AIRFLOW_CONFIG_FILENAME => self - .webserver_config_py - .as_ref() - .map(|o| o.as_product_config_overrides()) - .unwrap_or_default(), - _ => BTreeMap::new(), - } - } -} - #[derive(Clone, Debug)] pub struct MergedOverrides { pub env_overrides: HashMap, @@ -463,18 +450,21 @@ impl v1alpha2::AirflowCluster { let role_config = role.role_config(self)?; let mut env_overrides = role_config.config.env_overrides.clone(); + // role-level webserver_config.py overrides; role-group overrides are extended on top below + // (role-group wins). Reads the v2 KeyValueConfigOverrides map directly, like hdfs/trino. let mut file_overrides = role_config .config .config_overrides - .get_key_value_overrides(AIRFLOW_CONFIG_FILENAME); + .webserver_config_py + .as_ref() + .map(|o| o.overrides.clone()) + .unwrap_or_default(); if let Some(rg) = role_config.role_groups.get(rolegroup_name) { env_overrides.extend(rg.config.env_overrides.clone()); - let rg_file = rg - .config - .config_overrides - .get_key_value_overrides(AIRFLOW_CONFIG_FILENAME); - file_overrides.extend(rg_file); + if let Some(rg_file) = rg.config.config_overrides.webserver_config_py.as_ref() { + file_overrides.extend(rg_file.overrides.clone()); + } } let config_file_overrides = file_overrides From be5c1216ab0e9b5efa0bc45397be225ceeac8ef3 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 5 Jun 2026 09:24:13 +0200 Subject: [PATCH 15/35] docs: make the vendored Flask writer doc operator-agnostic Neutralises the operator-specific wording in config/writer.rs so the file can be kept byte-identical between airflow-operator and superset-operator (which vendors the same writer), making the later move to a shared crate a trivial lift. Comment-only change. Co-Authored-By: Claude Opus 4.8 --- rust/operator-binary/src/config/writer.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/rust/operator-binary/src/config/writer.rs b/rust/operator-binary/src/config/writer.rs index 2af7efe0..7c34dec7 100644 --- a/rust/operator-binary/src/config/writer.rs +++ b/rust/operator-binary/src/config/writer.rs @@ -1,8 +1,8 @@ //! Writer for Flask App configurations (Python config files). //! -//! Vendored verbatim from `product_config::flask_app_config_writer` so -//! airflow-operator can drop the `product-config` crate dependency. Applications -//! based on the Flask App Builder (e.g. Apache Airflow, Apache Superset) use +//! Vendored verbatim from `product_config::flask_app_config_writer` so the +//! operator does not depend on the `product-config` crate. Applications based +//! on the Flask App Builder (e.g. Apache Airflow, Apache Superset) use //! configuration files written in Python. This writer only covers top-level //! assignments of a few primitive types and expressions — it is not a general //! Python code generator. @@ -11,10 +11,10 @@ //! invalid expressions produce invalid configuration files. Config overrides that do //! not map to a known option are treated as plain expressions. //! -// TODO: This is vendored, not airflow-specific. superset-operator still depends on -// `product_config::flask_app_config_writer`; this writer is a candidate for a shared -// crate (e.g. operator-rs) so both operators can drop the product-config crate. -// Until such a home exists it is duplicated here, kept identical to the upstream source. +// TODO: This file is vendored identically in airflow-operator and superset-operator; +// it is a candidate for a shared crate (e.g. operator-rs) so the duplication can be +// removed. Until such a home exists it is kept identical in both operators and to the +// upstream source. use std::{ io::{self, Write}, From 317f0291522f99fca35189ab41c59f773a03a53c Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Fri, 5 Jun 2026 13:35:55 +0200 Subject: [PATCH 16/35] refactor: consume the Flask config writer from stackable-operator Replace the vendored flask writer (rust/operator-binary/src/config/writer.rs) with stackable_operator::v2::flask_config_writer, which now hosts the same code (moved there verbatim from this repo via operator-rs #1217, merged into the smooth-operator branch). The base dependency tag moves from stackable-operator-0.111.0 to 0.111.1, matching the other operators. This is required for the [patch] to apply: cargo only substitutes a patch whose package version matches the dependency, and the smooth-operator branch carries 0.111.1. No behaviour change; rendered webserver_config.py output is byte-identical by construction (same code, new home). Co-Authored-By: Claude Opus 4.8 --- CHANGELOG.md | 2 + Cargo.lock | 116 +++--- Cargo.nix | 337 ++++++++++-------- Cargo.toml | 2 +- crate-hashes.json | 4 +- rust/operator-binary/src/config/mod.rs | 1 - .../src/config/webserver_config.rs | 13 +- rust/operator-binary/src/config/writer.rs | 331 ----------------- rust/operator-binary/src/crd/mod.rs | 6 +- 9 files changed, 277 insertions(+), 535 deletions(-) delete mode 100644 rust/operator-binary/src/config/writer.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index 15fab1eb..77ef92f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ - Removed the product-config based configuration validation. Config and environment overrides are now merged directly from the CRD, and dereferenced objects are added to the validated cluster. The `--product-config` CLI flag is now a no-op ([#804]). +- The Flask config-file writer for `webserver_config.py` is now provided by `stackable-operator` + (`v2::flask_config_writer`) instead of a vendored copy ([#804]). ### Fixed diff --git a/Cargo.lock b/Cargo.lock index 3f1c1cfb..0b263faf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1526,11 +1526,11 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "darling", "regex", - "snafu 0.9.0", + "snafu 0.9.1", ] [[package]] @@ -1851,9 +1851,9 @@ checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" [[package]] name = "opentelemetry" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +checksum = "b0142c63252a9e054e68a4c61a5778f7b14f576274d593f8ce883d191a099682" dependencies = [ "futures-core", "futures-sink", @@ -1865,9 +1865,9 @@ dependencies = [ [[package]] name = "opentelemetry-appender-tracing" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +checksum = "2c0080f0dc1d7c786f467cd85a4e395fcab11ee852004f39a29a18ab7c25d837" dependencies = [ "opentelemetry", "tracing", @@ -1877,9 +1877,9 @@ dependencies = [ [[package]] name = "opentelemetry-http" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +checksum = "5683015d09e2df236ef005b17f6f196f0d5f6313c4fa43a7b6a53b52776e4331" dependencies = [ "async-trait", "bytes", @@ -1890,9 +1890,9 @@ dependencies = [ [[package]] name = "opentelemetry-otlp" -version = "0.31.1" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +checksum = "9966929966d17620d7c316c643ba62631826e10021409357772d5eea84f62c35" dependencies = [ "http", "opentelemetry", @@ -1904,14 +1904,14 @@ dependencies = [ "thiserror 2.0.18", "tokio", "tonic", - "tracing", + "tonic-types", ] [[package]] name = "opentelemetry-proto" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +checksum = "56d658ba1faf63f7b9c492cfbe6e0ec365440a16132d3270c1065f7b33f1b638" dependencies = [ "opentelemetry", "opentelemetry_sdk", @@ -1922,21 +1922,22 @@ dependencies = [ [[package]] name = "opentelemetry-semantic-conventions" -version = "0.31.0" +version = "0.32.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" +checksum = "6ca2f98a0437b427b4b08f19f1caa3c44db885a202bc12cfea13d6c702243d68" [[package]] name = "opentelemetry_sdk" -version = "0.31.0" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +checksum = "9b59f80e1ac4d5ff7a2db8fb6c80badb7f0f3f858211fba08dd9aaec750894f9" dependencies = [ "futures-channel", "futures-executor", "futures-util", "opentelemetry", "percent-encoding", + "portable-atomic", "rand 0.9.4", "thiserror 2.0.18", "tokio", @@ -2219,6 +2220,15 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "prost-types" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +dependencies = [ + "prost", +] + [[package]] name = "quote" version = "1.0.45" @@ -2358,9 +2368,9 @@ checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" [[package]] name = "reqwest" -version = "0.12.28" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +checksum = "219c5811de6525e5416c7d5d53bb656d3afdbc6c5af816e0802bcfa42dbdc1c3" dependencies = [ "base64", "bytes", @@ -2376,9 +2386,6 @@ dependencies = [ "log", "percent-encoding", "pin-project-lite", - "serde", - "serde_json", - "serde_urlencoded", "sync_wrapper", "tokio", "tower", @@ -2821,11 +2828,11 @@ dependencies = [ [[package]] name = "snafu" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1d4bced6a69f90b2056c03dcff2c4737f98d6fb9e0853493996e1d253ca29c6" +checksum = "d1a012328be2e3f5d5f6f3218147ca02588cea4cb865e876849ab6debcf36522" dependencies = [ - "snafu-derive 0.9.0", + "snafu-derive 0.9.1", ] [[package]] @@ -2853,9 +2860,9 @@ dependencies = [ [[package]] name = "snafu-derive" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "54254b8531cafa275c5e096f62d48c81435d1015405a91198ddb11e967301d40" +checksum = "5f103c50866b8743da9429b8a581d81a27c2d3a9c4ac7df8f8571c1dd7896eda" dependencies = [ "heck", "proc-macro2", @@ -2910,7 +2917,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-operator", "strum", "tokio", @@ -2920,7 +2927,7 @@ dependencies = [ [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "const-oid", "ecdsa", @@ -2932,7 +2939,7 @@ dependencies = [ "rsa", "sha2", "signature", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-shared", "tokio", "tokio-rustls", @@ -2944,7 +2951,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "base64", "clap", @@ -2956,6 +2963,7 @@ dependencies = [ "futures 0.3.32", "http", "indexmap", + "java-properties", "jiff", "json-patch", "k8s-openapi", @@ -2968,7 +2976,7 @@ dependencies = [ "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-operator-derive", "stackable-shared", "stackable-telemetry", @@ -2981,12 +2989,13 @@ dependencies = [ "tracing-subscriber", "url", "uuid", + "xml", ] [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "darling", "proc-macro2", @@ -2996,8 +3005,8 @@ dependencies = [ [[package]] name = "stackable-shared" -version = "0.1.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +version = "0.1.1" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "jiff", "k8s-openapi", @@ -3006,15 +3015,15 @@ dependencies = [ "semver", "serde", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "time", ] [[package]] name = "stackable-telemetry" -version = "0.6.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +version = "0.6.4" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "axum", "clap", @@ -3025,7 +3034,7 @@ dependencies = [ "opentelemetry-semantic-conventions", "opentelemetry_sdk", "pin-project", - "snafu 0.9.0", + "snafu 0.9.1", "strum", "tokio", "tower", @@ -3038,21 +3047,21 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "kube", "schemars", "serde", "serde_json", "serde_yaml", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-versioned-macros", ] [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "convert_case", "convert_case_extras", @@ -3070,7 +3079,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#a31cd2514445b251038fc4ea7abc28c57b2a6ad9" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" dependencies = [ "arc-swap", "async-trait", @@ -3086,7 +3095,7 @@ dependencies = [ "rand 0.9.4", "serde", "serde_json", - "snafu 0.9.0", + "snafu 0.9.1", "stackable-certs", "stackable-shared", "stackable-telemetry", @@ -3422,6 +3431,17 @@ dependencies = [ "tonic", ] +[[package]] +name = "tonic-types" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a875a902255423d34c1f20838ab374126db8eb41625b7947a1d54113b0b7399" +dependencies = [ + "prost", + "prost-types", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -3533,9 +3553,9 @@ dependencies = [ [[package]] name = "tracing-opentelemetry" -version = "0.32.1" +version = "0.33.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" +checksum = "adbc64cba7137545b8044cb1fe9814f7aacf3c6b5f9b45be8bb5db538befdb26" dependencies = [ "js-sys", "opentelemetry", @@ -3956,9 +3976,9 @@ dependencies = [ [[package]] name = "xml" -version = "1.2.1" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8aa498d22c9bbaf482329839bc5620c46be275a19a812e9a22a2b07529a642a" +checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" [[package]] name = "yoke" diff --git a/Cargo.nix b/Cargo.nix index 41b1e39f..f3a57fca 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4876,7 +4876,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "k8s_version"; @@ -4895,7 +4895,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } ]; features = { @@ -6076,9 +6076,9 @@ rec { }; "opentelemetry" = rec { crateName = "opentelemetry"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "18629xsj4rsyiby9aj511q6wcw6s9m09gx3ymw1yjcvix1mcsjxq"; + sha256 = "10ln14d1jgc8rvw97mblc9blzcgpg1bimim4d170b7ia4mijq55h"; dependencies = [ { name = "futures-core"; @@ -6115,24 +6115,24 @@ rec { ]; features = { "default" = [ "trace" "metrics" "logs" "internal-logs" "futures" ]; + "experimental_metrics_bound_instruments" = [ "metrics" ]; "futures" = [ "futures-core" "futures-sink" "pin-project-lite" ]; "futures-core" = [ "dep:futures-core" ]; "futures-sink" = [ "dep:futures-sink" ]; "internal-logs" = [ "tracing" ]; "pin-project-lite" = [ "dep:pin-project-lite" ]; - "spec_unstable_logs_enabled" = [ "logs" ]; "testing" = [ "trace" ]; "thiserror" = [ "dep:thiserror" ]; "trace" = [ "futures" "thiserror" ]; "tracing" = [ "dep:tracing" ]; }; - resolvedDefaultFeatures = [ "default" "futures" "futures-core" "futures-sink" "internal-logs" "logs" "metrics" "pin-project-lite" "spec_unstable_logs_enabled" "thiserror" "trace" "tracing" ]; + resolvedDefaultFeatures = [ "default" "futures" "futures-core" "futures-sink" "internal-logs" "logs" "metrics" "pin-project-lite" "thiserror" "trace" "tracing" ]; }; "opentelemetry-appender-tracing" = rec { crateName = "opentelemetry-appender-tracing"; - version = "0.31.1"; + version = "0.32.0"; edition = "2021"; - sha256 = "1hnwizzgfhpjfnvml638yy846py8hf2gl1n3p1igbk1srb2ilspg"; + sha256 = "0dyq4myan64sl8wly02jx0gb3jjz7575mn3w8rpphz0xvkq8001c"; libName = "opentelemetry_appender_tracing"; dependencies = [ { @@ -6175,18 +6175,15 @@ rec { ]; features = { "experimental_metadata_attributes" = [ "dep:tracing-log" ]; - "experimental_use_tracing_span_context" = [ "tracing-opentelemetry" ]; "log" = [ "dep:log" ]; - "spec_unstable_logs_enabled" = [ "opentelemetry/spec_unstable_logs_enabled" ]; - "tracing-opentelemetry" = [ "dep:tracing-opentelemetry" ]; }; resolvedDefaultFeatures = [ "default" ]; }; "opentelemetry-http" = rec { crateName = "opentelemetry-http"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "0pc5nw1ds8v8w0nvyall39m92v8m1xl1p3vwvxk6nkhrffdd19np"; + sha256 = "0ca3drvm4fx5nskl7yn42dimy3bg35ppzc85y1p27pz215fh30sn"; libName = "opentelemetry_http"; dependencies = [ { @@ -6222,16 +6219,16 @@ rec { "internal-logs" = [ "opentelemetry/internal-logs" ]; "reqwest" = [ "dep:reqwest" ]; "reqwest-blocking" = [ "dep:reqwest" "reqwest/blocking" ]; - "reqwest-rustls" = [ "dep:reqwest" "reqwest/rustls-tls-native-roots" ]; - "reqwest-rustls-webpki-roots" = [ "dep:reqwest" "reqwest/rustls-tls-webpki-roots" ]; + "reqwest-rustls" = [ "dep:reqwest" "reqwest/default-tls" ]; + "reqwest-rustls-webpki-roots" = [ "dep:reqwest" "reqwest/default-tls" "reqwest/webpki-roots" ]; }; - resolvedDefaultFeatures = [ "internal-logs" "reqwest" "reqwest-blocking" ]; + resolvedDefaultFeatures = [ "reqwest" "reqwest-blocking" ]; }; "opentelemetry-otlp" = rec { crateName = "opentelemetry-otlp"; - version = "0.31.1"; + version = "0.32.0"; edition = "2021"; - sha256 = "07zp0b62b9dajnvvcd6j2ppw5zg7wp4ixka9z6fr3bxrrdmcss8z"; + sha256 = "0d9cys2flpidfxbr6h1103hjc633cax47ihnqgbj0xnicscr4rlr"; libName = "opentelemetry_otlp"; dependencies = [ { @@ -6292,10 +6289,9 @@ rec { usesDefaultFeatures = false; } { - name = "tracing"; - packageId = "tracing"; + name = "tonic-types"; + packageId = "tonic-types"; optional = true; - usesDefaultFeatures = false; } ]; devDependencies = [ @@ -6320,16 +6316,19 @@ rec { ]; features = { "default" = [ "http-proto" "reqwest-blocking-client" "trace" "metrics" "logs" "internal-logs" ]; + "experimental-grpc-retry" = [ "grpc-tonic" "opentelemetry_sdk/experimental_async_runtime" "opentelemetry_sdk/rt-tokio" ]; + "experimental-http-retry" = [ "opentelemetry_sdk/experimental_async_runtime" "opentelemetry_sdk/rt-tokio" "tokio" "httpdate" ]; "flate2" = [ "dep:flate2" ]; - "grpc-tonic" = [ "tonic" "prost" "http" "tokio" "opentelemetry-proto/gen-tonic" ]; + "grpc-tonic" = [ "tonic" "tonic-types" "prost" "http" "tokio" "opentelemetry-proto/gen-tonic" ]; "gzip-http" = [ "flate2" ]; "gzip-tonic" = [ "tonic/gzip" ]; "http" = [ "dep:http" ]; "http-json" = [ "serde_json" "prost" "opentelemetry-http" "opentelemetry-proto/gen-tonic-messages" "opentelemetry-proto/with-serde" "http" "trace" "metrics" ]; "http-proto" = [ "prost" "opentelemetry-http" "opentelemetry-proto/gen-tonic-messages" "http" "trace" "metrics" ]; + "httpdate" = [ "dep:httpdate" ]; "hyper-client" = [ "opentelemetry-http/hyper" ]; "integration-testing" = [ "tonic" "prost" "tokio/full" "trace" "logs" ]; - "internal-logs" = [ "tracing" "opentelemetry_sdk/internal-logs" "opentelemetry-http/internal-logs" ]; + "internal-logs" = [ "opentelemetry_sdk/internal-logs" "opentelemetry/internal-logs" ]; "logs" = [ "opentelemetry/logs" "opentelemetry_sdk/logs" "opentelemetry-proto/logs" ]; "metrics" = [ "opentelemetry/metrics" "opentelemetry_sdk/metrics" "opentelemetry-proto/metrics" ]; "opentelemetry-http" = [ "dep:opentelemetry-http" ]; @@ -6342,27 +6341,27 @@ rec { "serde" = [ "dep:serde" ]; "serde_json" = [ "dep:serde_json" ]; "serialize" = [ "serde" "serde_json" ]; - "tls" = [ "tonic/tls-ring" ]; + "tls" = [ "tls-ring" ]; "tls-aws-lc" = [ "tonic/tls-aws-lc" ]; "tls-provider-agnostic" = [ "tonic/_tls-any" ]; "tls-ring" = [ "tonic/tls-ring" ]; - "tls-roots" = [ "tls" "tonic/tls-native-roots" ]; - "tls-webpki-roots" = [ "tls" "tonic/tls-webpki-roots" ]; + "tls-roots" = [ "tonic/tls-native-roots" ]; + "tls-webpki-roots" = [ "tonic/tls-webpki-roots" ]; "tokio" = [ "dep:tokio" ]; "tonic" = [ "dep:tonic" ]; + "tonic-types" = [ "dep:tonic-types" ]; "trace" = [ "opentelemetry/trace" "opentelemetry_sdk/trace" "opentelemetry-proto/trace" ]; - "tracing" = [ "dep:tracing" ]; "zstd" = [ "dep:zstd" ]; "zstd-http" = [ "zstd" ]; "zstd-tonic" = [ "tonic/zstd" ]; }; - resolvedDefaultFeatures = [ "default" "grpc-tonic" "gzip-tonic" "http" "http-proto" "internal-logs" "logs" "metrics" "opentelemetry-http" "prost" "reqwest" "reqwest-blocking-client" "tokio" "tonic" "trace" "tracing" ]; + resolvedDefaultFeatures = [ "default" "grpc-tonic" "gzip-tonic" "http" "http-proto" "internal-logs" "logs" "metrics" "opentelemetry-http" "prost" "reqwest" "reqwest-blocking-client" "tokio" "tonic" "tonic-types" "trace" ]; }; "opentelemetry-proto" = rec { crateName = "opentelemetry-proto"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "03xkjsjrsm7zkkx5gascqd9bg2z20wymm06l16cyxsp5dpq5s5x7"; + sha256 = "0f5ny4rpnpq6q5q34b8k2q548rf31rpbxkwjqjwzfqxg3yx5imjn"; libName = "opentelemetry_proto"; dependencies = [ { @@ -6406,30 +6405,29 @@ rec { "const-hex" = [ "dep:const-hex" ]; "default" = [ "full" ]; "full" = [ "gen-tonic" "trace" "logs" "metrics" "zpages" "with-serde" "internal-logs" ]; - "gen-tonic" = [ "gen-tonic-messages" "tonic/channel" ]; - "gen-tonic-messages" = [ "tonic" "tonic-prost" "prost" ]; + "gen-tonic" = [ "gen-tonic-messages" "tonic" "tonic-prost" "tonic/channel" ]; + "gen-tonic-messages" = [ "prost" ]; "internal-logs" = [ "opentelemetry/internal-logs" ]; "logs" = [ "opentelemetry/logs" "opentelemetry_sdk/logs" ]; "metrics" = [ "opentelemetry/metrics" "opentelemetry_sdk/metrics" ]; "prost" = [ "dep:prost" ]; "schemars" = [ "dep:schemars" ]; "serde" = [ "dep:serde" ]; - "serde_json" = [ "dep:serde_json" ]; "testing" = [ "opentelemetry/testing" ]; "tonic" = [ "dep:tonic" ]; "tonic-prost" = [ "dep:tonic-prost" ]; "trace" = [ "opentelemetry/trace" "opentelemetry_sdk/trace" ]; "with-schemars" = [ "schemars" ]; - "with-serde" = [ "serde" "const-hex" "base64" "serde_json" ]; + "with-serde" = [ "serde" "const-hex" "base64" ]; "zpages" = [ "trace" ]; }; resolvedDefaultFeatures = [ "gen-tonic" "gen-tonic-messages" "logs" "metrics" "prost" "tonic" "tonic-prost" "trace" ]; }; "opentelemetry-semantic-conventions" = rec { crateName = "opentelemetry-semantic-conventions"; - version = "0.31.0"; + version = "0.32.0"; edition = "2021"; - sha256 = "0in8plv2l2ar7anzi7lrbll0fjfvaymkg5vc5bnvibs1w3gjjbp6"; + sha256 = "0s1x4h1cgmhkxb7i5g02la2vhkf4lg5g26cgn2s2gd1p0j5gk8kc"; libName = "opentelemetry_semantic_conventions"; features = { }; @@ -6437,9 +6435,9 @@ rec { }; "opentelemetry_sdk" = rec { crateName = "opentelemetry_sdk"; - version = "0.31.0"; + version = "0.32.1"; edition = "2021"; - sha256 = "1gbjsggdxfpjbanjvaxa3nq32vfa37i3v13dvx4gsxhrk7sy8jp1"; + sha256 = "1ycl11syranrinhgn4c2hlzhyzyvpa06ryxq5mxgzmf4387ghncv"; dependencies = [ { name = "futures-channel"; @@ -6465,6 +6463,13 @@ rec { packageId = "percent-encoding"; optional = true; } + { + name = "portable-atomic"; + packageId = "portable-atomic"; + usesDefaultFeatures = false; + target = { target, features }: (!("64" == target."has_atomic" or null)); + features = [ "fallback" ]; + } { name = "rand"; packageId = "rand 0.9.4"; @@ -6489,10 +6494,18 @@ rec { optional = true; } ]; + devDependencies = [ + { + name = "tokio"; + packageId = "tokio"; + usesDefaultFeatures = false; + features = [ "macros" "rt-multi-thread" ]; + } + ]; features = { "default" = [ "trace" "metrics" "logs" "internal-logs" ]; "experimental_logs_batch_log_processor_with_async_runtime" = [ "logs" "experimental_async_runtime" ]; - "experimental_logs_concurrent_log_processor" = [ "logs" ]; + "experimental_metrics_bound_instruments" = [ "metrics" "opentelemetry/experimental_metrics_bound_instruments" ]; "experimental_metrics_custom_reader" = [ "metrics" ]; "experimental_metrics_disable_name_validation" = [ "metrics" ]; "experimental_metrics_periodicreader_with_async_runtime" = [ "metrics" "experimental_async_runtime" ]; @@ -6509,15 +6522,14 @@ rec { "rt-tokio-current-thread" = [ "tokio/rt" "tokio/time" "tokio-stream" "experimental_async_runtime" ]; "serde" = [ "dep:serde" ]; "serde_json" = [ "dep:serde_json" ]; - "spec_unstable_logs_enabled" = [ "logs" "opentelemetry/spec_unstable_logs_enabled" ]; "spec_unstable_metrics_views" = [ "metrics" ]; - "testing" = [ "opentelemetry/testing" "trace" "metrics" "logs" "rt-tokio" "rt-tokio-current-thread" "tokio/macros" "tokio/rt-multi-thread" ]; + "testing" = [ "opentelemetry/testing" "trace" "metrics" "logs" "tokio/sync" ]; "tokio" = [ "dep:tokio" ]; "tokio-stream" = [ "dep:tokio-stream" ]; "trace" = [ "opentelemetry/trace" "rand" "percent-encoding" ]; "url" = [ "dep:url" ]; }; - resolvedDefaultFeatures = [ "default" "experimental_async_runtime" "internal-logs" "logs" "metrics" "percent-encoding" "rand" "rt-tokio" "spec_unstable_logs_enabled" "tokio" "tokio-stream" "trace" ]; + resolvedDefaultFeatures = [ "default" "experimental_async_runtime" "internal-logs" "logs" "metrics" "percent-encoding" "rand" "rt-tokio" "tokio" "tokio-stream" "trace" ]; }; "ordered-float" = rec { crateName = "ordered-float"; @@ -7026,7 +7038,7 @@ rec { "default" = [ "fallback" ]; "serde" = [ "dep:serde" ]; }; - resolvedDefaultFeatures = [ "require-cas" ]; + resolvedDefaultFeatures = [ "fallback" "require-cas" ]; }; "portable-atomic-util" = rec { crateName = "portable-atomic-util"; @@ -7294,6 +7306,34 @@ rec { ]; }; + "prost-types" = rec { + crateName = "prost-types"; + version = "0.14.3"; + edition = "2021"; + sha256 = "1mrxrciryfgi6a0vmrgyj3g27r9hdhlgwkq71cgv3icbvg5w94c9"; + libName = "prost_types"; + authors = [ + "Dan Burkert " + "Lucio Franco " + "Casper Meijn " + "Tokio Contributors " + ]; + dependencies = [ + { + name = "prost"; + packageId = "prost"; + usesDefaultFeatures = false; + features = [ "derive" ]; + } + ]; + features = { + "arbitrary" = [ "dep:arbitrary" ]; + "chrono" = [ "dep:chrono" ]; + "default" = [ "std" ]; + "std" = [ "prost/std" ]; + }; + resolvedDefaultFeatures = [ "default" "std" ]; + }; "quote" = rec { crateName = "quote"; version = "1.0.45"; @@ -7723,9 +7763,9 @@ rec { }; "reqwest" = rec { crateName = "reqwest"; - version = "0.12.28"; + version = "0.13.4"; edition = "2021"; - sha256 = "0iqidijghgqbzl3bjg5hb4zmigwa4r612bgi0yiq0c90b6jkrpgd"; + sha256 = "1hy1plns9krbh3h1dy2sdjygsfkdcnxm6pbxdi0ya9b5vq8mi711"; authors = [ "Sean McArthur " ]; @@ -7742,7 +7782,7 @@ rec { name = "futures-channel"; packageId = "futures-channel"; optional = true; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "futures-core"; @@ -7762,62 +7802,44 @@ rec { { name = "http-body"; packageId = "http-body"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "http-body-util"; packageId = "http-body-util"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "hyper"; packageId = "hyper"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "client" ]; } { name = "hyper-util"; packageId = "hyper-util"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "client" "client-legacy" "client-proxy" "tokio" ]; } { name = "js-sys"; packageId = "js-sys"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); } { name = "log"; packageId = "log"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "percent-encoding"; packageId = "percent-encoding"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "pin-project-lite"; packageId = "pin-project-lite"; - target = { target, features }: (!("wasm32" == target."arch" or null)); - } - { - name = "serde"; - packageId = "serde"; - } - { - name = "serde_json"; - packageId = "serde_json"; - optional = true; - } - { - name = "serde_json"; - packageId = "serde_json"; - target = { target, features }: ("wasm32" == target."arch" or null); - } - { - name = "serde_urlencoded"; - packageId = "serde_urlencoded"; + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "sync_wrapper"; @@ -7828,27 +7850,27 @@ rec { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "net" "time" ]; } { name = "tower"; packageId = "tower"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "retry" "timeout" "util" ]; } { name = "tower-http"; packageId = "tower-http"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "follow-redirect" ]; } { name = "tower-service"; packageId = "tower-service"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); } { name = "url"; @@ -7857,17 +7879,17 @@ rec { { name = "wasm-bindgen"; packageId = "wasm-bindgen"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); } { name = "wasm-bindgen-futures"; packageId = "wasm-bindgen-futures"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); } { name = "web-sys"; packageId = "web-sys"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); features = [ "AbortController" "AbortSignal" "Headers" "Request" "RequestInit" "RequestMode" "Response" "Window" "FormData" "Blob" "BlobPropertyBag" "ServiceWorkerGlobalScope" "RequestCredentials" "File" "ReadableStream" "RequestCache" ]; } ]; @@ -7876,33 +7898,27 @@ rec { name = "futures-util"; packageId = "futures-util"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "std" "alloc" ]; } { name = "hyper"; packageId = "hyper"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "http2" "client" "server" ]; } { name = "hyper-util"; packageId = "hyper-util"; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "http1" "http2" "client" "client-legacy" "server-auto" "server-graceful" "tokio" ]; } - { - name = "serde"; - packageId = "serde"; - target = { target, features }: (!("wasm32" == target."arch" or null)); - features = [ "derive" ]; - } { name = "tokio"; packageId = "tokio"; usesDefaultFeatures = false; - target = { target, features }: (!("wasm32" == target."arch" or null)); + target = { target, features }: (!(("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null)))); features = [ "macros" "rt-multi-thread" ]; } { @@ -7914,40 +7930,37 @@ rec { { name = "wasm-bindgen"; packageId = "wasm-bindgen"; - target = { target, features }: ("wasm32" == target."arch" or null); + target = { target, features }: (("wasm32" == target."arch" or null) && (("unknown" == target."os" or null) || ("none" == target."os" or null))); features = [ "serde-serialize" ]; } ]; features = { + "__native-tls" = [ "dep:hyper-tls" "dep:native-tls-crate" "__tls" "dep:tokio-native-tls" ]; + "__native-tls-alpn" = [ "native-tls-crate?/alpn" "hyper-tls?/alpn" ]; "__rustls" = [ "dep:hyper-rustls" "dep:tokio-rustls" "dep:rustls" "__tls" ]; - "__rustls-ring" = [ "hyper-rustls?/ring" "tokio-rustls?/ring" "rustls?/ring" "quinn?/ring" ]; + "__rustls-aws-lc-rs" = [ "hyper-rustls?/aws-lc-rs" "tokio-rustls?/aws-lc-rs" "rustls?/aws-lc-rs" "quinn?/rustls-aws-lc-rs" ]; "__tls" = [ "dep:rustls-pki-types" "tokio/io-util" ]; "blocking" = [ "dep:futures-channel" "futures-channel?/sink" "dep:futures-util" "futures-util?/io" "futures-util?/sink" "tokio/sync" ]; "brotli" = [ "tower-http/decompression-br" ]; "charset" = [ "dep:encoding_rs" "dep:mime" ]; "cookies" = [ "dep:cookie_crate" "dep:cookie_store" ]; "default" = [ "default-tls" "charset" "http2" "system-proxy" ]; - "default-tls" = [ "dep:hyper-tls" "dep:native-tls-crate" "__tls" "dep:tokio-native-tls" ]; + "default-tls" = [ "rustls" ]; "deflate" = [ "tower-http/decompression-deflate" ]; + "form" = [ "dep:serde" "dep:serde_urlencoded" ]; "gzip" = [ "tower-http/decompression-gzip" ]; - "h2" = [ "dep:h2" ]; "hickory-dns" = [ "dep:hickory-resolver" "dep:once_cell" ]; - "http2" = [ "h2" "hyper/http2" "hyper-util/http2" "hyper-rustls?/http2" ]; - "http3" = [ "rustls-tls-manual-roots" "dep:h3" "dep:h3-quinn" "dep:quinn" "tokio/macros" ]; - "json" = [ "dep:serde_json" ]; - "macos-system-configuration" = [ "system-proxy" ]; + "http2" = [ "dep:h2" "hyper/http2" "hyper-util/http2" "hyper-rustls?/http2" ]; + "http3" = [ "rustls" "dep:h3" "dep:h3-quinn" "dep:quinn" "tokio/macros" ]; + "json" = [ "dep:serde" "dep:serde_json" ]; "multipart" = [ "dep:mime_guess" "dep:futures-util" ]; - "native-tls" = [ "default-tls" ]; - "native-tls-alpn" = [ "native-tls" "native-tls-crate?/alpn" "hyper-tls?/alpn" ]; - "native-tls-vendored" = [ "native-tls" "native-tls-crate?/vendored" ]; - "rustls-tls" = [ "rustls-tls-webpki-roots" ]; - "rustls-tls-manual-roots" = [ "rustls-tls-manual-roots-no-provider" "__rustls-ring" ]; - "rustls-tls-manual-roots-no-provider" = [ "__rustls" ]; - "rustls-tls-native-roots" = [ "rustls-tls-native-roots-no-provider" "__rustls-ring" ]; - "rustls-tls-native-roots-no-provider" = [ "dep:rustls-native-certs" "hyper-rustls?/native-tokio" "__rustls" ]; - "rustls-tls-no-provider" = [ "rustls-tls-manual-roots-no-provider" ]; - "rustls-tls-webpki-roots" = [ "rustls-tls-webpki-roots-no-provider" "__rustls-ring" ]; - "rustls-tls-webpki-roots-no-provider" = [ "dep:webpki-roots" "hyper-rustls?/webpki-tokio" "__rustls" ]; + "native-tls" = [ "__native-tls" "__native-tls-alpn" ]; + "native-tls-no-alpn" = [ "__native-tls" ]; + "native-tls-vendored" = [ "__native-tls" "native-tls-crate?/vendored" "__native-tls-alpn" ]; + "native-tls-vendored-no-alpn" = [ "__native-tls" "native-tls-crate?/vendored" ]; + "query" = [ "dep:serde" "dep:serde_urlencoded" ]; + "rustls" = [ "__rustls-aws-lc-rs" "dep:rustls-platform-verifier" "__rustls" ]; + "rustls-no-provider" = [ "dep:rustls-platform-verifier" "__rustls" ]; "stream" = [ "tokio/fs" "dep:futures-util" "dep:tokio-util" "dep:wasm-streams" ]; "system-proxy" = [ "hyper-util/client-proxy-system" ]; "zstd" = [ "tower-http/decompression-zstd" ]; @@ -9321,29 +9334,25 @@ rec { }; resolvedDefaultFeatures = [ "alloc" "default" "rust_1_61" "rust_1_65" "std" ]; }; - "snafu 0.9.0" = rec { + "snafu 0.9.1" = rec { crateName = "snafu"; - version = "0.9.0"; + version = "0.9.1"; edition = "2018"; - sha256 = "1ii9r99x5qcn754m624yzgb9hzvkqkrcygf0aqh0pyb9dbnvrm6i"; + sha256 = "08k5yfydxdlshivfhrdq9km8qn02r93q28gkyvazbqz2icr1586i"; authors = [ "Jake Goulding " ]; dependencies = [ { name = "snafu-derive"; - packageId = "snafu-derive 0.9.0"; + packageId = "snafu-derive 0.9.1"; } ]; features = { - "backtrace" = [ "dep:backtrace" ]; - "backtraces-impl-backtrace-crate" = [ "backtrace" ]; + "backtraces-impl-backtrace-crate" = [ "dep:backtrace" ]; "default" = [ "std" "rust_1_81" ]; - "futures" = [ "futures-core-crate" "pin-project" ]; - "futures-core-crate" = [ "dep:futures-core-crate" ]; - "futures-crate" = [ "dep:futures-crate" ]; - "internal-dev-dependencies" = [ "futures-crate" ]; - "pin-project" = [ "dep:pin-project" ]; + "futures" = [ "dep:futures-core" "dep:pin-project" ]; + "internal-dev-dependencies" = [ "dep:futures" ]; "std" = [ "alloc" ]; "unstable-provider-api" = [ "snafu-derive/unstable-provider-api" ]; }; @@ -9411,11 +9420,11 @@ rec { }; resolvedDefaultFeatures = [ "rust_1_61" ]; }; - "snafu-derive 0.9.0" = rec { + "snafu-derive 0.9.1" = rec { crateName = "snafu-derive"; - version = "0.9.0"; + version = "0.9.1"; edition = "2018"; - sha256 = "0h0x61kyj4fvilcr2nj02l85shw1ika64vq9brf2gyna662ln9al"; + sha256 = "1nkfi7bis72pz3w7vb64m79w49qsv20sbf19jkd471vbhr83q42z"; procMacro = true; libName = "snafu_derive"; authors = [ @@ -9441,7 +9450,7 @@ rec { name = "syn"; packageId = "syn 2.0.117"; usesDefaultFeatures = false; - features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" ]; + features = [ "clone-impls" "derive" "full" "parsing" "printing" "proc-macro" "visit-mut" ]; } ]; features = { @@ -9599,7 +9608,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator"; @@ -9647,7 +9656,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_certs"; @@ -9706,7 +9715,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-shared"; @@ -9750,7 +9759,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_operator"; @@ -9801,6 +9810,10 @@ rec { name = "indexmap"; packageId = "indexmap"; } + { + name = "java-properties"; + packageId = "java-properties"; + } { name = "jiff"; packageId = "jiff"; @@ -9858,7 +9871,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-operator-derive"; @@ -9916,12 +9929,17 @@ rec { name = "uuid"; packageId = "uuid"; } + { + name = "xml"; + packageId = "xml"; + } ]; features = { "certs" = [ "dep:stackable-certs" ]; + "client-feature-gates" = [ "dep:winnow" ]; "crds" = [ "dep:stackable-versioned" ]; "default" = [ "crds" ]; - "full" = [ "crds" "certs" "time" "webhook" "kube-ws" ]; + "full" = [ "client-feature-gates" "crds" "certs" "time" "webhook" "kube-ws" ]; "kube-ws" = [ "kube/ws" ]; "time" = [ "stackable-shared/time" ]; "webhook" = [ "dep:stackable-webhook" ]; @@ -9935,7 +9953,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; procMacro = true; @@ -9965,12 +9983,12 @@ rec { }; "stackable-shared" = rec { crateName = "stackable-shared"; - version = "0.1.0"; + version = "0.1.1"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_shared"; @@ -10015,7 +10033,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -10046,12 +10064,12 @@ rec { }; "stackable-telemetry" = rec { crateName = "stackable-telemetry"; - version = "0.6.3"; + version = "0.6.4"; edition = "2024"; workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_telemetry"; @@ -10095,7 +10113,7 @@ rec { { name = "opentelemetry_sdk"; packageId = "opentelemetry_sdk"; - features = [ "rt-tokio" "logs" "rt-tokio" "spec_unstable_logs_enabled" ]; + features = [ "rt-tokio" "logs" "rt-tokio" ]; } { name = "pin-project"; @@ -10103,7 +10121,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "strum"; @@ -10161,7 +10179,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_versioned"; @@ -10195,7 +10213,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-versioned-macros"; @@ -10211,7 +10229,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; procMacro = true; @@ -10279,7 +10297,7 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "a31cd2514445b251038fc4ea7abc28c57b2a6ad9"; + rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; }; libName = "stackable_webhook"; @@ -10352,7 +10370,7 @@ rec { } { name = "snafu"; - packageId = "snafu 0.9.0"; + packageId = "snafu 0.9.1"; } { name = "stackable-certs"; @@ -11443,6 +11461,33 @@ rec { } ]; + }; + "tonic-types" = rec { + crateName = "tonic-types"; + version = "0.14.5"; + edition = "2021"; + sha256 = "16bk1cxi2m0xgaabf98nnj7dn9j16ymkh27jq4s3shjm4a85m1ra"; + libName = "tonic_types"; + authors = [ + "Lucio Franco " + "Rafael Lemos " + ]; + dependencies = [ + { + name = "prost"; + packageId = "prost"; + } + { + name = "prost-types"; + packageId = "prost-types"; + } + { + name = "tonic"; + packageId = "tonic"; + usesDefaultFeatures = false; + } + ]; + }; "tower" = rec { crateName = "tower"; @@ -11899,9 +11944,9 @@ rec { }; "tracing-opentelemetry" = rec { crateName = "tracing-opentelemetry"; - version = "0.32.1"; + version = "0.33.0"; edition = "2021"; - sha256 = "1z2jjmxbkm1qawlb3bm99x8xwf4g8wjkbcknm9z4fv1w14nqzhhs"; + sha256 = "09nvxy5m7nxmifz4b6szdcyczapp2jcgxcac0jw4ax8klz5n9g5d"; libName = "tracing_opentelemetry"; dependencies = [ { @@ -14021,9 +14066,9 @@ rec { }; "xml" = rec { crateName = "xml"; - version = "1.2.1"; + version = "1.3.0"; edition = "2021"; - sha256 = "0ak4k990faralbli5a0rb8kvwihccb2rp0r94d4azfy94a6lkamq"; + sha256 = "128s58qhq8whrx90zbw8r5algr7lakgbf7mn05jfk234rbjqavv3"; authors = [ "Vladimir Matveev " "Kornel (https://github.com/kornelski)" diff --git a/Cargo.toml b/Cargo.toml index b11fdea9..89d4bbc9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ repository = "https://github.com/stackabletech/airflow-operator" stackable-operator = { git = "https://github.com/stackabletech/operator-rs.git", features = [ "crds", "webhook", -], tag = "stackable-operator-0.111.0" } +], tag = "stackable-operator-0.111.1" } anyhow = "1.0" built = { version = "0.8", features = ["chrono", "git2"] } diff --git a/crate-hashes.json b/crate-hashes.json index 362b463b..014d9478 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -3,8 +3,8 @@ "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", diff --git a/rust/operator-binary/src/config/mod.rs b/rust/operator-binary/src/config/mod.rs index 49899f42..d4213c18 100644 --- a/rust/operator-binary/src/config/mod.rs +++ b/rust/operator-binary/src/config/mod.rs @@ -1,5 +1,4 @@ pub mod webserver_config; -pub mod writer; use std::collections::BTreeMap; diff --git a/rust/operator-binary/src/config/webserver_config.rs b/rust/operator-binary/src/config/webserver_config.rs index 17311f0c..914e2a50 100644 --- a/rust/operator-binary/src/config/webserver_config.rs +++ b/rust/operator-binary/src/config/webserver_config.rs @@ -4,8 +4,9 @@ use std::{collections::BTreeMap, io::Write}; use snafu::{ResultExt, Snafu}; +use stackable_operator::v2::flask_config_writer; -use super::{PYTHON_IMPORTS, add_airflow_config, writer}; +use super::{PYTHON_IMPORTS, add_airflow_config}; use crate::crd::{ AirflowConfigOptions, authentication::AirflowClientAuthenticationDetailsResolved, authorization::AirflowAuthorizationResolved, @@ -23,7 +24,7 @@ pub enum Error { #[snafu(display("failed to write the webserver config file"))] WriteConfigFile { - source: writer::FlaskAppConfigWriterError, + source: flask_config_writer::FlaskAppConfigWriterError, }, #[snafu(display("failed to write the header/footer to the webserver config file"))] @@ -65,8 +66,12 @@ pub fn build( let temp_file_footer: Option = config.remove(CONFIG_OVERRIDE_FILE_FOOTER_KEY); - writer::write::(&mut config_file, config.iter(), PYTHON_IMPORTS) - .context(WriteConfigFileSnafu)?; + flask_config_writer::write::( + &mut config_file, + config.iter(), + PYTHON_IMPORTS, + ) + .context(WriteConfigFileSnafu)?; if let Some(footer) = temp_file_footer { writeln!(config_file, "{}", footer).context(WriteHeaderFooterSnafu)?; diff --git a/rust/operator-binary/src/config/writer.rs b/rust/operator-binary/src/config/writer.rs deleted file mode 100644 index 7c34dec7..00000000 --- a/rust/operator-binary/src/config/writer.rs +++ /dev/null @@ -1,331 +0,0 @@ -//! Writer for Flask App configurations (Python config files). -//! -//! Vendored verbatim from `product_config::flask_app_config_writer` so the -//! operator does not depend on the `product-config` crate. Applications based -//! on the Flask App Builder (e.g. Apache Airflow, Apache Superset) use -//! configuration files written in Python. This writer only covers top-level -//! assignments of a few primitive types and expressions — it is not a general -//! Python code generator. -//! -//! Primitive types are escaped accordingly. Python expressions are written as-is; -//! invalid expressions produce invalid configuration files. Config overrides that do -//! not map to a known option are treated as plain expressions. -//! -// TODO: This file is vendored identically in airflow-operator and superset-operator; -// it is a candidate for a shared crate (e.g. operator-rs) so the duplication can be -// removed. Until such a home exists it is kept identical in both operators and to the -// upstream source. - -use std::{ - io::{self, Write}, - num::ParseIntError, - str::{FromStr, ParseBoolError}, -}; - -use snafu::{ResultExt, Snafu}; - -/// Errors which can occur when using this module -// Variant names share the `Error` suffix; kept as-is from the vendored -// `product_config::flask_app_config_writer` source. -#[allow(clippy::enum_variant_names)] -#[derive(Debug, Snafu)] -pub enum FlaskAppConfigWriterError { - #[snafu(display("failed to convert '{value}' into a identifier"))] - ConvertIdentifierError { value: String }, - - #[snafu(display("failed to convert '{value}' into a boolean literal"))] - ConvertBoolLiteralError { - value: String, - source: ParseBoolError, - }, - - #[snafu(display("failed to convert '{value}' into an integer literal"))] - ConvertIntLiteralError { - value: String, - source: ParseIntError, - }, - - #[snafu(display("failed to convert '{value}' into an ASCII string literal"))] - ConvertStringLiteralError { value: String }, - - #[snafu(display("failed to convert '{value}' into a Python expression"))] - ConvertExpressionError { value: String }, - - #[snafu(display("Configuration cannot be written."))] - WriteConfigError { source: io::Error }, -} - -/// Mapping from configuration options to Python types. -pub trait FlaskAppConfigOptions { - fn python_type(&self) -> PythonType; -} - -/// All supported Python types -pub enum PythonType { - /// Python identifier - Identifier, - /// Boolean literal - BoolLiteral, - /// Integer literal - IntLiteral, - /// ASCII string literal - StringLiteral, - /// Python expression - Expression, -} - -impl PythonType { - /// Converts the given string to Python. - fn convert_to_python(&self, value: &str) -> Result { - let convert = match self { - PythonType::Identifier => PythonType::convert_to_python_identifier, - PythonType::BoolLiteral => PythonType::convert_to_python_bool_literal, - PythonType::IntLiteral => PythonType::convert_to_python_int_literal, - PythonType::StringLiteral => PythonType::convert_to_python_string_literal, - PythonType::Expression => PythonType::convert_to_python_expression, - }; - - convert(value) - } - - fn convert_to_python_identifier(value: &str) -> Result { - if value.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') - && value - .chars() - .next() - .filter(|c| !c.is_ascii_digit()) - .is_some() - { - Ok(value.to_string()) - } else { - ConvertIdentifierSnafu { value }.fail() - } - } - - fn convert_to_python_bool_literal(value: &str) -> Result { - value - .parse::() - .map(|b| if b { "True".into() } else { "False".into() }) - .context(ConvertBoolLiteralSnafu { value }) - } - - fn convert_to_python_int_literal(value: &str) -> Result { - value - .parse::() - .map(|i| i.to_string()) - .context(ConvertIntLiteralSnafu { value }) - } - - fn convert_to_python_string_literal(value: &str) -> Result { - if value.is_ascii() { - Ok(format!("\"{}\"", value.escape_default())) - } else { - ConvertStringLiteralSnafu { value }.fail() - } - } - - fn convert_to_python_expression(value: &str) -> Result { - if !value.trim().is_empty() { - Ok(value.to_string()) - } else { - ConvertExpressionSnafu { value }.fail() - } - } -} - -/// Writes a configuration file according to the given `FlaskAppConfigOptions` type. -pub fn write<'a, O, P, W>( - writer: &mut W, - properties: P, - imports: &[&str], -) -> Result<(), FlaskAppConfigWriterError> -where - O: FlaskAppConfigOptions + FromStr, - P: Iterator, - W: Write, -{ - for import in imports { - writeln!(writer, "{import}").context(WriteConfigSnafu)?; - } - - writeln!(writer).context(WriteConfigSnafu)?; - - for (name, value) in properties { - let variable = PythonType::Identifier.convert_to_python(name)?; - - // If an option cannot be mapped to a Python type then it is a config override and treated - // as Python expression. - let content = O::from_str(name) - .map(|option| option.python_type()) - .unwrap_or(PythonType::Expression) - .convert_to_python(value)?; - - writeln!(writer, "{variable} = {content}").context(WriteConfigSnafu)?; - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use std::{ - collections::BTreeMap, - str::{FromStr, from_utf8}, - }; - - use rstest::*; - - use super::{FlaskAppConfigOptions, FlaskAppConfigWriterError, PythonType, write}; - - #[rstest] - #[case::valid_identifiers_are_converted_to_python( - PythonType::Identifier, &[ - ("_", "_"), - ("a", "a"), - ("A", "A"), - ("__", "__"), - ("_a", "_a"), - ("_A", "_A"), - ("_0", "_0"), - ("SECRET_KEY", "SECRET_KEY"), - ] - )] - #[case::valid_booleans_are_converted_to_python( - PythonType::BoolLiteral, &[ - ("False", "false"), - ("True", "true"), - ] - )] - #[case::valid_integers_are_converted_to_python( - PythonType::IntLiteral, &[ - ("-9223372036854775808", "-9223372036854775808"), - ("0", "0"), - ("9223372036854775807", "9223372036854775807"), - ] - )] - #[case::valid_strings_are_converted_to_python( - PythonType::StringLiteral, &[ - (r#""""#, ""), - (r#"" ~""#, " ~"), - (r#""\t\r\n\'\"\\""#, "\t\r\n'\"\\"), - ] - )] - #[case::valid_expressions_are_converted_to_python( - PythonType::Expression, &[ - ("os.environ[\"HOME\"]", "os.environ[\"HOME\"]"), - ] - )] - fn valid_values_are_converted_to_python( - #[case] python_type: PythonType, - #[case] values: &[(&str, &str)], - ) -> Result<(), FlaskAppConfigWriterError> { - for (expected, input) in values { - assert_eq!(*expected, python_type.convert_to_python(input)?); - } - - Ok(()) - } - - #[rstest] - #[case::invalid_identifiers_are_not_converted_to_python( - PythonType::Identifier, &[ - "", "0", "-", "\n", "_-", "_\n", - ] - )] - #[case::invalid_booleans_are_not_converted_to_python( - PythonType::BoolLiteral, &[ - "", "False", "True", "0", "1", - ] - )] - #[case::invalid_integers_are_not_converted_to_python( - PythonType::IntLiteral, &[ - "", "a", "0x10", "inf", - ] - )] - #[case::invalid_strings_are_not_converted_to_python( - PythonType::StringLiteral, &[ - "ä", "❤" - ] - )] - #[case::invalid_expressions_are_not_converted_to_python( - PythonType::Expression, &[ - "" - ] - )] - fn invalid_values_are_converted_to_python( - #[case] python_type: PythonType, - #[case] values: &[&str], - ) { - for input in values { - assert!(python_type.convert_to_python(input).is_err()); - } - } - - #[test] - fn valid_options_are_written_into_a_configuration() -> Result<(), FlaskAppConfigWriterError> { - #[allow(clippy::enum_variant_names)] - enum Options { - BoolOption, - IntOption, - StringOption, - ExpressionOption, - _UnusedOption, - } - - impl FromStr for Options { - type Err = &'static str; - - fn from_str(s: &str) -> Result { - match s { - "BOOL_OPTION" => Ok(Options::BoolOption), - "INT_OPTION" => Ok(Options::IntOption), - "STRING_OPTION" => Ok(Options::StringOption), - "EXPRESSION_OPTION" => Ok(Options::ExpressionOption), - _ => Err("unknown option"), - } - } - } - - impl FlaskAppConfigOptions for Options { - fn python_type(&self) -> PythonType { - match self { - Options::BoolOption => PythonType::BoolLiteral, - Options::IntOption => PythonType::IntLiteral, - Options::StringOption => PythonType::StringLiteral, - Options::ExpressionOption => PythonType::Expression, - Options::_UnusedOption => PythonType::Expression, - } - } - } - - let config: BTreeMap<_, _> = [ - ("BOOL_OPTION", "true"), - ("INT_OPTION", "0"), - ("STRING_OPTION", ""), - ("EXPRESSION_OPTION", "{ \"key\": \"value\" }"), - ("OVERRIDDEN_OPTION", "None"), - ] - .map(|(k, v)| (k.to_string(), v.to_string())) - .into(); - - let imports = ["import module", "from module import member"]; - - let mut config_file = Vec::new(); - write::(&mut config_file, config.iter(), &imports)?; - - assert_eq!( - r#"import module -from module import member - -BOOL_OPTION = True -EXPRESSION_OPTION = { "key": "value" } -INT_OPTION = 0 -OVERRIDDEN_OPTION = None -STRING_OPTION = "" -"#, - from_utf8(&config_file).unwrap() - ); - - Ok(()) - } -} diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index c28543e4..e7b228d6 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -39,13 +39,15 @@ use stackable_operator::{ shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, utils::{COMMON_BASH_TRAP_FUNCTIONS, crds::raw_object_list_schema}, - v2::config_overrides::KeyValueConfigOverrides, + v2::{ + config_overrides::KeyValueConfigOverrides, + flask_config_writer::{FlaskAppConfigOptions, PythonType}, + }, versioned::versioned, }; use strum::{Display, EnumIter, EnumString, IntoEnumIterator}; use crate::{ - config::writer::{FlaskAppConfigOptions, PythonType}, crd::{ affinity::{get_affinity, get_executor_affinity}, authentication::{ From b4046425ed297ad39e6500e3b0edcec93ff6b001 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 8 Jun 2026 11:03:39 +0200 Subject: [PATCH 17/35] updated changelog --- CHANGELOG.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 77ef92f0..1c35b501 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,11 +24,9 @@ The broker `spec.celeryExecutors.broker` is now `spec.clusterConfig.celeryBroker`. - Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#795]). - test: Bump vector-aggregator to 0.55.0, replace /graphql call with gRPC call ([#801]). -- Removed the product-config based configuration validation. - Config and environment overrides are now merged directly from the CRD, and dereferenced objects are added to the validated cluster. +- BREAKING: Removed product-config machinery. This is a breaking change in terms of configuration. + Users relying on the product-config `properties.yaml` file have to set these properties via the CRD. The `--product-config` CLI flag is now a no-op ([#804]). -- The Flask config-file writer for `webserver_config.py` is now provided by `stackable-operator` - (`v2::flask_config_writer`) instead of a vendored copy ([#804]). ### Fixed From 2dfb36e32d0bbf325364bf2fb032f8913e748b3f Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 8 Jun 2026 11:31:06 +0200 Subject: [PATCH 18/35] remove result as return value as it is now used in calling function --- rust/operator-binary/src/controller/build/config_map.rs | 5 +---- rust/operator-binary/src/product_logging.rs | 7 +------ 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index b1bc88ab..0804dee1 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -98,10 +98,7 @@ pub fn build_rolegroup_config_map( &Container::Vector, &mut cm_builder, &validated_cluster.image, - ) - .context(InvalidLoggingConfigSnafu { - cm_name: rolegroup.object_name(), - })?; + ); cm_builder.build().with_context(|_| BuildConfigMapSnafu { rolegroup: rolegroup.clone(), diff --git a/rust/operator-binary/src/product_logging.rs b/rust/operator-binary/src/product_logging.rs index 51572729..a3f85258 100644 --- a/rust/operator-binary/src/product_logging.rs +++ b/rust/operator-binary/src/product_logging.rs @@ -32,8 +32,6 @@ pub enum Error { MissingVectorAggregatorAddress, } -type Result = std::result::Result; - const LOG_CONFIG_FILE: &str = "log_config.py"; const LOG_FILE: &str = "airflow.py.json"; @@ -45,8 +43,7 @@ pub fn extend_config_map_with_log_config( vector_container: &C, cm_builder: &mut ConfigMapBuilder, resolved_product_image: &ResolvedProductImage, -) -> Result<()> -where +) where C: Clone + Ord + Display, K: Resource, { @@ -76,8 +73,6 @@ where product_logging::framework::create_vector_config(rolegroup, vector_log_config), ); } - - Ok(()) } fn create_airflow_config( From cd28b2a37b4ca321f6e1dcdc02c415c4c57bb897 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 8 Jun 2026 15:17:03 +0200 Subject: [PATCH 19/35] make webserver_config_py non-optional to bring in line with other operators --- rust/operator-binary/src/crd/mod.rs | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index e7b228d6..b9eeed53 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -107,12 +107,8 @@ pub type AirflowWebserverRoleType = #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AirflowConfigOverrides { - #[serde( - default, - rename = "webserver_config.py", - skip_serializing_if = "Option::is_none" - )] - pub webserver_config_py: Option, + #[serde(default, rename = "webserver_config.py")] + pub webserver_config_py: KeyValueConfigOverrides, } #[derive(Clone, Debug)] @@ -458,15 +454,18 @@ impl v1alpha2::AirflowCluster { .config .config_overrides .webserver_config_py - .as_ref() - .map(|o| o.overrides.clone()) - .unwrap_or_default(); + .overrides + .clone(); if let Some(rg) = role_config.role_groups.get(rolegroup_name) { env_overrides.extend(rg.config.env_overrides.clone()); - if let Some(rg_file) = rg.config.config_overrides.webserver_config_py.as_ref() { - file_overrides.extend(rg_file.overrides.clone()); - } + file_overrides.extend( + rg.config + .config_overrides + .webserver_config_py + .overrides + .clone(), + ); } let config_file_overrides = file_overrides From c00ab43966a7636a3e560871d8548a89c70e8cdb Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 8 Jun 2026 15:59:52 +0200 Subject: [PATCH 20/35] bring addition of log config config maps inline --- .../src/controller/build/config_map.rs | 46 +++++++++++---- rust/operator-binary/src/product_logging.rs | 58 ++----------------- 2 files changed, 39 insertions(+), 65 deletions(-) diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 0804dee1..a68f252e 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -7,7 +7,10 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::{configmap::ConfigMapBuilder, meta::ObjectMetaBuilder}, k8s_openapi::api::core::v1::ConfigMap, - product_logging::spec::Logging, + product_logging::{ + self, + spec::{ContainerLogConfig, ContainerLogConfigChoice, Logging}, + }, role_utils::RoleGroupRef, }; @@ -15,8 +18,10 @@ use crate::{ airflow_controller::AIRFLOW_CONTROLLER_NAME, config::webserver_config, controller::validate::ValidatedAirflowCluster, - crd::{AIRFLOW_CONFIG_FILENAME, Container, build_recommended_labels, v1alpha2}, - product_logging::extend_config_map_with_log_config, + crd::{ + AIRFLOW_CONFIG_FILENAME, Container, STACKABLE_LOG_DIR, build_recommended_labels, v1alpha2, + }, + product_logging::{LOG_CONFIG_FILE, create_airflow_config}, }; #[derive(Snafu, Debug)] @@ -91,14 +96,33 @@ pub fn build_rolegroup_config_map( ) .add_data(AIRFLOW_CONFIG_FILENAME, config_file); - extend_config_map_with_log_config( - rolegroup, - logging, - container, - &Container::Vector, - &mut cm_builder, - &validated_cluster.image, - ); + // Log config for the main container, when it uses an Automatic log config. + if let Some(ContainerLogConfig { + choice: Some(ContainerLogConfigChoice::Automatic(log_config)), + }) = logging.containers.get(container) + { + let log_dir = format!("{STACKABLE_LOG_DIR}/{container}"); + cm_builder.add_data( + LOG_CONFIG_FILE, + create_airflow_config(log_config, &log_dir, &validated_cluster.image), + ); + } + + // Vector agent config, when enabled. + if logging.enable_vector_agent { + let vector_log_config = if let Some(ContainerLogConfig { + choice: Some(ContainerLogConfigChoice::Automatic(log_config)), + }) = logging.containers.get(&Container::Vector) + { + Some(log_config) + } else { + None + }; + cm_builder.add_data( + product_logging::framework::VECTOR_CONFIG_FILE, + product_logging::framework::create_vector_config(rolegroup, vector_log_config), + ); + } cm_builder.build().with_context(|_| BuildConfigMapSnafu { rolegroup: rolegroup.clone(), diff --git a/rust/operator-binary/src/product_logging.rs b/rust/operator-binary/src/product_logging.rs index a3f85258..4f985945 100644 --- a/rust/operator-binary/src/product_logging.rs +++ b/rust/operator-binary/src/product_logging.rs @@ -1,21 +1,11 @@ -use std::fmt::{Display, Write}; +use std::fmt::Write; use snafu::Snafu; use stackable_operator::{ - builder::configmap::ConfigMapBuilder, commons::product_image_selection::ResolvedProductImage, - kube::Resource, - product_logging::{ - self, - spec::{ - AutomaticContainerLogConfig, ContainerLogConfig, ContainerLogConfigChoice, Logging, - }, - }, - role_utils::RoleGroupRef, + product_logging::spec::AutomaticContainerLogConfig, }; -use crate::crd::STACKABLE_LOG_DIR; - #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("failed to retrieve the ConfigMap [{cm_name}]"))] @@ -32,50 +22,10 @@ pub enum Error { MissingVectorAggregatorAddress, } -const LOG_CONFIG_FILE: &str = "log_config.py"; +pub const LOG_CONFIG_FILE: &str = "log_config.py"; const LOG_FILE: &str = "airflow.py.json"; -/// Extend the ConfigMap with logging and Vector configurations -pub fn extend_config_map_with_log_config( - rolegroup: &RoleGroupRef, - logging: &Logging, - main_container: &C, - vector_container: &C, - cm_builder: &mut ConfigMapBuilder, - resolved_product_image: &ResolvedProductImage, -) where - C: Clone + Ord + Display, - K: Resource, -{ - if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(main_container) - { - let log_dir = format!("{STACKABLE_LOG_DIR}/{main_container}"); - cm_builder.add_data( - LOG_CONFIG_FILE, - create_airflow_config(log_config, &log_dir, resolved_product_image), - ); - } - - let vector_log_config = if let Some(ContainerLogConfig { - choice: Some(ContainerLogConfigChoice::Automatic(log_config)), - }) = logging.containers.get(vector_container) - { - Some(log_config) - } else { - None - }; - - if logging.enable_vector_agent { - cm_builder.add_data( - product_logging::framework::VECTOR_CONFIG_FILE, - product_logging::framework::create_vector_config(rolegroup, vector_log_config), - ); - } -} - -fn create_airflow_config( +pub fn create_airflow_config( log_config: &AutomaticContainerLogConfig, log_dir: &str, resolved_product_image: &ResolvedProductImage, From e46506559b13d172045a4c8be08dfb0245709289 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Mon, 8 Jun 2026 16:44:20 +0200 Subject: [PATCH 21/35] regenerate charts --- extra/crds.yaml | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/extra/crds.yaml b/extra/crds.yaml index 691e3350..0f42aa74 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -485,12 +485,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -1004,12 +1004,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -2002,12 +2002,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -2521,12 +2521,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -3075,12 +3075,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -3573,12 +3573,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -4092,12 +4092,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -4584,12 +4584,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -5103,12 +5103,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -5595,12 +5595,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -6119,12 +6119,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -6686,12 +6686,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -7205,12 +7205,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -8179,12 +8179,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -8698,12 +8698,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -9252,12 +9252,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -9750,12 +9750,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -10269,12 +10269,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -10761,12 +10761,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -11280,12 +11280,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -11772,12 +11772,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: @@ -12296,12 +12296,12 @@ spec: additionalProperties: nullable: true type: string + default: {} description: |- Flat key-value overrides for `*.properties`, Hadoop XML, etc. This is backwards-compatible with the existing flat key-value YAML format used by `HashMap`. - nullable: true type: object type: object envOverrides: From 1a34af94b768ef31e96d1e6f1686482698416d49 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 9 Jun 2026 10:32:32 +0200 Subject: [PATCH 22/35] remove clippy directive and simplify parameters --- .../operator-binary/src/airflow_controller.rs | 40 +++++++++---------- .../src/controller/build/config_map.rs | 1 - 2 files changed, 20 insertions(+), 21 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 3a1e8708..0072451d 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -67,18 +67,19 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - controller::{build::config_map, validate::ValidatedAirflowCluster}, + controller::{ + build::config_map, + validate::{ValidatedAirflowCluster, ValidatedRoleGroupConfig}, + }, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ - self, APP_NAME, AirflowClusterStatus, AirflowConfig, AirflowExecutor, - AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, ExecutorConfig, - HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, - METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, TEMPLATE_LOCATION, - TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, + self, APP_NAME, AirflowClusterStatus, AirflowExecutor, AirflowExecutorCommonConfiguration, + AirflowRole, CONFIG_PATH, Container, ExecutorConfig, HTTP_PORT, HTTP_PORT_NAME, + LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, METRICS_PORT, METRICS_PORT_NAME, + OPERATOR_NAME, STACKABLE_LOG_DIR, TEMPLATE_LOCATION, TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, authentication::{ AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved, }, - authorization::AirflowAuthorizationResolved, build_recommended_labels, internal_secret::{ FERNET_KEY_SECRET_KEY, INTERNAL_SECRET_SECRET_KEY, JWT_SECRET_SECRET_KEY, @@ -556,17 +557,13 @@ pub async fn reconcile_airflow( let rg_statefulset = build_server_rolegroup_statefulset( airflow, - &validated_cluster.image, + &validated_cluster, airflow_role, &rolegroup, - &validated_rg_config.overrides.env_overrides, - &validated_cluster.authentication_config, - &validated_cluster.authorization_config, + validated_rg_config, &metadata_database_connection_details, &celery_database_connection_details, &rbac_sa, - &validated_rg_config.merged_config, - &validated_cluster.executor, &git_sync_resources, )?; @@ -601,7 +598,6 @@ pub async fn reconcile_airflow( Ok(Action::await_change()) } -#[allow(clippy::too_many_arguments)] async fn build_executor_template( airflow: &v1alpha2::AirflowCluster, common_config: &AirflowExecutorCommonConfiguration, @@ -738,22 +734,26 @@ fn listener_ports() -> Vec { #[allow(clippy::too_many_arguments)] fn build_server_rolegroup_statefulset( airflow: &v1alpha2::AirflowCluster, - resolved_product_image: &ResolvedProductImage, + validated_cluster: &ValidatedAirflowCluster, airflow_role: &AirflowRole, rolegroup_ref: &RoleGroupRef, - env_overrides: &HashMap, - authentication_config: &AirflowClientAuthenticationDetailsResolved, - authorization_config: &AirflowAuthorizationResolved, + validated_rg_config: &ValidatedRoleGroupConfig, metadata_database_connection_details: &SqlAlchemyDatabaseConnectionDetails, celery_database_connection_details: &Option<( CeleryDatabaseConnectionDetails, CeleryDatabaseConnectionDetails, )>, service_account: &ServiceAccount, - merged_airflow_config: &AirflowConfig, - executor: &AirflowExecutor, git_sync_resources: &git_sync::v1alpha2::GitSyncResources, ) -> Result { + let merged_airflow_config = &validated_rg_config.merged_config; + let env_overrides = &validated_rg_config.overrides.env_overrides; + + let resolved_product_image = &validated_cluster.image; + let authentication_config = &validated_cluster.authentication_config; + let authorization_config = &validated_cluster.authorization_config; + let executor = &validated_cluster.executor; + let binding = airflow.get_role(airflow_role); let role = binding.as_ref().context(NoAirflowRoleSnafu)?; diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index a68f252e..8948b47a 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -56,7 +56,6 @@ pub enum Error { } /// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator -#[allow(clippy::too_many_arguments)] pub fn build_rolegroup_config_map( airflow: &v1alpha2::AirflowCluster, validated_cluster: &ValidatedAirflowCluster, From a765f81cf9a76d98eb3189169b8bedc2c22d1d2f Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 9 Jun 2026 10:48:20 +0200 Subject: [PATCH 23/35] add function for multiple callouts --- .../operator-binary/src/airflow_controller.rs | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 0072451d..65de5812 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -475,16 +475,7 @@ pub async fn reconcile_airflow( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, &validated_cluster.image, - &validated_rg_config - .overrides - .env_overrides - .iter() - .map(|(k, v)| EnvVar { - name: k.clone(), - value: Some(v.clone()), - ..EnvVar::default() - }) - .collect::>(), + &env_vars_from_overrides(&validated_rg_config.overrides.env_overrides), &airflow.volume_mounts(), LOG_VOLUME_NAME, &validated_rg_config @@ -635,15 +626,7 @@ async fn build_executor_template( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, &validated_cluster.image, - &common_config - .env_overrides - .iter() - .map(|(k, v)| EnvVar { - name: k.clone(), - value: Some(v.clone()), - ..EnvVar::default() - }) - .collect::>(), + &env_vars_from_overrides(&common_config.env_overrides), &airflow.volume_mounts(), LOG_VOLUME_NAME, &merged_executor_config @@ -1292,3 +1275,15 @@ fn add_git_sync_resources( Ok(()) } + +/// Convert user-supplied `envOverrides` into a list of [`EnvVar`]s. +fn env_vars_from_overrides(env_overrides: &HashMap) -> Vec { + env_overrides + .iter() + .map(|(k, v)| EnvVar { + name: k.clone(), + value: Some(v.clone()), + ..EnvVar::default() + }) + .collect() +} From 6512d4da5a55410e71562dc606d160c27b1be065 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 9 Jun 2026 17:29:45 +0200 Subject: [PATCH 24/35] use emrge instead of extends --- rust/operator-binary/src/crd/mod.rs | 36 ++++++++++++++--------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index b9eeed53..04972821 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -104,7 +104,7 @@ pub type AirflowExecutorCommonConfiguration = pub type AirflowWebserverRoleType = Role; -#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] +#[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, Merge, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AirflowConfigOverrides { #[serde(default, rename = "webserver_config.py")] @@ -446,29 +446,27 @@ impl v1alpha2::AirflowCluster { rolegroup_name: &str, ) -> Result { let role_config = role.role_config(self)?; + let rolegroup = role_config.role_groups.get(rolegroup_name); + // env overrides: role first, role-group extended on top (role-group wins) let mut env_overrides = role_config.config.env_overrides.clone(); - // role-level webserver_config.py overrides; role-group overrides are extended on top below - // (role-group wins). Reads the v2 KeyValueConfigOverrides map directly, like hdfs/trino. - let mut file_overrides = role_config - .config - .config_overrides - .webserver_config_py - .overrides - .clone(); - - if let Some(rg) = role_config.role_groups.get(rolegroup_name) { + if let Some(rg) = rolegroup { env_overrides.extend(rg.config.env_overrides.clone()); - file_overrides.extend( - rg.config - .config_overrides - .webserver_config_py - .overrides - .clone(), - ); } - let config_file_overrides = file_overrides + // file overrides: role-group merged over role (role-group wins) via the `Merge` + // impl on AirflowConfigOverrides. A role-group `null` inherits the role value + // rather than unsetting it. Mirrors hdfs-operator. + + let mut config_overrides = rolegroup + .map(|rg| rg.config.config_overrides.clone()) + .unwrap_or_default(); + + config_overrides.merge(&role_config.config.config_overrides); + + let config_file_overrides = config_overrides + .webserver_config_py + .overrides .into_iter() .filter_map(|(k, v)| v.map(|v| (k, v))) .collect(); From 5a6f9dcc3434357339d35c5ff795dfce65002f1d Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Tue, 9 Jun 2026 18:17:13 +0200 Subject: [PATCH 25/35] alter structure to attempt to being it mroe in line with e.g. hdfs. Added test for merge/extend difference --- .../operator-binary/src/airflow_controller.rs | 21 +- .../src/controller/build/config_map.rs | 16 +- .../src/controller/validate.rs | 234 +++++++++++++++++- rust/operator-binary/src/crd/mod.rs | 138 +---------- 4 files changed, 254 insertions(+), 155 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 65de5812..7da37ba2 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -1,6 +1,6 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha2::AirflowCluster`] use std::{ - collections::{BTreeMap, BTreeSet, HashMap}, + collections::{BTreeSet, HashMap}, sync::Arc, }; @@ -73,10 +73,11 @@ use crate::{ }, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ - self, APP_NAME, AirflowClusterStatus, AirflowExecutor, AirflowExecutorCommonConfiguration, - AirflowRole, CONFIG_PATH, Container, ExecutorConfig, HTTP_PORT, HTTP_PORT_NAME, - LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, METRICS_PORT, METRICS_PORT_NAME, - OPERATOR_NAME, STACKABLE_LOG_DIR, TEMPLATE_LOCATION, TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, + self, APP_NAME, AirflowClusterStatus, AirflowConfigOverrides, AirflowExecutor, + AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, ExecutorConfig, + HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, + METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, TEMPLATE_LOCATION, + TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, authentication::{ AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved, }, @@ -475,7 +476,7 @@ pub async fn reconcile_airflow( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, &validated_cluster.image, - &env_vars_from_overrides(&validated_rg_config.overrides.env_overrides), + &env_vars_from_overrides(&validated_rg_config.env_overrides), &airflow.volume_mounts(), LOG_VOLUME_NAME, &validated_rg_config @@ -534,7 +535,7 @@ pub async fn reconcile_airflow( airflow, &validated_cluster, &rolegroup, - &validated_rg_config.overrides.config_file_overrides, + &validated_rg_config.config_overrides, &validated_rg_config.merged_config.logging, &Container::Airflow, ) @@ -611,7 +612,9 @@ async fn build_executor_template( airflow, validated_cluster, &rolegroup, - &BTreeMap::new(), + // The kubernetes-executor pod template does not apply webserver_config.py overrides + // (preserves prior behaviour, which passed an empty map here). + &AirflowConfigOverrides::default(), &merged_executor_config.logging, &Container::Base, ) @@ -730,7 +733,7 @@ fn build_server_rolegroup_statefulset( git_sync_resources: &git_sync::v1alpha2::GitSyncResources, ) -> Result { let merged_airflow_config = &validated_rg_config.merged_config; - let env_overrides = &validated_rg_config.overrides.env_overrides; + let env_overrides = &validated_rg_config.env_overrides; let resolved_product_image = &validated_cluster.image; let authentication_config = &validated_cluster.authentication_config; diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 8948b47a..87d7907b 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -19,7 +19,8 @@ use crate::{ config::webserver_config, controller::validate::ValidatedAirflowCluster, crd::{ - AIRFLOW_CONFIG_FILENAME, Container, STACKABLE_LOG_DIR, build_recommended_labels, v1alpha2, + AIRFLOW_CONFIG_FILENAME, AirflowConfigOverrides, Container, STACKABLE_LOG_DIR, + build_recommended_labels, v1alpha2, }, product_logging::{LOG_CONFIG_FILE, create_airflow_config}, }; @@ -60,15 +61,24 @@ pub fn build_rolegroup_config_map( airflow: &v1alpha2::AirflowCluster, validated_cluster: &ValidatedAirflowCluster, rolegroup: &RoleGroupRef, - config_file_overrides: &BTreeMap, + config_overrides: &AirflowConfigOverrides, logging: &Logging, container: &Container, ) -> Result { + // Flatten the typed `webserver_config.py` overrides into a plain map for the file writer, + // dropping entries whose value is unset (`null`). + let config_file_overrides: BTreeMap = config_overrides + .webserver_config_py + .overrides + .iter() + .filter_map(|(key, value)| value.clone().map(|value| (key.clone(), value))) + .collect(); + let config_file = webserver_config::build( &validated_cluster.authentication_config, &validated_cluster.authorization_config, &validated_cluster.image.product_version, - config_file_overrides, + &config_file_overrides, ) .with_context(|_| BuildWebserverConfigSnafu { rolegroup: rolegroup.clone(), diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index c0ce093f..64924157 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -1,8 +1,9 @@ -use std::collections::BTreeMap; +use std::collections::{BTreeMap, HashMap}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, + config::merge::Merge, role_utils::RoleGroupRef, }; use strum::IntoEnumIterator; @@ -11,7 +12,7 @@ use super::dereference::DereferencedObjects; use crate::{ airflow_controller::CONTAINER_IMAGE_BASE_NAME, crd::{ - AirflowConfig, AirflowExecutor, AirflowRole, MergedOverrides, + AirflowConfig, AirflowConfigOverrides, AirflowExecutor, AirflowRole, AirflowRoleType, authentication::AirflowClientAuthenticationDetailsResolved, authorization::AirflowAuthorizationResolved, v1alpha2, }, @@ -37,10 +38,15 @@ pub struct ValidatedRoleConfig { } /// Per-rolegroup configuration: the merged CRD config plus overrides. +/// +/// `config_overrides` is kept as the typed [`AirflowConfigOverrides`] (role-group merged over +/// role); it is flattened into the rendered config file later, in the build step. This mirrors +/// hdfs-operator. `env_overrides` is already a flat map. #[derive(Clone, Debug)] pub struct ValidatedRoleGroupConfig { pub merged_config: AirflowConfig, - pub overrides: MergedOverrides, + pub config_overrides: AirflowConfigOverrides, + pub env_overrides: HashMap, } /// The validated cluster: proves that config merging succeeded for every role and @@ -104,15 +110,15 @@ pub fn validate_cluster( .merged_config(&role, &rolegroup_ref) .context(FailedToResolveConfigSnafu)?; - let overrides = airflow - .merged_overrides(&role, rolegroup_name) - .context(FailedToResolveConfigSnafu)?; + let (config_overrides, env_overrides) = + merge_role_group_overrides(&resolved_role, rolegroup_name); group_configs.insert( rolegroup_name.clone(), ValidatedRoleGroupConfig { merged_config, - overrides, + config_overrides, + env_overrides, }, ); } @@ -134,3 +140,217 @@ pub fn validate_cluster( authorization_config, }) } + +/// Merge a role group's config overrides over the role-level ones (role-group wins per key) via +/// the `Merge` impl on [`AirflowConfigOverrides`], and combine env overrides (role first, then +/// role-group on top). Mirrors hdfs-operator's `validate_role_group_config`. +/// +/// The merged overrides are returned *typed*; flattening into the rendered `webserver_config.py` +/// happens later, in the build step. Note the `Merge` semantics: a role-group `null` inherits the +/// role-level value rather than unsetting it. +fn merge_role_group_overrides( + role: &AirflowRoleType, + rolegroup_name: &str, +) -> (AirflowConfigOverrides, HashMap) { + let rolegroup = role.role_groups.get(rolegroup_name); + + let mut config_overrides = rolegroup + .map(|rg| rg.config.config_overrides.clone()) + .unwrap_or_default(); + config_overrides.merge(&role.config.config_overrides); + + let mut env_overrides = role.config.env_overrides.clone(); + if let Some(rg) = rolegroup { + env_overrides.extend(rg.config.env_overrides.clone()); + } + + (config_overrides, env_overrides) +} + +#[cfg(test)] +mod tests { + use std::collections::BTreeMap; + + use super::merge_role_group_overrides; + use crate::crd::{AirflowRole, v1alpha2}; + + fn test_cluster() -> v1alpha2::AirflowCluster { + let cluster_yaml = r#" + apiVersion: airflow.stackable.tech/v1alpha2 + kind: AirflowCluster + metadata: + name: airflow + spec: + image: + productVersion: 3.1.6 + clusterConfig: + loadExamples: false + exposeConfig: false + credentialsSecretName: airflow-admin-credentials + metadataDatabase: + postgresql: + host: airflow-postgresql + database: airflow + credentialsSecretName: airflow-postgresql-credentials + webservers: + config: {} + configOverrides: + webserver_config.py: + AUTH_TYPE: "AUTH_OID" + ROLE_ONLY_KEY: "role-value" + envOverrides: + ROLE_ENV_VAR: "role-env-value" + roleGroups: + default: + config: {} + configOverrides: + webserver_config.py: + AUTH_TYPE: "AUTH_DB" + GROUP_ONLY_KEY: "group-value" + envOverrides: + GROUP_ENV_VAR: "group-env-value" + schedulers: + config: {} + roleGroups: + default: + config: {} + kubernetesExecutors: + config: {} + "#; + let deserializer = serde_yaml::Deserializer::from_str(cluster_yaml); + serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap() + } + + #[test] + fn role_group_overrides_merge_over_role_overrides() { + let cluster = test_cluster(); + let role = cluster + .get_role(&AirflowRole::Webserver) + .expect("webserver role"); + + let (config_overrides, env_overrides) = merge_role_group_overrides(&role, "default"); + + // configOverrides are kept typed (values are `Option`). The role-group AUTH_TYPE + // overrides the role-level one; both role-only and group-only keys are kept. + assert_eq!( + config_overrides.webserver_config_py.overrides, + BTreeMap::from([ + ("AUTH_TYPE".to_string(), Some("AUTH_DB".to_string())), + ("ROLE_ONLY_KEY".to_string(), Some("role-value".to_string())), + ( + "GROUP_ONLY_KEY".to_string(), + Some("group-value".to_string()) + ), + ]) + ); + + assert_eq!(env_overrides.len(), 2); + assert_eq!(env_overrides.get("ROLE_ENV_VAR").unwrap(), "role-env-value"); + assert_eq!( + env_overrides.get("GROUP_ENV_VAR").unwrap(), + "group-env-value" + ); + } + + /// A role-group `null` override inherits the role-level value instead of unsetting it — the + /// behavioural consequence of merging with `Merge` rather than `.extend()`. `main`'s + /// product-config used `.extend()`, where the same input would have *removed* the key. This + /// test pins that choice (mirrors the kafka-operator test). + #[test] + fn role_group_null_inherits_role_value_rather_than_unsetting_it() { + let cluster_yaml = r#" + apiVersion: airflow.stackable.tech/v1alpha2 + kind: AirflowCluster + metadata: + name: airflow + spec: + image: + productVersion: 3.1.6 + clusterConfig: + loadExamples: false + exposeConfig: false + credentialsSecretName: airflow-admin-credentials + metadataDatabase: + postgresql: + host: airflow-postgresql + database: airflow + credentialsSecretName: airflow-postgresql-credentials + webservers: + config: {} + configOverrides: + webserver_config.py: + AUTH_TYPE: "AUTH_OID" + roleGroups: + default: + config: {} + configOverrides: + webserver_config.py: + AUTH_TYPE: null + schedulers: + config: {} + roleGroups: + default: + config: {} + kubernetesExecutors: + config: {} + "#; + let deserializer = serde_yaml::Deserializer::from_str(cluster_yaml); + let cluster: v1alpha2::AirflowCluster = + serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); + let role = cluster + .get_role(&AirflowRole::Webserver) + .expect("webserver role"); + + // For contrast: under the old `.extend()` layering the role-group `null` overwrites the + // role value, and the key is dropped on flatten — i.e. AUTH_TYPE is unset entirely. + let old_extend_behaviour: BTreeMap = { + let mut combined = role + .config + .config_overrides + .webserver_config_py + .overrides + .clone(); + let rg = role.role_groups.get("default").expect("default role group"); + combined.extend( + rg.config + .config_overrides + .webserver_config_py + .overrides + .clone(), + ); + combined + .into_iter() + .filter_map(|(key, value)| value.map(|value| (key, value))) + .collect() + }; + assert!( + !old_extend_behaviour.contains_key("AUTH_TYPE"), + "under the old `.extend()` behaviour the role-group `null` unsets AUTH_TYPE" + ); + + // What we do now (Merge): the role-group `null` inherits the role-level value, so + // AUTH_TYPE survives as the role's "AUTH_OID". + let (config_overrides, _env_overrides) = merge_role_group_overrides(&role, "default"); + assert_eq!( + config_overrides + .webserver_config_py + .overrides + .get("AUTH_TYPE"), + Some(&Some("AUTH_OID".to_string())), + "role-group `null` should inherit the role-level AUTH_TYPE under Merge semantics" + ); + } + + #[test] + fn role_without_overrides_yields_empty() { + let cluster = test_cluster(); + let role = cluster + .get_role(&AirflowRole::Scheduler) + .expect("scheduler role"); + + let (config_overrides, env_overrides) = merge_role_group_overrides(&role, "default"); + + assert!(config_overrides.webserver_config_py.overrides.is_empty()); + assert!(env_overrides.is_empty()); + } +} diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 04972821..136790c6 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::collections::BTreeSet; use serde::{Deserialize, Serialize}; use snafu::{OptionExt, ResultExt, Snafu}; @@ -111,12 +111,6 @@ pub struct AirflowConfigOverrides { pub webserver_config_py: KeyValueConfigOverrides, } -#[derive(Clone, Debug)] -pub struct MergedOverrides { - pub env_overrides: HashMap, - pub config_file_overrides: BTreeMap, -} - #[derive(Snafu, Debug)] pub enum Error { #[snafu(display("Unknown Airflow role found {role}. Should be one of {roles:?}"))] @@ -440,43 +434,6 @@ impl v1alpha2::AirflowCluster { format!("{}-executor-pod-template", self.name_any()) } - pub fn merged_overrides( - &self, - role: &AirflowRole, - rolegroup_name: &str, - ) -> Result { - let role_config = role.role_config(self)?; - let rolegroup = role_config.role_groups.get(rolegroup_name); - - // env overrides: role first, role-group extended on top (role-group wins) - let mut env_overrides = role_config.config.env_overrides.clone(); - if let Some(rg) = rolegroup { - env_overrides.extend(rg.config.env_overrides.clone()); - } - - // file overrides: role-group merged over role (role-group wins) via the `Merge` - // impl on AirflowConfigOverrides. A role-group `null` inherits the role value - // rather than unsetting it. Mirrors hdfs-operator. - - let mut config_overrides = rolegroup - .map(|rg| rg.config.config_overrides.clone()) - .unwrap_or_default(); - - config_overrides.merge(&role_config.config.config_overrides); - - let config_file_overrides = config_overrides - .webserver_config_py - .overrides - .into_iter() - .filter_map(|(k, v)| v.map(|v| (k, v))) - .collect(); - - Ok(MergedOverrides { - env_overrides, - config_file_overrides, - }) - } - /// Retrieve and merge resource configs for role and role groups pub fn merged_config( &self, @@ -1112,15 +1069,13 @@ pub fn build_recommended_labels<'a, T>( #[cfg(test)] mod tests { - use std::collections::BTreeMap; - use indoc::formatdoc; use stackable_operator::{ commons::product_image_selection::ResolvedProductImage, versioned::test_utils::RoundtripTestData, }; - use crate::{crd::AirflowRole, v1alpha1, v1alpha2}; + use crate::{v1alpha1, v1alpha2}; #[test] fn test_cluster_config() { @@ -1345,93 +1300,4 @@ mod tests { "# } } - - #[test] - fn merged_overrides_match_config_overrides_from_crd() { - let cluster_yaml = r#" - apiVersion: airflow.stackable.tech/v1alpha2 - kind: AirflowCluster - metadata: - name: airflow - spec: - image: - productVersion: 3.1.6 - clusterConfig: - loadExamples: false - exposeConfig: false - credentialsSecretName: airflow-admin-credentials - metadataDatabase: - postgresql: - host: airflow-postgresql - database: airflow - credentialsSecretName: airflow-postgresql-credentials - webservers: - config: {} - configOverrides: - webserver_config.py: - AUTH_TYPE: "AUTH_OID" - ROLE_ONLY_KEY: "role-value" - envOverrides: - ROLE_ENV_VAR: "role-env-value" - roleGroups: - default: - config: {} - configOverrides: - webserver_config.py: - AUTH_TYPE: "AUTH_DB" - GROUP_ONLY_KEY: "group-value" - envOverrides: - GROUP_ENV_VAR: "group-env-value" - schedulers: - config: {} - roleGroups: - default: - config: {} - kubernetesExecutors: - config: {} - "#; - - let deserializer = serde_yaml::Deserializer::from_str(cluster_yaml); - let cluster: v1alpha2::AirflowCluster = - serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - - // Webservers/default: role-group overrides merge with (and override) role-level ones - let overrides = cluster - .merged_overrides(&AirflowRole::Webserver, "default") - .expect("merged_overrides should succeed"); - - // configOverrides: group AUTH_TYPE overrides role AUTH_TYPE, both unique keys kept - assert_eq!( - overrides.config_file_overrides, - BTreeMap::from([ - ("AUTH_TYPE".into(), "AUTH_DB".into()), - ("ROLE_ONLY_KEY".into(), "role-value".into()), - ("GROUP_ONLY_KEY".into(), "group-value".into()), - ]) - ); - - // envOverrides: both role and group env vars present - assert_eq!(overrides.env_overrides.len(), 2); - assert_eq!( - overrides.env_overrides.get("ROLE_ENV_VAR").unwrap(), - "role-env-value" - ); - assert_eq!( - overrides.env_overrides.get("GROUP_ENV_VAR").unwrap(), - "group-env-value" - ); - - // Schedulers/default: no overrides configured → both maps empty - let overrides = cluster - .merged_overrides(&AirflowRole::Scheduler, "default") - .expect("merged_overrides should succeed"); - assert!( - overrides.config_file_overrides.is_empty(), - "scheduler should have no config file overrides" - ); - assert!( - overrides.env_overrides.is_empty(), - "scheduler should have no env overrides" - ); - } } From 6a22f47cb5d4ad5ebbccb4f715bcf64d11eb6eb8 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 10 Jun 2026 10:49:53 +0200 Subject: [PATCH 26/35] move to v2::role_utils --- .../src/controller/validate.rs | 128 ++++++++++-------- rust/operator-binary/src/crd/mod.rs | 18 ++- 2 files changed, 82 insertions(+), 64 deletions(-) diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 64924157..9b270004 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -3,8 +3,10 @@ use std::collections::{BTreeMap, HashMap}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, - config::merge::Merge, - role_utils::RoleGroupRef, + config::fragment, + kube::ResourceExt, + role_utils::{GenericRoleConfig, RoleGroup}, + v2::role_utils::{GenericCommonConfig, with_validated_config}, }; use strum::IntoEnumIterator; @@ -12,8 +14,8 @@ use super::dereference::DereferencedObjects; use crate::{ airflow_controller::CONTAINER_IMAGE_BASE_NAME, crd::{ - AirflowConfig, AirflowConfigOverrides, AirflowExecutor, AirflowRole, AirflowRoleType, - authentication::AirflowClientAuthenticationDetailsResolved, + AirflowConfig, AirflowConfigFragment, AirflowConfigOverrides, AirflowExecutor, AirflowRole, + AirflowRoleType, authentication::AirflowClientAuthenticationDetailsResolved, authorization::AirflowAuthorizationResolved, v1alpha2, }, }; @@ -25,8 +27,11 @@ pub enum Error { source: product_image_selection::Error, }, - #[snafu(display("failed to resolve and merge config for role and role group"))] - FailedToResolveConfig { source: crate::crd::Error }, + #[snafu(display("failed to resolve and merge config for role group {role_group}"))] + FailedToResolveConfig { + source: fragment::ValidationError, + role_group: String, + }, } /// Per-role configuration extracted during validation. @@ -98,29 +103,16 @@ pub fn validate_cluster( }, ); + let default_config = AirflowConfig::default_config(&airflow.name_any(), &role); + let mut group_configs = BTreeMap::new(); - for rolegroup_name in resolved_role.role_groups.keys() { - let rolegroup_ref = RoleGroupRef { - cluster: stackable_operator::kube::runtime::reflector::ObjectRef::from_obj(airflow), - role: role.to_string(), - role_group: rolegroup_name.into(), - }; - - let merged_config = airflow - .merged_config(&role, &rolegroup_ref) - .context(FailedToResolveConfigSnafu)?; - - let (config_overrides, env_overrides) = - merge_role_group_overrides(&resolved_role, rolegroup_name); - - group_configs.insert( - rolegroup_name.clone(), - ValidatedRoleGroupConfig { - merged_config, - config_overrides, - env_overrides, - }, - ); + for (rolegroup_name, rolegroup) in &resolved_role.role_groups { + let validated = validate_role_group(&resolved_role, rolegroup, &default_config) + .with_context(|_| FailedToResolveConfigSnafu { + role_group: rolegroup_name.clone(), + })?; + + group_configs.insert(rolegroup_name.clone(), validated); } role_groups.insert(role, group_configs); @@ -141,38 +133,42 @@ pub fn validate_cluster( }) } -/// Merge a role group's config overrides over the role-level ones (role-group wins per key) via -/// the `Merge` impl on [`AirflowConfigOverrides`], and combine env overrides (role first, then -/// role-group on top). Mirrors hdfs-operator's `validate_role_group_config`. +/// Validate and merge one role group against its role, via the shared +/// [`with_validated_config`] from `operator-rs`. /// -/// The merged overrides are returned *typed*; flattening into the rendered `webserver_config.py` -/// happens later, in the build step. Note the `Merge` semantics: a role-group `null` inherits the -/// role-level value rather than unsetting it. -fn merge_role_group_overrides( +/// This performs the full `default → role → role-group` merge of the config fragment (then +/// validates it) *and* the role←role-group merge of the overrides in one step. The config +/// overrides are kept *typed* ([`AirflowConfigOverrides`]); flattening into the rendered +/// `webserver_config.py` happens later, in the build step. +/// +/// Note the override `Merge` semantics: a role-group `null` inherits the role-level value rather +/// than unsetting it (config overrides), and env overrides layer role-group on top of role. +fn validate_role_group( role: &AirflowRoleType, - rolegroup_name: &str, -) -> (AirflowConfigOverrides, HashMap) { - let rolegroup = role.role_groups.get(rolegroup_name); - - let mut config_overrides = rolegroup - .map(|rg| rg.config.config_overrides.clone()) - .unwrap_or_default(); - config_overrides.merge(&role.config.config_overrides); - - let mut env_overrides = role.config.env_overrides.clone(); - if let Some(rg) = rolegroup { - env_overrides.extend(rg.config.env_overrides.clone()); - } - - (config_overrides, env_overrides) + rolegroup: &RoleGroup, + default_config: &AirflowConfigFragment, +) -> Result { + let validated = with_validated_config::< + AirflowConfig, + GenericCommonConfig, + AirflowConfigFragment, + GenericRoleConfig, + AirflowConfigOverrides, + >(rolegroup, role, default_config)?; + + Ok(ValidatedRoleGroupConfig { + merged_config: validated.config.config, + config_overrides: validated.config.config_overrides, + env_overrides: validated.config.env_overrides, + }) } #[cfg(test)] mod tests { use std::collections::BTreeMap; - use super::merge_role_group_overrides; - use crate::crd::{AirflowRole, v1alpha2}; + use super::validate_role_group; + use crate::crd::{AirflowConfig, AirflowRole, v1alpha2}; fn test_cluster() -> v1alpha2::AirflowCluster { let cluster_yaml = r#" @@ -227,8 +223,13 @@ mod tests { let role = cluster .get_role(&AirflowRole::Webserver) .expect("webserver role"); + let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver); + let rolegroup = role.role_groups.get("default").expect("default role group"); - let (config_overrides, env_overrides) = merge_role_group_overrides(&role, "default"); + let validated = + validate_role_group(&role, rolegroup, &default_config).expect("validated role group"); + let config_overrides = validated.config_overrides; + let env_overrides = validated.env_overrides; // configOverrides are kept typed (values are `Option`). The role-group AUTH_TYPE // overrides the role-level one; both role-only and group-only keys are kept. @@ -330,7 +331,11 @@ mod tests { // What we do now (Merge): the role-group `null` inherits the role-level value, so // AUTH_TYPE survives as the role's "AUTH_OID". - let (config_overrides, _env_overrides) = merge_role_group_overrides(&role, "default"); + let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver); + let rolegroup = role.role_groups.get("default").expect("default role group"); + let config_overrides = validate_role_group(&role, rolegroup, &default_config) + .expect("validated role group") + .config_overrides; assert_eq!( config_overrides .webserver_config_py @@ -347,10 +352,19 @@ mod tests { let role = cluster .get_role(&AirflowRole::Scheduler) .expect("scheduler role"); + let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Scheduler); + let rolegroup = role.role_groups.get("default").expect("default role group"); - let (config_overrides, env_overrides) = merge_role_group_overrides(&role, "default"); + let validated = + validate_role_group(&role, rolegroup, &default_config).expect("validated role group"); - assert!(config_overrides.webserver_config_py.overrides.is_empty()); - assert!(env_overrides.is_empty()); + assert!( + validated + .config_overrides + .webserver_config_py + .overrides + .is_empty() + ); + assert!(validated.env_overrides.is_empty()); } } diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 136790c6..8f77cbb1 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -32,9 +32,7 @@ use stackable_operator::{ framework::{create_vector_shutdown_file_command, remove_vector_shutdown_file_command}, spec::Logging, }, - role_utils::{ - CommonConfiguration, GenericCommonConfig, GenericRoleConfig, Role, RoleGroup, RoleGroupRef, - }, + role_utils::{CommonConfiguration, GenericRoleConfig, Role, RoleGroup, RoleGroupRef}, schemars::{self, JsonSchema}, shared::time::Duration, status::condition::{ClusterCondition, HasStatusCondition}, @@ -42,6 +40,7 @@ use stackable_operator::{ v2::{ config_overrides::KeyValueConfigOverrides, flask_config_writer::{FlaskAppConfigOptions, PythonType}, + role_utils::GenericCommonConfig, }, versioned::versioned, }; @@ -96,13 +95,18 @@ pub const MAX_LOG_FILES_SIZE: MemoryQuantity = MemoryQuantity { unit: BinaryMultiple::Mebi, }; -pub type AirflowRoleType = Role; +pub type AirflowRoleType = + Role; pub type AirflowExecutorCommonConfiguration = CommonConfiguration; -pub type AirflowWebserverRoleType = - Role; +pub type AirflowWebserverRoleType = Role< + AirflowConfigFragment, + AirflowConfigOverrides, + v1alpha2::WebserverRoleConfig, + GenericCommonConfig, +>; #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, Merge, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] @@ -971,7 +975,7 @@ pub struct AirflowConfig { impl AirflowConfig { pub const GIT_CREDENTIALS_SECRET_PROPERTY: &'static str = "gitCredentialsSecret"; - fn default_config(cluster_name: &str, role: &AirflowRole) -> AirflowConfigFragment { + pub(crate) fn default_config(cluster_name: &str, role: &AirflowRole) -> AirflowConfigFragment { AirflowConfigFragment { resources: default_resources(role), logging: product_logging::spec::default_logging(), From 85fdfaeb2fb0408ffa610287e25bb383c17f2038 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 10 Jun 2026 10:52:44 +0200 Subject: [PATCH 27/35] add replicas and pod_overrides to ValidatedRoleGroupConfig --- .../operator-binary/src/airflow_controller.rs | 23 ++---- .../src/controller/validate.rs | 79 +++++++++++++++++++ 2 files changed, 87 insertions(+), 15 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 7da37ba2..06214911 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -5,7 +5,7 @@ use std::{ }; use const_format::concatcp; -use snafu::{OptionExt, ResultExt, Snafu}; +use snafu::{ResultExt, Snafu}; use stackable_operator::{ builder::{ self, @@ -113,9 +113,6 @@ pub struct Ctx { #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(IntoStaticStr))] pub enum Error { - #[snafu(display("object defines no airflow config role"))] - NoAirflowRole, - #[snafu(display("failed to apply Service for {rolegroup}"))] ApplyRoleGroupService { source: stackable_operator::cluster_resources::Error, @@ -740,11 +737,6 @@ fn build_server_rolegroup_statefulset( let authorization_config = &validated_cluster.authorization_config; let executor = &validated_cluster.executor; - let binding = airflow.get_role(airflow_role); - let role = binding.as_ref().context(NoAirflowRoleSnafu)?; - - let rolegroup = role.role_groups.get(&rolegroup_ref.role_group); - let mut pb = PodBuilder::new(); let recommended_object_labels = build_recommended_labels( airflow, @@ -868,7 +860,11 @@ fn build_server_rolegroup_statefulset( let mut pvcs: Option> = None; - if let Some(listener_group_name) = airflow.group_listener_name(airflow_role) { + if let Some(listener_group_name) = validated_cluster + .role_configs + .get(airflow_role) + .and_then(|role_config| role_config.group_listener_name.clone()) + { // Listener endpoints for the Webserver role will use persistent volumes // so that load balancers can hard-code the target addresses. This will // be the case even when no class is set (and the value defaults to @@ -981,10 +977,7 @@ fn build_server_rolegroup_statefulset( } } let mut pod_template = pb.build_template(); - pod_template.merge_from(role.config.pod_overrides.clone()); - if let Some(rolegroup) = rolegroup { - pod_template.merge_from(rolegroup.config.pod_overrides.clone()); - } + pod_template.merge_from(validated_rg_config.pod_overrides.clone()); let restarter_label = Label::try_from(("restarter.stackable.tech/enabled", "true")).context(BuildLabelSnafu)?; @@ -1018,7 +1011,7 @@ fn build_server_rolegroup_statefulset( } .to_string(), ), - replicas: rolegroup.and_then(|rg| rg.replicas).map(i32::from), + replicas: validated_rg_config.replicas.map(i32::from), selector: LabelSelector { match_labels: Some(statefulset_match_labels.into()), ..LabelSelector::default() diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 9b270004..9423e32c 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -4,6 +4,7 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::{ commons::product_image_selection::{self, ResolvedProductImage}, config::fragment, + k8s_openapi::api::core::v1::PodTemplateSpec, kube::ResourceExt, role_utils::{GenericRoleConfig, RoleGroup}, v2::role_utils::{GenericCommonConfig, with_validated_config}, @@ -52,6 +53,8 @@ pub struct ValidatedRoleGroupConfig { pub merged_config: AirflowConfig, pub config_overrides: AirflowConfigOverrides, pub env_overrides: HashMap, + pub replicas: Option, + pub pod_overrides: PodTemplateSpec, } /// The validated cluster: proves that config merging succeeded for every role and @@ -160,6 +163,8 @@ fn validate_role_group( merged_config: validated.config.config, config_overrides: validated.config.config_overrides, env_overrides: validated.config.env_overrides, + replicas: validated.replicas, + pod_overrides: validated.config.pod_overrides, }) } @@ -367,4 +372,78 @@ mod tests { ); assert!(validated.env_overrides.is_empty()); } + + /// `replicas` and the role←role-group merged `pod_overrides` are produced by + /// `with_validated_config` and must be carried on `ValidatedRoleGroupConfig`, so the build + /// step reads them from here rather than re-deriving from the raw cluster. + #[test] + fn role_group_carries_merged_pod_overrides_and_replicas() { + let cluster_yaml = r#" + apiVersion: airflow.stackable.tech/v1alpha2 + kind: AirflowCluster + metadata: + name: airflow + spec: + image: + productVersion: 3.1.6 + clusterConfig: + loadExamples: false + exposeConfig: false + credentialsSecretName: airflow-admin-credentials + metadataDatabase: + postgresql: + host: airflow-postgresql + database: airflow + credentialsSecretName: airflow-postgresql-credentials + webservers: + config: {} + podOverrides: + metadata: + labels: + role-label: role + shared: role + roleGroups: + default: + replicas: 3 + config: {} + podOverrides: + metadata: + labels: + rg-label: rg + shared: rg + schedulers: + config: {} + roleGroups: + default: + config: {} + kubernetesExecutors: + config: {} + "#; + let deserializer = serde_yaml::Deserializer::from_str(cluster_yaml); + let cluster: v1alpha2::AirflowCluster = + serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); + let role = cluster + .get_role(&AirflowRole::Webserver) + .expect("webserver role"); + let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver); + let rolegroup = role.role_groups.get("default").expect("default role group"); + + let validated = + validate_role_group(&role, rolegroup, &default_config).expect("validated role group"); + + // replicas is carried through from the role group. + assert_eq!(validated.replicas, Some(3)); + + // pod_overrides is merged role←role-group (role-group wins on shared keys, both levels' + // unique keys survive). + let labels = validated + .pod_overrides + .metadata + .expect("pod override metadata") + .labels + .expect("pod override labels"); + assert_eq!(labels.get("role-label"), Some(&"role".to_string())); + assert_eq!(labels.get("rg-label"), Some(&"rg".to_string())); + assert_eq!(labels.get("shared"), Some(&"rg".to_string())); + } } From 598be32ee3ad735da9b49df005b955eb44a826eb Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 10 Jun 2026 11:04:48 +0200 Subject: [PATCH 28/35] move remaining validated structs to controller --- .../operator-binary/src/airflow_controller.rs | 53 +++++++++++++++---- .../src/controller/build/config_map.rs | 3 +- .../src/controller/validate.rs | 50 +++-------------- 3 files changed, 52 insertions(+), 54 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 06214911..310aeb42 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -1,6 +1,6 @@ //! Ensures that `Pod`s are configured and running for each [`v1alpha2::AirflowCluster`] use std::{ - collections::{BTreeSet, HashMap}, + collections::{BTreeMap, BTreeSet, HashMap}, sync::Arc, }; @@ -67,20 +67,18 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - controller::{ - build::config_map, - validate::{ValidatedAirflowCluster, ValidatedRoleGroupConfig}, - }, + controller::build::config_map, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ - self, APP_NAME, AirflowClusterStatus, AirflowConfigOverrides, AirflowExecutor, - AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, ExecutorConfig, - HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, - METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, TEMPLATE_LOCATION, - TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, + self, APP_NAME, AirflowClusterStatus, AirflowConfig, AirflowConfigOverrides, + AirflowExecutor, AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, + ExecutorConfig, HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, + LOG_CONFIG_DIR, METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, + TEMPLATE_LOCATION, TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, authentication::{ AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved, }, + authorization::AirflowAuthorizationResolved, build_recommended_labels, internal_secret::{ FERNET_KEY_SECRET_KEY, INTERNAL_SECRET_SECRET_KEY, JWT_SECRET_SECRET_KEY, @@ -110,6 +108,41 @@ pub struct Ctx { pub operator_environment: OperatorEnvironmentOptions, } +/// Per-role configuration extracted during validation. +#[derive(Clone, Debug)] +pub struct ValidatedRoleConfig { + pub pdb: Option, + pub listener_class: Option, + pub group_listener_name: Option, +} + +/// Per-rolegroup configuration: the merged CRD config plus overrides. +/// +/// `config_overrides` is kept as the typed [`AirflowConfigOverrides`] (role-group merged over +/// role); it is flattened into the rendered config file later, in the build step. This mirrors +/// hdfs-operator. `env_overrides` is already a flat map. +#[derive(Clone, Debug)] +pub struct ValidatedRoleGroupConfig { + pub merged_config: AirflowConfig, + pub config_overrides: AirflowConfigOverrides, + pub env_overrides: HashMap, + pub replicas: Option, + pub pod_overrides: PodTemplateSpec, +} + +/// The validated cluster: proves that config merging succeeded for every role and +/// role group before any resources are created. It also carries the dereferenced +/// external references, so every downstream build step reads them from here. +#[derive(Clone, Debug)] +pub struct ValidatedAirflowCluster { + pub image: ResolvedProductImage, + pub role_groups: BTreeMap>, + pub role_configs: BTreeMap, + pub executor: AirflowExecutor, + pub authentication_config: AirflowClientAuthenticationDetailsResolved, + pub authorization_config: AirflowAuthorizationResolved, +} + #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(IntoStaticStr))] pub enum Error { diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 87d7907b..1fdb415e 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -15,9 +15,8 @@ use stackable_operator::{ }; use crate::{ - airflow_controller::AIRFLOW_CONTROLLER_NAME, + airflow_controller::{AIRFLOW_CONTROLLER_NAME, ValidatedAirflowCluster}, config::webserver_config, - controller::validate::ValidatedAirflowCluster, crd::{ AIRFLOW_CONFIG_FILENAME, AirflowConfigOverrides, Container, STACKABLE_LOG_DIR, build_recommended_labels, v1alpha2, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 9423e32c..a16ef09f 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -1,10 +1,9 @@ -use std::collections::{BTreeMap, HashMap}; +use std::collections::BTreeMap; use snafu::{ResultExt, Snafu}; use stackable_operator::{ - commons::product_image_selection::{self, ResolvedProductImage}, + commons::product_image_selection, config::fragment, - k8s_openapi::api::core::v1::PodTemplateSpec, kube::ResourceExt, role_utils::{GenericRoleConfig, RoleGroup}, v2::role_utils::{GenericCommonConfig, with_validated_config}, @@ -13,11 +12,13 @@ use strum::IntoEnumIterator; use super::dereference::DereferencedObjects; use crate::{ - airflow_controller::CONTAINER_IMAGE_BASE_NAME, + airflow_controller::{ + CONTAINER_IMAGE_BASE_NAME, ValidatedAirflowCluster, ValidatedRoleConfig, + ValidatedRoleGroupConfig, + }, crd::{ - AirflowConfig, AirflowConfigFragment, AirflowConfigOverrides, AirflowExecutor, AirflowRole, - AirflowRoleType, authentication::AirflowClientAuthenticationDetailsResolved, - authorization::AirflowAuthorizationResolved, v1alpha2, + AirflowConfig, AirflowConfigFragment, AirflowConfigOverrides, AirflowRole, AirflowRoleType, + v1alpha2, }, }; @@ -35,41 +36,6 @@ pub enum Error { }, } -/// Per-role configuration extracted during validation. -#[derive(Clone, Debug)] -pub struct ValidatedRoleConfig { - pub pdb: Option, - pub listener_class: Option, - pub group_listener_name: Option, -} - -/// Per-rolegroup configuration: the merged CRD config plus overrides. -/// -/// `config_overrides` is kept as the typed [`AirflowConfigOverrides`] (role-group merged over -/// role); it is flattened into the rendered config file later, in the build step. This mirrors -/// hdfs-operator. `env_overrides` is already a flat map. -#[derive(Clone, Debug)] -pub struct ValidatedRoleGroupConfig { - pub merged_config: AirflowConfig, - pub config_overrides: AirflowConfigOverrides, - pub env_overrides: HashMap, - pub replicas: Option, - pub pod_overrides: PodTemplateSpec, -} - -/// The validated cluster: proves that config merging succeeded for every role and -/// role group before any resources are created. It also carries the dereferenced -/// external references, so every downstream build step reads them from here. -#[derive(Clone, Debug)] -pub struct ValidatedAirflowCluster { - pub image: ResolvedProductImage, - pub role_groups: BTreeMap>, - pub role_configs: BTreeMap, - pub executor: AirflowExecutor, - pub authentication_config: AirflowClientAuthenticationDetailsResolved, - pub authorization_config: AirflowAuthorizationResolved, -} - pub fn validate_cluster( airflow: &v1alpha2::AirflowCluster, image_repository: &str, From e8de64651bb73cfeb29935b7b64381cc9b068dd3 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 10 Jun 2026 11:30:47 +0200 Subject: [PATCH 29/35] env_vars_from_overrides to sort deterministically --- .../operator-binary/src/airflow_controller.rs | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 310aeb42..5caad8b5 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -1307,8 +1307,13 @@ fn add_git_sync_resources( /// Convert user-supplied `envOverrides` into a list of [`EnvVar`]s. fn env_vars_from_overrides(env_overrides: &HashMap) -> Vec { + // Collect into a `BTreeMap` first so the env vars come out in a deterministic (sorted) order; + // `HashMap` iteration order is randomised per instance and would otherwise churn the containers + // this feeds between reconciles. Mirrors the override handling in `env_vars.rs`. env_overrides .iter() + .collect::>() + .into_iter() .map(|(k, v)| EnvVar { name: k.clone(), value: Some(v.clone()), @@ -1316,3 +1321,31 @@ fn env_vars_from_overrides(env_overrides: &HashMap) -> Vec = env_vars_from_overrides(&overrides) + .into_iter() + .map(|env_var| env_var.name) + .collect(); + + assert_eq!(names, ["ALPHA", "BRAVO", "CHARLIE", "DELTA", "ECHO"]); + } +} From d430d35f19266634eaf34c72bf480de23a8e077b Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 10 Jun 2026 11:34:30 +0200 Subject: [PATCH 30/35] regenerate nix --- Cargo.nix | 18 +++++++++--------- crate-hashes.json | 18 +++++++++--------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Cargo.nix b/Cargo.nix index c7f04c42..1d42f2ed 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -4877,7 +4877,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; libName = "k8s_version"; authors = [ @@ -9657,7 +9657,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; libName = "stackable_certs"; authors = [ @@ -9760,7 +9760,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; libName = "stackable_operator"; authors = [ @@ -9954,7 +9954,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9989,7 +9989,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; libName = "stackable_shared"; authors = [ @@ -10070,7 +10070,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; libName = "stackable_telemetry"; authors = [ @@ -10180,7 +10180,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; libName = "stackable_versioned"; authors = [ @@ -10230,7 +10230,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10298,7 +10298,7 @@ rec { src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc"; + sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; }; libName = "stackable_webhook"; authors = [ diff --git a/crate-hashes.json b/crate-hashes.json index 014d9478..deac3bf4 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,12 @@ { - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "14q10sppdjdf3vbcbxz12rlgm1g9l6p87nk9wr707w2a71z8vgxc", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file From fc6a42f7b7cb1cc6f3521f6c930e738174870515 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 10 Jun 2026 12:49:13 +0200 Subject: [PATCH 31/35] bump operator-rs to use updated KeyValueConfigOverrides --- Cargo.lock | 274 ++++++------ Cargo.nix | 416 ++++++++---------- crate-hashes.json | 18 +- .../src/controller/build/config_map.rs | 11 +- .../src/controller/validate.rs | 73 +-- 5 files changed, 337 insertions(+), 455 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0b263faf..5e6cd2cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -163,9 +163,9 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.5.0" +version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" [[package]] name = "axum" @@ -265,9 +265,9 @@ checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" -version = "2.11.1" +version = "2.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" [[package]] name = "block-buffer" @@ -280,9 +280,9 @@ dependencies = [ [[package]] name = "built" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4ad8f11f288f48ca24471bbd51ac257aaeaaa07adae295591266b792902ae64" +checksum = "5c0e531d93d39c34eef561e929e8a7f86d77a5af08aac4f6d6e39976c51858e9" dependencies = [ "chrono", "git2", @@ -290,9 +290,9 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.20.2" +version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" [[package]] name = "bytes" @@ -302,9 +302,9 @@ checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "cc" -version = "1.2.60" +version = "1.2.63" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43c5703da9466b66a946814e1adf53ea2c90f10063b86290cc9eb67ce3478a20" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" dependencies = [ "find-msvc-tools", "jobserver", @@ -320,9 +320,9 @@ checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chrono" -version = "0.4.44" +version = "0.4.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" dependencies = [ "iana-time-zone", "num-traits", @@ -613,9 +613,9 @@ dependencies = [ [[package]] name = "displaydoc" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" dependencies = [ "proc-macro2", "quote", @@ -676,9 +676,9 @@ dependencies = [ [[package]] name = "either" -version = "1.15.0" +version = "1.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" [[package]] name = "elliptic-curve" @@ -924,9 +924,9 @@ checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-timer" -version = "3.0.3" +version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" [[package]] name = "futures-util" @@ -983,15 +983,14 @@ dependencies = [ [[package]] name = "git2" -version = "0.20.4" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b88256088d75a56f8ecfa070513a775dd9107f6530ef14919dac831af9cfe2b" +checksum = "ddddbf932745a6be37109b6112d3ee09696106f848449069d3a57bba937ab82e" dependencies = [ "bitflags", "libc", "libgit2-sys", "log", - "url", ] [[package]] @@ -1025,9 +1024,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" +checksum = "171fefbc92fe4a4de27e0698d6a5b392d6a0e333506bc49133760b3bcf948733" dependencies = [ "atomic-waker", "bytes", @@ -1055,9 +1054,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.17.0" +version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" [[package]] name = "heck" @@ -1087,9 +1086,9 @@ dependencies = [ [[package]] name = "http" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +checksum = "6970f50e31d6fc17d3fa27329444bfa74e196cf62e95052a3f6fee181dba6425" dependencies = [ "bytes", "itoa", @@ -1138,9 +1137,9 @@ checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "hyper" -version = "1.9.0" +version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" dependencies = [ "atomic-waker", "bytes", @@ -1336,9 +1335,9 @@ dependencies = [ [[package]] name = "idna_adapter" -version = "1.2.1" +version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" dependencies = [ "icu_normalizer", "icu_properties", @@ -1351,7 +1350,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" dependencies = [ "equivalent", - "hashbrown 0.17.0", + "hashbrown 0.17.1", ] [[package]] @@ -1369,16 +1368,6 @@ version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" -[[package]] -name = "iri-string" -version = "0.7.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" -dependencies = [ - "memchr", - "serde", -] - [[package]] name = "is_terminal_polyfill" version = "1.70.2" @@ -1413,9 +1402,9 @@ dependencies = [ [[package]] name = "jiff" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a3546dc96b6d42c5f24902af9e2538e82e39ad350b0c766eb3fbf2d8f3d8359" +checksum = "4603d3033e49e2b0e31229fcab20a5d40089c607d975cd9c80551dc69eed9102" dependencies = [ "jiff-static", "jiff-tzdb-platform", @@ -1423,14 +1412,14 @@ dependencies = [ "portable-atomic", "portable-atomic-util", "serde_core", - "windows-sys 0.61.2", + "windows-link", ] [[package]] name = "jiff-static" -version = "0.2.23" +version = "0.2.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a8c8b344124222efd714b73bb41f8b5120b27a7cc1c75593a6ff768d9d05aa4" +checksum = "782d32378dddf207193ac91cefb848ad41abb58195c95168e1291227a0832b47" dependencies = [ "proc-macro2", "quote", @@ -1464,27 +1453,26 @@ dependencies = [ [[package]] name = "js-sys" -version = "0.3.95" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2964e92d1d9dc3364cae4d718d93f227e3abb088e747d92e0395bfdedf1c12ca" +checksum = "f2025f20d7a4fa7785846e7b63d10a76d3f1cee98ee5cb79ea59703f95e42162" dependencies = [ "cfg-if", "futures-util", - "once_cell", "wasm-bindgen", ] [[package]] name = "json-patch" -version = "4.1.0" +version = "4.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f300e415e2134745ef75f04562dd0145405c2f7fd92065db029ac4b16b57fe90" +checksum = "7421438de105a0827e44fadd05377727847d717c80ce29a229f85fd04c427b72" dependencies = [ "jsonptr", "schemars", "serde", "serde_json", - "thiserror 1.0.69", + "thiserror 2.0.18", ] [[package]] @@ -1526,7 +1514,7 @@ dependencies = [ [[package]] name = "k8s-version" version = "0.1.3" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "darling", "regex", @@ -1667,15 +1655,15 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.185" +version = "0.2.186" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ff2c0fe9bc6cb6b14a0592c2ff4fa9ceb83eea9db979b0487cd054946a2b8f" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" [[package]] name = "libgit2-sys" -version = "0.18.3+1.9.2" +version = "0.18.5+1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9b3acc4b91781bb0b3386669d325163746af5f6e4f73e6d2d630e09a35f3487" +checksum = "005d6ae6eac1912906073e069f7db60b1fa98e052a68227824afe3e3a1c59ca2" dependencies = [ "cc", "libc", @@ -1691,9 +1679,9 @@ checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libz-sys" -version = "1.1.28" +version = "1.1.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc3a226e576f50782b3305c5ccf458698f92798987f551c6a02efe8276721e22" +checksum = "85bc9657773828b90eeb625adff10eeac83cc21bbfd8e23a03eaa8a33c9e28d9" dependencies = [ "cc", "libc", @@ -1718,9 +1706,9 @@ dependencies = [ [[package]] name = "log" -version = "0.4.29" +version = "0.4.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" [[package]] name = "matchers" @@ -1739,9 +1727,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" [[package]] name = "memchr" -version = "2.8.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" [[package]] name = "mime" @@ -1761,9 +1749,9 @@ dependencies = [ [[package]] name = "mio" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" dependencies = [ "libc", "wasi", @@ -1797,9 +1785,9 @@ dependencies = [ [[package]] name = "num-conv" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" [[package]] name = "num-integer" @@ -2064,18 +2052,18 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.11" +version = "1.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" dependencies = [ "proc-macro2", "quote", @@ -2199,9 +2187,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ "bytes", "prost-derive", @@ -2209,9 +2197,9 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ "anyhow", "itertools", @@ -2222,9 +2210,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.14.3" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" +checksum = "f94967dc7688f3054c7fac87473ffae4cc4c3904800e2d9f5b857246d8963b0a" dependencies = [ "prost", ] @@ -2333,9 +2321,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.3" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2356,9 +2344,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.10" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "relative-path" @@ -2482,9 +2470,9 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.38" +version = "0.23.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f9466fb2c14ea04357e91413efb882e2a6d4a406e625449bc0a5d360d53a21" +checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" dependencies = [ "log", "once_cell", @@ -2497,9 +2485,9 @@ dependencies = [ [[package]] name = "rustls-native-certs" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +checksum = "dab5152771c58876a2146916e53e35057e1a4dfa2b9df0f0305b07f611fdea4d" dependencies = [ "openssl-probe", "rustls-pki-types", @@ -2509,9 +2497,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.14.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" dependencies = [ "zeroize", ] @@ -2685,9 +2673,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.149" +version = "1.0.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" dependencies = [ "itoa", "memchr", @@ -2765,9 +2753,9 @@ dependencies = [ [[package]] name = "shlex" -version = "1.3.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" [[package]] name = "signal-hook-registry" @@ -2872,9 +2860,9 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.3" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" dependencies = [ "libc", "windows-sys 0.61.2", @@ -2927,7 +2915,7 @@ dependencies = [ [[package]] name = "stackable-certs" version = "0.4.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "const-oid", "ecdsa", @@ -2951,7 +2939,7 @@ dependencies = [ [[package]] name = "stackable-operator" version = "0.111.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "base64", "clap", @@ -2995,7 +2983,7 @@ dependencies = [ [[package]] name = "stackable-operator-derive" version = "0.3.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "darling", "proc-macro2", @@ -3006,7 +2994,7 @@ dependencies = [ [[package]] name = "stackable-shared" version = "0.1.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "jiff", "k8s-openapi", @@ -3023,7 +3011,7 @@ dependencies = [ [[package]] name = "stackable-telemetry" version = "0.6.4" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "axum", "clap", @@ -3047,7 +3035,7 @@ dependencies = [ [[package]] name = "stackable-versioned" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "kube", "schemars", @@ -3061,7 +3049,7 @@ dependencies = [ [[package]] name = "stackable-versioned-macros" version = "0.10.0" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "convert_case", "convert_case_extras", @@ -3079,7 +3067,7 @@ dependencies = [ [[package]] name = "stackable-webhook" version = "0.9.1" -source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#46cd3f93a788d44d177a8794fde91fbefa3156d7" +source = "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#451088f77acee6c3d296754698260256c250ecb2" dependencies = [ "arc-swap", "async-trait", @@ -3302,9 +3290,9 @@ dependencies = [ [[package]] name = "tokio" -version = "1.52.1" +version = "1.52.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67dee974fe86fd92cc45b7a95fdd2f99a36a6d7b0d431a231178d3d670bbcc6" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" dependencies = [ "bytes", "libc", @@ -3374,9 +3362,9 @@ dependencies = [ [[package]] name = "toml_edit" -version = "0.25.11+spec-1.1.0" +version = "0.25.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +checksum = "d2153edc6955a6c354fad8f5efd38b6a8769bdccf9fe50f8e1329f81b0baa5d7" dependencies = [ "indexmap", "toml_datetime", @@ -3395,9 +3383,9 @@ dependencies = [ [[package]] name = "tonic" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" dependencies = [ "async-trait", "base64", @@ -3422,9 +3410,9 @@ dependencies = [ [[package]] name = "tonic-prost" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" dependencies = [ "bytes", "prost", @@ -3433,9 +3421,9 @@ dependencies = [ [[package]] name = "tonic-types" -version = "0.14.5" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a875a902255423d34c1f20838ab374126db8eb41625b7947a1d54113b0b7399" +checksum = "73ab1b02061f83d519bba3caa167f88f261ef05720ab8ebc954ade70de3348e8" dependencies = [ "prost", "prost-types", @@ -3463,9 +3451,9 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" dependencies = [ "base64", "bitflags", @@ -3473,13 +3461,13 @@ dependencies = [ "futures-util", "http", "http-body", - "iri-string", "mime", "pin-project-lite", "tower", "tower-layer", "tower-service", "tracing", + "url", ] [[package]] @@ -3606,9 +3594,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typenum" -version = "1.20.0" +version = "1.20.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" [[package]] name = "ucd-trie" @@ -3624,9 +3612,9 @@ checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-segmentation" -version = "1.13.2" +version = "1.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" [[package]] name = "unicode-xid" @@ -3673,9 +3661,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.23.2" +version = "1.23.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" dependencies = [ "js-sys", "wasm-bindgen", @@ -3725,9 +3713,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.118" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89" +checksum = "a254a4b10c19a76f09a27640e7ffbf9bc30bf67e16a3bf28aaefa4920fe81563" dependencies = [ "cfg-if", "once_cell", @@ -3738,9 +3726,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-futures" -version = "0.4.68" +version = "0.4.73" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f371d383f2fb139252e0bfac3b81b265689bf45b6874af544ffa4c975ac1ebf8" +checksum = "54568702fabf5d4849ce2b90fadfa64168a097eaf4b351ce9df8b687a0086aaf" dependencies = [ "js-sys", "wasm-bindgen", @@ -3748,9 +3736,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.118" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed" +checksum = "24a40fc75b0ec6f3746ceb10d36f53a93dcd68a93b11b6445983945d79eba0dc" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -3758,9 +3746,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.118" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904" +checksum = "908f34bd9b9ce3d4caf07b72dfab63d61504d156856c6bd3cd87fa350cf3985b" dependencies = [ "bumpalo", "proc-macro2", @@ -3771,18 +3759,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.118" +version = "0.2.123" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129" +checksum = "7acbf7616c27b194bbb550bf77ed0c2c3e5b7fd1260a93082b95fb7f47959b92" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.95" +version = "0.3.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f2dfbb17949fa2088e5d39408c48368947b86f7834484e87b73de55bc14d97d" +checksum = "6e0871acf327f283dc6da28a1696cdc64fb355ba9f935d052021fa77f35cce69" dependencies = [ "js-sys", "wasm-bindgen", @@ -3941,9 +3929,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "1.0.1" +version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" +checksum = "0592e1c9d151f854e6fd382574c3a0855250e1d9b2f99d9281c6e6391af352f1" dependencies = [ "memchr", ] @@ -3982,9 +3970,9 @@ checksum = "636f85e5ca6488e96401b61eb7de54f4e44755c988af0f52cf90230c312a1a89" [[package]] name = "yoke" -version = "0.8.2" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" dependencies = [ "stable_deref_trait", "yoke-derive", @@ -4005,18 +3993,18 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.48" +version = "0.8.52" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" dependencies = [ "proc-macro2", "quote", @@ -4025,9 +4013,9 @@ dependencies = [ [[package]] name = "zerofrom" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" dependencies = [ "zerofrom-derive", ] diff --git a/Cargo.nix b/Cargo.nix index 1d42f2ed..fd187670 100644 --- a/Cargo.nix +++ b/Cargo.nix @@ -489,9 +489,9 @@ rec { }; "autocfg" = rec { crateName = "autocfg"; - version = "1.5.0"; + version = "1.5.1"; edition = "2015"; - sha256 = "1s77f98id9l4af4alklmzq46f21c980v13z2r1pcxx6bqgw0d1n0"; + sha256 = "0lqasy5i30flcgih1b50kvsk6z32g09r1q4ql7q81pj6228jy0zj"; authors = [ "Josh Stone " ]; @@ -866,9 +866,9 @@ rec { }; "bitflags" = rec { crateName = "bitflags"; - version = "2.11.1"; + version = "2.13.0"; edition = "2021"; - sha256 = "1cvqijg3rvwgis20a66vfdxannjsxfy5fgjqkaq3l13gyfcj4lf4"; + sha256 = "1y239gpvl061rfvav7jds8mjs42kmwi39is7yx5d1qw3hvp8nf5l"; authors = [ "The Rust Project Developers" ]; @@ -898,9 +898,9 @@ rec { }; "built" = rec { crateName = "built"; - version = "0.8.0"; - edition = "2021"; - sha256 = "0r5f08lpjsr6j5ajkbmd0ymfmajpq8ddbfvi8ji8rx48y88qzbgl"; + version = "0.8.1"; + edition = "2024"; + sha256 = "1saq332pd6g3svvc9ah8myjpfvgqlzl2ksb1ypp3976kjcfm63jw"; authors = [ "Lukas Lueg " ]; @@ -924,15 +924,16 @@ rec { "chrono" = [ "dep:chrono" ]; "dependency-tree" = [ "cargo-lock/dependency-tree" ]; "git2" = [ "dep:git2" ]; + "gix" = [ "dep:gix" ]; "semver" = [ "dep:semver" ]; }; resolvedDefaultFeatures = [ "chrono" "git2" ]; }; "bumpalo" = rec { crateName = "bumpalo"; - version = "3.20.2"; + version = "3.20.3"; edition = "2021"; - sha256 = "1jrgxlff76k9glam0akhwpil2fr1w32gbjdf5hpipc7ld2c7h82x"; + sha256 = "0jc6va3nwcqikm7chnpdv1s87my3gs2j7g1sc7g3k91brg3arxbj"; authors = [ "Nick Fitzgerald " ]; @@ -961,9 +962,9 @@ rec { }; "cc" = rec { crateName = "cc"; - version = "1.2.60"; + version = "1.2.63"; edition = "2018"; - sha256 = "084a8ziprdlyrj865f3303qr0b7aaggilkl18slncss6m4yp1ia3"; + sha256 = "0zy2bqc4nvj6bv2cipx4h4bn65wf1zqf1fw1hsh64mmvg1hh2vjm"; authors = [ "Alex Crichton " ]; @@ -1011,9 +1012,9 @@ rec { }; "chrono" = rec { crateName = "chrono"; - version = "0.4.44"; + version = "0.4.45"; edition = "2021"; - sha256 = "1c64mk9a235271j5g3v4zrzqqmd43vp9vki7vqfllpqf5rd0fwy6"; + sha256 = "09rkcgk6is2sdhqs9142zv8xqnj8ryx8m9hknllqwyv9wxi9x9qs"; dependencies = [ { name = "iana-time-zone"; @@ -1906,9 +1907,9 @@ rec { }; "displaydoc" = rec { crateName = "displaydoc"; - version = "0.2.5"; + version = "0.2.6"; edition = "2021"; - sha256 = "1q0alair462j21iiqwrr21iabkfnb13d6x5w95lkdg21q2xrqdlp"; + sha256 = "0kyxwfbdmagd8afzb2pzja7wj8dhah7smxdsgw00iq8pa2jhmiqs"; procMacro = true; authors = [ "Jane Lusby " @@ -2108,12 +2109,9 @@ rec { }; "either" = rec { crateName = "either"; - version = "1.15.0"; + version = "1.16.0"; edition = "2021"; - sha256 = "069p1fknsmzn9llaizh77kip0pqmcwpdsykv2x30xpjyija5gis8"; - authors = [ - "bluss" - ]; + sha256 = "17k7jfbdz7k440h6lws9baz8p9zlxgb41sig3w81h80nwzsjyqli"; features = { "default" = [ "std" ]; "serde" = [ "dep:serde" ]; @@ -2830,9 +2828,9 @@ rec { }; "futures-timer" = rec { crateName = "futures-timer"; - version = "3.0.3"; + version = "3.0.4"; edition = "2018"; - sha256 = "094vw8k37djpbwv74bwf2qb7n6v6ghif4myss6smd6hgyajb127j"; + sha256 = "0s39in8ivw7g4d37pf31q02y44zd1hpfkd1pgra2slcqibdzlhxg"; libName = "futures_timer"; authors = [ "Alex Crichton " @@ -3090,9 +3088,9 @@ rec { }; "git2" = rec { crateName = "git2"; - version = "0.20.4"; - edition = "2018"; - sha256 = "0azykjpk3j6s354z23jkyq3r3pbmlw9ha1zsxkw5cnnpi1h2b23v"; + version = "0.21.0"; + edition = "2021"; + sha256 = "0bmqga9vlyx5sdlr0i28z0362s89xv9i4qcv20vvx9j54y9vzpfx"; authors = [ "Josh Triplett " "Alex Crichton " @@ -3114,17 +3112,14 @@ rec { name = "log"; packageId = "log"; } - { - name = "url"; - packageId = "url"; - } ]; features = { - "default" = [ "ssh" "https" ]; - "https" = [ "libgit2-sys/https" "openssl-sys" "openssl-probe" ]; + "cred" = [ "dep:url" ]; + "https" = [ "libgit2-sys/https" "openssl-sys" "openssl-probe" "cred" ]; "openssl-probe" = [ "dep:openssl-probe" ]; "openssl-sys" = [ "dep:openssl-sys" ]; - "ssh" = [ "libgit2-sys/ssh" ]; + "ssh" = [ "libgit2-sys/ssh" "cred" ]; + "unstable-sha256" = [ "libgit2-sys/unstable-sha256" ]; "vendored-libgit2" = [ "libgit2-sys/vendored" ]; "vendored-openssl" = [ "openssl-sys/vendored" "libgit2-sys/vendored-openssl" ]; "zlib-ng-compat" = [ "libgit2-sys/zlib-ng-compat" ]; @@ -3214,9 +3209,9 @@ rec { }; "h2" = rec { crateName = "h2"; - version = "0.4.13"; + version = "0.4.14"; edition = "2021"; - sha256 = "0m6w5gg0n0m1m5915bxrv8n4rlazhx5icknkslz719jhh4xdli1g"; + sha256 = "0cw7jk7kn2vn6f8w8ssh6gis1mljnfjxd606gvi4sjpyjayfy7qp"; authors = [ "Carl Lerche " "Sean McArthur " @@ -3327,14 +3322,11 @@ rec { }; resolvedDefaultFeatures = [ "allocator-api2" "default" "default-hasher" "equivalent" "inline-more" "raw-entry" ]; }; - "hashbrown 0.17.0" = rec { + "hashbrown 0.17.1" = rec { crateName = "hashbrown"; - version = "0.17.0"; + version = "0.17.1"; edition = "2024"; - sha256 = "0l8gvcz80lvinb7x22h53cqbi2y1fm603y2jhhh9qwygvkb7sijg"; - authors = [ - "Amanieu d'Antras " - ]; + sha256 = "0jmqz7i4yl6cm7rbn0i2ffkfrmwi6xkmzkaldr2v8bcsx2v0jngd"; features = { "alloc" = [ "dep:alloc" ]; "allocator-api2" = [ "dep:allocator-api2" ]; @@ -3409,9 +3401,9 @@ rec { }; "http" = rec { crateName = "http"; - version = "1.4.0"; + version = "1.4.2"; edition = "2021"; - sha256 = "06iind4cwsj1d6q8c2xgq8i2wka4ps74kmws24gsi1bzdlw2mfp3"; + sha256 = "09b4p8fiivkg7wm0b59fyrn1jkm7px298ci7zb9igz6n647gaw39"; authors = [ "Alex Crichton " "Carl Lerche " @@ -3528,9 +3520,9 @@ rec { }; "hyper" = rec { crateName = "hyper"; - version = "1.9.0"; + version = "1.10.1"; edition = "2021"; - sha256 = "1jmwbwqcaficskg76kq402gbymbnh2z4v99xwq3l5aa6n8bg16b2"; + sha256 = "1624nwrh1ci34psqcl3q8q266kha8kd6fmqjj14qck49l59iqa2m"; authors = [ "Sean McArthur " ]; @@ -4301,9 +4293,9 @@ rec { }; "idna_adapter" = rec { crateName = "idna_adapter"; - version = "1.2.1"; - edition = "2021"; - sha256 = "0i0339pxig6mv786nkqcxnwqa87v4m94b2653f6k3aj0jmhfkjis"; + version = "1.2.2"; + edition = "2024"; + sha256 = "0557p76l8hj35r9zn1yv7c6x1c0qbrsffmg80n0yy8361ly3fs6b"; authors = [ "The rust-url developers" ]; @@ -4337,7 +4329,7 @@ rec { } { name = "hashbrown"; - packageId = "hashbrown 0.17.0"; + packageId = "hashbrown 0.17.1"; usesDefaultFeatures = false; } ]; @@ -4395,39 +4387,6 @@ rec { }; resolvedDefaultFeatures = [ "default" "std" ]; }; - "iri-string" = rec { - crateName = "iri-string"; - version = "0.7.12"; - edition = "2021"; - sha256 = "082fpx6c5ghvmqpwxaf2b268m47z2ic3prajqbmi1s1qpfj5kri5"; - libName = "iri_string"; - authors = [ - "YOSHIOKA Takuma " - ]; - dependencies = [ - { - name = "memchr"; - packageId = "memchr"; - optional = true; - usesDefaultFeatures = false; - } - { - name = "serde"; - packageId = "serde"; - optional = true; - usesDefaultFeatures = false; - features = [ "derive" ]; - } - ]; - features = { - "alloc" = [ "serde?/alloc" ]; - "default" = [ "std" ]; - "memchr" = [ "dep:memchr" ]; - "serde" = [ "dep:serde" ]; - "std" = [ "alloc" "memchr?/std" "serde?/std" ]; - }; - resolvedDefaultFeatures = [ "alloc" "default" "std" ]; - }; "is_terminal_polyfill" = rec { crateName = "is_terminal_polyfill"; version = "1.70.2"; @@ -4497,9 +4456,9 @@ rec { }; "jiff" = rec { crateName = "jiff"; - version = "0.2.23"; + version = "0.2.28"; edition = "2021"; - sha256 = "0nc37n7jvgrzxdkcgc2hsfdf70lfagigjalh4igjrm5njvf4cd8s"; + sha256 = "00lixngcc7amh2fcsxfr0z38j06lllhapz192biv1qj97q1x60s6"; authors = [ "Andrew Gallant " ]; @@ -4545,12 +4504,10 @@ rec { usesDefaultFeatures = false; } { - name = "windows-sys"; - packageId = "windows-sys 0.61.2"; + name = "windows-link"; + packageId = "windows-link"; optional = true; - usesDefaultFeatures = false; target = { target, features }: (target."windows" or false); - features = [ "Win32_Foundation" "Win32_System_Time" ]; } ]; devDependencies = [ @@ -4569,7 +4526,7 @@ rec { "static-tz" = [ "dep:jiff-static" ]; "std" = [ "alloc" "log?/std" "serde_core?/std" ]; "tz-fat" = [ "jiff-static?/tz-fat" ]; - "tz-system" = [ "std" "dep:windows-sys" ]; + "tz-system" = [ "std" "dep:windows-link" ]; "tzdb-bundle-always" = [ "dep:jiff-tzdb" "alloc" ]; "tzdb-bundle-platform" = [ "dep:jiff-tzdb-platform" "alloc" ]; "tzdb-concatenated" = [ "std" ]; @@ -4579,9 +4536,9 @@ rec { }; "jiff-static" = rec { crateName = "jiff-static"; - version = "0.2.23"; + version = "0.2.28"; edition = "2021"; - sha256 = "192ss3cnixvg79cpa76clwkhn4mmz10vnwsbf7yjw8i484s8p31a"; + sha256 = "0irbhfh2f4i9w5l53jcmh6ssnhdd92wfy76978chgwnxilvk4bbq"; procMacro = true; libName = "jiff_static"; authors = [ @@ -4661,9 +4618,9 @@ rec { }; "js-sys" = rec { crateName = "js-sys"; - version = "0.3.95"; + version = "0.3.100"; edition = "2021"; - sha256 = "1jhj3kgxxgwm0cpdjiz7i2qapqr7ya9qswadmr63dhwx3lnyjr19"; + sha256 = "0qi1wjakyw2rx9wwprcfx77g3lvn1b8n6yvfhj2pgym4swh5y0pj"; libName = "js_sys"; authors = [ "The wasm-bindgen Developers" @@ -4672,7 +4629,6 @@ rec { { name = "cfg-if"; packageId = "cfg-if"; - optional = true; } { name = "futures-util"; @@ -4681,11 +4637,6 @@ rec { usesDefaultFeatures = false; features = [ "std" ]; } - { - name = "once_cell"; - packageId = "once_cell"; - usesDefaultFeatures = false; - } { name = "wasm-bindgen"; packageId = "wasm-bindgen"; @@ -4694,17 +4645,16 @@ rec { ]; features = { "default" = [ "std" "unsafe-eval" ]; - "futures" = [ "dep:cfg-if" "dep:futures-util" ]; - "futures-core-03-stream" = [ "futures" "dep:futures-core" ]; - "std" = [ "wasm-bindgen/std" ]; + "futures-core-03-stream" = [ "dep:futures-util" "dep:futures-core" ]; + "std" = [ "wasm-bindgen/std" "dep:futures-util" ]; }; - resolvedDefaultFeatures = [ "default" "futures" "std" "unsafe-eval" ]; + resolvedDefaultFeatures = [ "default" "std" "unsafe-eval" ]; }; "json-patch" = rec { crateName = "json-patch"; - version = "4.1.0"; + version = "4.2.0"; edition = "2021"; - sha256 = "147yaxmv3i4s0bdna86rgwpmqh2507fn4ighfpplaiqkw8ay807k"; + sha256 = "0wkv896d0pzq56i2kkl0giqpv117fwvhbpgs8iz85805w66l68bl"; libName = "json_patch"; authors = [ "Ivan Dubrov " @@ -4730,7 +4680,7 @@ rec { } { name = "thiserror"; - packageId = "thiserror 1.0.69"; + packageId = "thiserror 2.0.18"; } ]; devDependencies = [ @@ -4876,8 +4826,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "k8s_version"; authors = [ @@ -5527,9 +5477,9 @@ rec { }; "libc" = rec { crateName = "libc"; - version = "0.2.185"; + version = "0.2.186"; edition = "2021"; - sha256 = "13rbdaa59l3w92q7kfcxx8zbikm99zzw54h59aqvcv5wx47jrzsj"; + sha256 = "0rnyhzjyqq9x56skkllbjzzzwym3r61lq3l4hqj64v71gw0r3av8"; authors = [ "The Rust Project Developers" ]; @@ -5543,10 +5493,10 @@ rec { }; "libgit2-sys" = rec { crateName = "libgit2-sys"; - version = "0.18.3+1.9.2"; + version = "0.18.5+1.9.4"; edition = "2021"; links = "git2"; - sha256 = "11rlbyihj3k35mnkxxz4yvsnlx33a4r9srl66c5vp08pp72arcy9"; + sha256 = "18lwqnhy7qxg4iw24s1a0n7aj7qbnryry1iy0w32k4f1xbk6lp80"; libName = "libgit2_sys"; libPath = "lib.rs"; authors = [ @@ -5604,10 +5554,10 @@ rec { }; "libz-sys" = rec { crateName = "libz-sys"; - version = "1.1.28"; + version = "1.1.29"; edition = "2018"; links = "z"; - sha256 = "08hyf9v85zifl3353xc7i5wr53v9b3scri856cmphl3gaxp24fpw"; + sha256 = "1n98kqya7a7a0cxf5n5z3g13rj7a1vqxynk2xc7bja1qfxbrdg45"; libName = "libz_sys"; authors = [ "Alex Crichton " @@ -5684,9 +5634,9 @@ rec { }; "log" = rec { crateName = "log"; - version = "0.4.29"; + version = "0.4.32"; edition = "2021"; - sha256 = "15q8j9c8g5zpkcw0hnd6cf2z7fxqnvsjh3rw5mv5q10r83i34l2y"; + sha256 = "0fmdg0cxig7i4fwf1sw7fmg4d1gdbfzniawcfpwydy1q7320fgwm"; authors = [ "The Rust Project Developers" ]; @@ -5740,9 +5690,9 @@ rec { }; "memchr" = rec { crateName = "memchr"; - version = "2.8.0"; + version = "2.8.1"; edition = "2021"; - sha256 = "0y9zzxcqxvdqg6wyag7vc3h0blhdn7hkq164bxyx2vph8zs5ijpq"; + sha256 = "1n448jx01h5z2xknj6x2dhxgr8s8fb717cf6vfqj5lmhkpj7m53b"; authors = [ "Andrew Gallant " "bluss" @@ -5803,9 +5753,9 @@ rec { }; "mio" = rec { crateName = "mio"; - version = "1.2.0"; + version = "1.2.1"; edition = "2021"; - sha256 = "1hanrh4fwsfkdqdaqfidz48zz1wdix23zwn3r2x78am0garfbdsh"; + sha256 = "1nkggmrlnjs93w8rja4lvjj4aml1xqahgimv1h0p7d373kvhmg82"; authors = [ "Carl Lerche " "Thomas de Zeeuw " @@ -5941,9 +5891,9 @@ rec { }; "num-conv" = rec { crateName = "num-conv"; - version = "0.2.1"; + version = "0.2.2"; edition = "2021"; - sha256 = "0rqrr29brafaa2za352pbmhkk556n7f8z9rrkgmjp1idvdl3fry6"; + sha256 = "0hg4f9bwmy7cwpxdkm165dmkfc8jhkkayci234jsmi5ssb33j5sj"; libName = "num_conv"; authors = [ "Jacob Pratt " @@ -6903,9 +6853,9 @@ rec { }; "pin-project" = rec { crateName = "pin-project"; - version = "1.1.11"; + version = "1.1.13"; edition = "2021"; - sha256 = "05zm3y3bl83ypsr6favxvny2kys4i19jiz1y18ylrbxwsiz9qx7i"; + sha256 = "09091qp946lpmjz4yp0xil1r5v4hgc91fi19dg5csayhdqrv4ri4"; libName = "pin_project"; dependencies = [ { @@ -6917,9 +6867,9 @@ rec { }; "pin-project-internal" = rec { crateName = "pin-project-internal"; - version = "1.1.11"; + version = "1.1.13"; edition = "2021"; - sha256 = "1ik4mpb92da75inmjvxf2qm61vrnwml3x24wddvrjlqh1z9hxcnr"; + sha256 = "12rzlh07i1sdgrvzj6wgkka5bjqyvbfsl8knq6qi7g16m7q9aqy9"; procMacro = true; libName = "pin_project_internal"; dependencies = [ @@ -7241,9 +7191,9 @@ rec { }; "prost" = rec { crateName = "prost"; - version = "0.14.3"; + version = "0.14.4"; edition = "2021"; - sha256 = "0s057z9nzggzy7x4bbsiar852hg7zb81f4z4phcdb0ig99971snj"; + sha256 = "1qas5v5rap45f43v3ja0jngxrrafrkcwl0iw5a3ld1pz2rscd2jj"; authors = [ "Dan Burkert " "Lucio Franco " @@ -7270,9 +7220,9 @@ rec { }; "prost-derive" = rec { crateName = "prost-derive"; - version = "0.14.3"; + version = "0.14.4"; edition = "2021"; - sha256 = "02zvva6kb0pfvlyc4nac6gd37ncjrs8jq5scxcq4nbqkc8wh5ii7"; + sha256 = "1pqa77d7da5pf6ba3kjj7510m5cynz6902ax01ckvr0pfrgv4w5m"; procMacro = true; libName = "prost_derive"; authors = [ @@ -7308,9 +7258,9 @@ rec { }; "prost-types" = rec { crateName = "prost-types"; - version = "0.14.3"; + version = "0.14.4"; edition = "2021"; - sha256 = "1mrxrciryfgi6a0vmrgyj3g27r9hdhlgwkq71cgv3icbvg5w94c9"; + sha256 = "02ivjvc4cwl5bfgjs3l00hwlrk74z8zlg1xcgx60bww8fvf6fjgr"; libName = "prost_types"; authors = [ "Dan Burkert " @@ -7613,9 +7563,9 @@ rec { }; "regex" = rec { crateName = "regex"; - version = "1.12.3"; + version = "1.12.4"; edition = "2021"; - sha256 = "0xp2q0x7ybmpa5zlgaz00p8zswcirj9h8nry3rxxsdwi9fhm81z1"; + sha256 = "1fm6si2xpmhwqflabdqsakc0qkq718wx2ljl37nbj75fb5vjnagi"; authors = [ "The Rust Project Developers" "Andrew Gallant " @@ -7732,9 +7682,9 @@ rec { }; "regex-syntax" = rec { crateName = "regex-syntax"; - version = "0.8.10"; + version = "0.8.11"; edition = "2021"; - sha256 = "02jx311ka0daxxc7v45ikzhcl3iydjbbb0mdrpc1xgg8v7c7v2fw"; + sha256 = "1m25h5q2wp976fb9gc3dsc9l99svcvd5cri8lncb51c46ydgzxnn"; libName = "regex_syntax"; authors = [ "The Rust Project Developers" @@ -8272,9 +8222,9 @@ rec { }; "rustls" = rec { crateName = "rustls"; - version = "0.23.38"; + version = "0.23.40"; edition = "2021"; - sha256 = "089ssmhd79f0kd22brh6lkaadql2p3pi6579ax1s0kn1n9pldyb9"; + sha256 = "12qnv3ag4wrw7aj8jng74kgrilpjm2b1rfcjaac8h691frccv1pg"; dependencies = [ { name = "log"; @@ -8341,9 +8291,9 @@ rec { }; "rustls-native-certs" = rec { crateName = "rustls-native-certs"; - version = "0.8.3"; + version = "0.8.4"; edition = "2021"; - sha256 = "0qrajg2n90bcr3bcq6j95gjm7a9lirfkkdmjj32419dyyzan0931"; + sha256 = "0kgazl8zc1sv63qg179bz96ilzh56lzfa5k92ji7d265f4kibdfs"; libName = "rustls_native_certs"; dependencies = [ { @@ -8372,9 +8322,9 @@ rec { }; "rustls-pki-types" = rec { crateName = "rustls-pki-types"; - version = "1.14.0"; + version = "1.14.1"; edition = "2021"; - sha256 = "1p9zsgslvwzzkzhm6bqicffqndr4jpx67992b0vl0pi21a5hy15y"; + sha256 = "1a9pr54y0f3qr97bxpd3ahjldq0gqdld0h799xbnwdzbwxx1k9rh"; libName = "rustls_pki_types"; dependencies = [ { @@ -8910,9 +8860,9 @@ rec { }; "serde_json" = rec { crateName = "serde_json"; - version = "1.0.149"; + version = "1.0.150"; edition = "2021"; - sha256 = "11jdx4vilzrjjd1dpgy67x5lgzr0laplz30dhv75lnf5ffa07z43"; + sha256 = "1ffgfhy9kndjnrz8lmy95pr758p2zk8dxv6yi99x0vkkni24w0g8"; authors = [ "Erick Tryzelaar " "David Tolnay " @@ -9153,9 +9103,9 @@ rec { }; "shlex" = rec { crateName = "shlex"; - version = "1.3.0"; - edition = "2015"; - sha256 = "0r1y6bv26c1scpxvhg2cabimrmwgbp4p3wy6syj9n0c4s3q2znhg"; + version = "2.0.1"; + edition = "2018"; + sha256 = "1fjsll1cd7d2bcpdij9kd6w62rpbc7qqzvydvs021vsmr1cxvypq"; authors = [ "comex " "Fenhl " @@ -9458,9 +9408,9 @@ rec { }; "socket2" = rec { crateName = "socket2"; - version = "0.6.3"; + version = "0.6.4"; edition = "2021"; - sha256 = "0gkjjcyn69hqhhlh5kl8byk5m0d7hyrp2aqwzbs3d33q208nwxis"; + sha256 = "0ldyp5rhba15spwxj1n94xh7sjks1398c3vwpwkxkd1087nwzlaj"; authors = [ "Alex Crichton " "Thomas de Zeeuw " @@ -9656,8 +9606,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_certs"; authors = [ @@ -9759,8 +9709,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_operator"; authors = [ @@ -9953,8 +9903,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; libName = "stackable_operator_derive"; @@ -9988,8 +9938,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_shared"; authors = [ @@ -10069,8 +10019,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_telemetry"; authors = [ @@ -10179,8 +10129,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_versioned"; authors = [ @@ -10229,8 +10179,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; procMacro = true; libName = "stackable_versioned_macros"; @@ -10297,8 +10247,8 @@ rec { workspace_member = null; src = pkgs.fetchgit { url = "https://github.com/stackabletech//operator-rs.git"; - rev = "46cd3f93a788d44d177a8794fde91fbefa3156d7"; - sha256 = "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47"; + rev = "451088f77acee6c3d296754698260256c250ecb2"; + sha256 = "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b"; }; libName = "stackable_webhook"; authors = [ @@ -10942,9 +10892,9 @@ rec { }; "tokio" = rec { crateName = "tokio"; - version = "1.52.1"; + version = "1.52.3"; edition = "2021"; - sha256 = "1imw1dkkv38p66i33m5hsyk3d6prsbyrayjvqhndjvz89ybywzdn"; + sha256 = "1zpzazypkg61sw91na1m85x5s4rsjym335fwwhwm1hcs70dz1iwg"; authors = [ "Tokio Contributors " ]; @@ -11254,9 +11204,9 @@ rec { }; "toml_edit" = rec { crateName = "toml_edit"; - version = "0.25.11+spec-1.1.0"; + version = "0.25.12+spec-1.1.0"; edition = "2024"; - sha256 = "0awzffbkx33v9x4h19b5mfrwp3sn4ifr16y58sbk6j6l5v9c8n8b"; + sha256 = "1mx5paq837rjw7w51zprrjynk1vaig9yzxfqz9ac79jmd7f3w5fj"; dependencies = [ { name = "indexmap"; @@ -11309,9 +11259,9 @@ rec { }; "tonic" = rec { crateName = "tonic"; - version = "0.14.5"; - edition = "2021"; - sha256 = "1v4k7aa28m7722gz9qak2jiy7lis1ycm4fdmq63iip4m0qdcdizy"; + version = "0.14.6"; + edition = "2024"; + sha256 = "1vs5ci6z6b9xhfsnx4s8qx6bqi1zzcrxncjp71147a0gqwc5aamc"; authors = [ "Lucio Franco " ]; @@ -11438,9 +11388,9 @@ rec { }; "tonic-prost" = rec { crateName = "tonic-prost"; - version = "0.14.5"; - edition = "2021"; - sha256 = "02fkg2bv87q0yds2wz3w0s7i1x6qcgbrl00dy6ipajdapfh7clx5"; + version = "0.14.6"; + edition = "2024"; + sha256 = "184y40nf0iyzc5rg32ivgd88snv68sqy1kchynn55r1vhml9z12h"; libName = "tonic_prost"; authors = [ "Lucio Franco " @@ -11464,9 +11414,9 @@ rec { }; "tonic-types" = rec { crateName = "tonic-types"; - version = "0.14.5"; - edition = "2021"; - sha256 = "16bk1cxi2m0xgaabf98nnj7dn9j16ymkh27jq4s3shjm4a85m1ra"; + version = "0.14.6"; + edition = "2024"; + sha256 = "1s286gg71pjajny8xar0azq1w9lgz1ks3jm3pccxb0qz0q11pavk"; libName = "tonic_types"; authors = [ "Lucio Franco " @@ -11609,9 +11559,9 @@ rec { }; "tower-http" = rec { crateName = "tower-http"; - version = "0.6.8"; + version = "0.6.11"; edition = "2018"; - sha256 = "1y514jwzbyrmrkbaajpwmss4rg0mak82k16d6588w9ncaffmbrnl"; + sha256 = "0h08wjgs3hwnq11iwwzlmnabn1h4cl0fzd48svaccvqffkiggz2c"; libName = "tower_http"; authors = [ "Tower Maintainers " @@ -11645,11 +11595,6 @@ rec { packageId = "http-body"; optional = true; } - { - name = "iri-string"; - packageId = "iri-string"; - optional = true; - } { name = "mime"; packageId = "mime"; @@ -11679,6 +11624,11 @@ rec { optional = true; usesDefaultFeatures = false; } + { + name = "url"; + packageId = "url"; + optional = true; + } ]; devDependencies = [ { @@ -11700,35 +11650,33 @@ rec { } ]; features = { - "async-compression" = [ "dep:async-compression" ]; "auth" = [ "base64" "validate-request" ]; "base64" = [ "dep:base64" ]; "catch-panic" = [ "tracing" "futures-util/std" "dep:http-body" "dep:http-body-util" ]; - "compression-br" = [ "async-compression/brotli" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; - "compression-deflate" = [ "async-compression/zlib" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; + "compression-br" = [ "dep:async-compression" "async-compression?/brotli" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; + "compression-deflate" = [ "dep:async-compression" "async-compression?/zlib" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; "compression-full" = [ "compression-br" "compression-deflate" "compression-gzip" "compression-zstd" ]; - "compression-gzip" = [ "async-compression/gzip" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; - "compression-zstd" = [ "async-compression/zstd" "futures-core" "dep:http-body" "tokio-util" "tokio" ]; - "decompression-br" = [ "async-compression/brotli" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; - "decompression-deflate" = [ "async-compression/zlib" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; + "compression-gzip" = [ "dep:async-compression" "async-compression?/gzip" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; + "compression-zstd" = [ "dep:async-compression" "async-compression?/zstd" "futures-core" "dep:http-body" "tokio-util" "dep:tokio" ]; + "decompression-br" = [ "dep:async-compression" "async-compression?/brotli" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; + "decompression-deflate" = [ "dep:async-compression" "async-compression?/zlib" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; "decompression-full" = [ "decompression-br" "decompression-deflate" "decompression-gzip" "decompression-zstd" ]; - "decompression-gzip" = [ "async-compression/gzip" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; - "decompression-zstd" = [ "async-compression/zstd" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "tokio" ]; - "follow-redirect" = [ "futures-util" "dep:http-body" "iri-string" "tower/util" ]; - "fs" = [ "futures-core" "futures-util" "dep:http-body" "dep:http-body-util" "tokio/fs" "tokio-util/io" "tokio/io-util" "dep:http-range-header" "mime_guess" "mime" "percent-encoding" "httpdate" "set-status" "futures-util/alloc" "tracing" ]; - "full" = [ "add-extension" "auth" "catch-panic" "compression-full" "cors" "decompression-full" "follow-redirect" "fs" "limit" "map-request-body" "map-response-body" "metrics" "normalize-path" "propagate-header" "redirect" "request-id" "sensitive-headers" "set-header" "set-status" "timeout" "trace" "util" "validate-request" ]; + "decompression-gzip" = [ "dep:async-compression" "async-compression?/gzip" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; + "decompression-zstd" = [ "dep:async-compression" "async-compression?/zstd" "futures-core" "dep:http-body" "dep:http-body-util" "tokio-util" "dep:tokio" ]; + "follow-redirect" = [ "futures-util" "dep:http-body" "dep:url" "tower/util" ]; + "fs" = [ "dep:tokio" "tokio?/fs" "tokio?/io-util" "futures-core" "futures-util" "dep:http-body" "dep:http-body-util" "tokio-util/io" "dep:http-range-header" "mime_guess" "mime" "percent-encoding" "httpdate" "set-status" "futures-util/alloc" ]; + "full" = [ "add-extension" "auth" "catch-panic" "compression-full" "cors" "decompression-full" "follow-redirect" "fs" "limit" "map-request-body" "map-response-body" "metrics" "normalize-path" "on-early-drop" "propagate-header" "redirect" "request-id" "sensitive-headers" "set-header" "set-status" "timeout" "trace" "util" "validate-request" ]; "futures-core" = [ "dep:futures-core" ]; "futures-util" = [ "dep:futures-util" ]; "httpdate" = [ "dep:httpdate" ]; - "iri-string" = [ "dep:iri-string" ]; "limit" = [ "dep:http-body" "dep:http-body-util" ]; - "metrics" = [ "dep:http-body" "tokio/time" ]; + "metrics" = [ "dep:http-body" "dep:tokio" "tokio?/time" ]; "mime" = [ "dep:mime" ]; "mime_guess" = [ "dep:mime_guess" ]; + "on-early-drop" = [ "dep:http-body" ]; "percent-encoding" = [ "dep:percent-encoding" ]; "request-id" = [ "uuid" ]; - "timeout" = [ "dep:http-body" "tokio/time" ]; - "tokio" = [ "dep:tokio" ]; + "timeout" = [ "dep:http-body" "dep:tokio" "tokio?/time" ]; "tokio-util" = [ "dep:tokio-util" ]; "tower" = [ "dep:tower" ]; "trace" = [ "dep:http-body" "tracing" ]; @@ -11737,7 +11685,7 @@ rec { "uuid" = [ "dep:uuid" ]; "validate-request" = [ "mime" ]; }; - resolvedDefaultFeatures = [ "auth" "base64" "default" "follow-redirect" "futures-util" "iri-string" "map-response-body" "mime" "tower" "trace" "tracing" "util" "validate-request" ]; + resolvedDefaultFeatures = [ "auth" "base64" "default" "follow-redirect" "futures-util" "map-response-body" "mime" "tower" "trace" "tracing" "util" "validate-request" ]; }; "tower-layer" = rec { crateName = "tower-layer"; @@ -12181,13 +12129,9 @@ rec { }; "typenum" = rec { crateName = "typenum"; - version = "1.20.0"; + version = "1.20.1"; edition = "2018"; - sha256 = "1pj35y6q11d3y55gdl6g1h2dfhmybjming0jdi9bh0bpnqm11kj0"; - authors = [ - "Paho Lurie-Gregg " - "Andre Bogus " - ]; + sha256 = "086s9ly0906kw5yw41249fba97w5zfxf03pyfwdkffvcprqfixdn"; features = { "scale-info" = [ "dep:scale-info" ]; "scale_info" = [ "scale-info/derive" ]; @@ -12220,9 +12164,9 @@ rec { }; "unicode-segmentation" = rec { crateName = "unicode-segmentation"; - version = "1.13.2"; + version = "1.13.3"; edition = "2018"; - sha256 = "135a26m4a0wj319gcw28j6a5aqvz00jmgwgmcs6szgxjf942facn"; + sha256 = "1a47zaq83p386r3baq4m018xd5q4q0grdg56i1x042dzn71x7xf6"; libName = "unicode_segmentation"; authors = [ "kwantam " @@ -12350,9 +12294,9 @@ rec { }; "uuid" = rec { crateName = "uuid"; - version = "1.23.2"; + version = "1.23.3"; edition = "2021"; - sha256 = "1xy942s4z0bi8p3441wvd4ry3hx6ry1c7s6fgrr38462xqybhn6j"; + sha256 = "1drddl03gi12vl1s3l2h371dw39plhn9wappp00v707g7h96nk8l"; authors = [ "Ashley Mannix" "Dylan DPC" @@ -12495,9 +12439,9 @@ rec { }; "wasm-bindgen" = rec { crateName = "wasm-bindgen"; - version = "0.2.118"; + version = "0.2.123"; edition = "2021"; - sha256 = "129s5r14fx4v4xrzpx2c6l860nkxpl48j50y7kl6j16bpah3iy8b"; + sha256 = "0qqmx07r597gm8lbz8qngvv0phwvpzzyfh3nl84nz9qr1jqs8m52"; libName = "wasm_bindgen"; authors = [ "The wasm-bindgen Developers" @@ -12546,9 +12490,9 @@ rec { }; "wasm-bindgen-futures" = rec { crateName = "wasm-bindgen-futures"; - version = "0.4.68"; + version = "0.4.73"; edition = "2021"; - sha256 = "1y7bq5d9fk7s9xaayx38bgs9ns35na0kpb5zw19944zvya1x6wgk"; + sha256 = "1bva12h8gdpqkp753czlxabs0s21lvgzm41brr4lhpdzz818fmjl"; libName = "wasm_bindgen_futures"; authors = [ "The wasm-bindgen Developers" @@ -12558,7 +12502,6 @@ rec { name = "js-sys"; packageId = "js-sys"; usesDefaultFeatures = false; - features = [ "futures" ]; } { name = "wasm-bindgen"; @@ -12575,9 +12518,9 @@ rec { }; "wasm-bindgen-macro" = rec { crateName = "wasm-bindgen-macro"; - version = "0.2.118"; + version = "0.2.123"; edition = "2021"; - sha256 = "1v98r8vs17cj8918qsg0xx4nlg4nxk1g0jd4nwnyrh1687w29zzf"; + sha256 = "1p50xdwmv543b52bc49vm5lcsgd9adpx647bdisg7ihfbg3hz914"; procMacro = true; libName = "wasm_bindgen_macro"; authors = [ @@ -12599,9 +12542,9 @@ rec { }; "wasm-bindgen-macro-support" = rec { crateName = "wasm-bindgen-macro-support"; - version = "0.2.118"; + version = "0.2.123"; edition = "2021"; - sha256 = "0169jr0q469hfx5zqxfyywf2h2f4aj17vn4zly02nfwqmxghc24x"; + sha256 = "0nwqyc63byl7rp9nnv45av8h85fncfmxywkvy35d9qwwkfyk93wh"; libName = "wasm_bindgen_macro_support"; authors = [ "The wasm-bindgen Developers" @@ -12635,10 +12578,10 @@ rec { }; "wasm-bindgen-shared" = rec { crateName = "wasm-bindgen-shared"; - version = "0.2.118"; + version = "0.2.123"; edition = "2021"; links = "wasm_bindgen"; - sha256 = "0ag1vvdzi4334jlzilsy14y3nyzwddf1ndn62fyhf6bg62g4vl2z"; + sha256 = "14lvjm3pzywm5c4962i6s5zmngic1knpggshnnxr9c97dihzgjvs"; libName = "wasm_bindgen_shared"; authors = [ "The wasm-bindgen Developers" @@ -12653,9 +12596,9 @@ rec { }; "web-sys" = rec { crateName = "web-sys"; - version = "0.3.95"; + version = "0.3.100"; edition = "2021"; - sha256 = "0zfr2jy5bpkkggl88i43yy37p538hg20i56kwn421yj9g6qznbag"; + sha256 = "0sffbkrpgyi1402mv4wzp9av6ky6rnb1d2m2dpf87wi7yfn7223f"; libName = "web_sys"; authors = [ "The wasm-bindgen Developers" @@ -12739,6 +12682,7 @@ rec { "CssStyleSheet" = [ "StyleSheet" ]; "CssSupportsRule" = [ "CssConditionRule" "CssGroupingRule" "CssRule" ]; "CssTransition" = [ "Animation" "EventTarget" ]; + "CssViewTransitionRule" = [ "CssRule" ]; "CustomEvent" = [ "Event" ]; "DedicatedWorkerGlobalScope" = [ "EventTarget" "WorkerGlobalScope" ]; "DelayNode" = [ "AudioNode" "EventTarget" ]; @@ -13815,7 +13759,7 @@ rec { "Win32_Web" = [ "Win32" ]; "Win32_Web_InternetExplorer" = [ "Win32_Web" ]; }; - resolvedDefaultFeatures = [ "Wdk" "Wdk_Foundation" "Wdk_Storage" "Wdk_Storage_FileSystem" "Wdk_System" "Wdk_System_IO" "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_Security_Cryptography" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Memory" "Win32_System_Pipes" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_Time" "Win32_System_WindowsProgramming" "default" ]; + resolvedDefaultFeatures = [ "Wdk" "Wdk_Foundation" "Wdk_Storage" "Wdk_Storage_FileSystem" "Wdk_System" "Wdk_System_IO" "Win32" "Win32_Foundation" "Win32_Networking" "Win32_Networking_WinSock" "Win32_Security" "Win32_Security_Authentication" "Win32_Security_Authentication_Identity" "Win32_Security_Credentials" "Win32_Security_Cryptography" "Win32_Storage" "Win32_Storage_FileSystem" "Win32_System" "Win32_System_Console" "Win32_System_Diagnostics" "Win32_System_Diagnostics_Debug" "Win32_System_IO" "Win32_System_LibraryLoader" "Win32_System_Memory" "Win32_System_Pipes" "Win32_System_SystemInformation" "Win32_System_SystemServices" "Win32_System_Threading" "Win32_System_WindowsProgramming" "default" ]; }; "windows-targets" = rec { crateName = "windows-targets"; @@ -13952,9 +13896,9 @@ rec { }; "winnow" = rec { crateName = "winnow"; - version = "1.0.1"; + version = "1.0.3"; edition = "2021"; - sha256 = "1dbji1bwviy08pl74f2qw2m4w9hc4p3vyl3lfj05jdydy59w1nh9"; + sha256 = "1wajycd3krn6h699vydjv7hm0ll5l31p899qzpk59y2is74y34h5"; dependencies = [ { name = "memchr"; @@ -14077,9 +14021,9 @@ rec { }; "yoke" = rec { crateName = "yoke"; - version = "0.8.2"; + version = "0.8.3"; edition = "2021"; - sha256 = "1jprcs7a98a5whvfs6r3jvfh1nnfp6zyijl7y4ywmn88lzywbs5b"; + sha256 = "1xgyj6c2lxj2bp891ynmhws87c6z7yyv2li1v0ss9di40hxf57vh"; authors = [ "Manish Goregaokar " ]; @@ -14143,9 +14087,9 @@ rec { }; "zerocopy" = rec { crateName = "zerocopy"; - version = "0.8.48"; + version = "0.8.52"; edition = "2021"; - sha256 = "1sb8plax8jbrsng1jdval7bdhk7hhrx40dz3hwh074k6knzkgm7f"; + sha256 = "0gv563swc1yn3k8w3wjj07a8q293rkx99nfp3a25vzzmbycj446f"; authors = [ "Joshua Liebow-Feeser " "Jack Wrenn " @@ -14179,9 +14123,9 @@ rec { }; "zerocopy-derive" = rec { crateName = "zerocopy-derive"; - version = "0.8.48"; + version = "0.8.52"; edition = "2021"; - sha256 = "1m5s0g92cxggqc74j83k1priz24k3z93sj5gadppd20p9c4cvqvh"; + sha256 = "0c3rhsh4sd9kdym4z55zprybjkydy9y2gvw75d72aapcfa5z7rqs"; procMacro = true; libName = "zerocopy_derive"; authors = [ @@ -14214,11 +14158,11 @@ rec { }; "zerofrom" = rec { crateName = "zerofrom"; - version = "0.1.7"; + version = "0.1.8"; edition = "2021"; - sha256 = "1py40in4rirc9q8w36q67pld0zk8ssg024xhh0cncxgal7ra3yk9"; + sha256 = "0wjjdj7gdmd0iq91gzkxl7dlv0nhkk80l4bmdpzh3a1yh48mmh0f"; authors = [ - "Manish Goregaokar " + "The ICU4X Project Developers" ]; dependencies = [ { diff --git a/crate-hashes.json b/crate-hashes.json index deac3bf4..c9a6e6a9 100644 --- a/crate-hashes.json +++ b/crate-hashes.json @@ -1,12 +1,12 @@ { - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", - "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "05zvdnnmjvpma9yds4kp8rwkna2d3kkws3yry2080jdy753npz47", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#k8s-version@0.1.3": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-certs@0.4.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator-derive@0.3.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-operator@0.111.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-shared@0.1.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-telemetry@0.6.4": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned-macros@0.10.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-versioned@0.10.0": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", + "git+https://github.com/stackabletech//operator-rs.git?branch=smooth-operator#stackable-webhook@0.9.1": "1ifdpn0jvrf3xbgqldqxrq9ig1dc34d4fip7qxn38526k8004p4b", "git+https://github.com/stackabletech/product-config.git?tag=0.8.0#product-config@0.8.0": "1dz70kapm2wdqcr7ndyjji0lhsl98bsq95gnb2lw487wf6yr7987" } \ No newline at end of file diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 1fdb415e..80f5205b 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -64,14 +64,9 @@ pub fn build_rolegroup_config_map( logging: &Logging, container: &Container, ) -> Result { - // Flatten the typed `webserver_config.py` overrides into a plain map for the file writer, - // dropping entries whose value is unset (`null`). - let config_file_overrides: BTreeMap = config_overrides - .webserver_config_py - .overrides - .iter() - .filter_map(|(key, value)| value.clone().map(|value| (key.clone(), value))) - .collect(); + // Flatten the typed `webserver_config.py` overrides into a plain map for the file writer. + let config_file_overrides: BTreeMap = + config_overrides.webserver_config_py.overrides.clone(); let config_file = webserver_config::build( &validated_cluster.authentication_config, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index a16ef09f..dbf16e54 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -202,17 +202,14 @@ mod tests { let config_overrides = validated.config_overrides; let env_overrides = validated.env_overrides; - // configOverrides are kept typed (values are `Option`). The role-group AUTH_TYPE - // overrides the role-level one; both role-only and group-only keys are kept. + // configOverrides are kept typed. The role-group AUTH_TYPE overrides the role-level one; + // both role-only and group-only keys are kept. assert_eq!( config_overrides.webserver_config_py.overrides, BTreeMap::from([ - ("AUTH_TYPE".to_string(), Some("AUTH_DB".to_string())), - ("ROLE_ONLY_KEY".to_string(), Some("role-value".to_string())), - ( - "GROUP_ONLY_KEY".to_string(), - Some("group-value".to_string()) - ), + ("AUTH_TYPE".to_string(), "AUTH_DB".to_string()), + ("ROLE_ONLY_KEY".to_string(), "role-value".to_string()), + ("GROUP_ONLY_KEY".to_string(), "group-value".to_string()), ]) ); @@ -224,12 +221,12 @@ mod tests { ); } - /// A role-group `null` override inherits the role-level value instead of unsetting it — the - /// behavioural consequence of merging with `Merge` rather than `.extend()`. `main`'s - /// product-config used `.extend()`, where the same input would have *removed* the key. This - /// test pins that choice (mirrors the kafka-operator test). + /// A `null` override value is rejected by the CRD. `configOverrides` values are typed as + /// `String` (operator-rs `KeyValueConfigOverrides`, since the `Option` was removed in + /// operator-rs #1219), so there is no longer a way to express "unset this key" via `null` — + /// the previous `null`-means-inherit/unset semantics no longer exist at the type level. #[test] - fn role_group_null_inherits_role_value_rather_than_unsetting_it() { + fn role_group_null_override_value_is_rejected() { let cluster_yaml = r#" apiVersion: airflow.stackable.tech/v1alpha2 kind: AirflowCluster @@ -267,53 +264,11 @@ mod tests { config: {} "#; let deserializer = serde_yaml::Deserializer::from_str(cluster_yaml); - let cluster: v1alpha2::AirflowCluster = - serde_yaml::with::singleton_map_recursive::deserialize(deserializer).unwrap(); - let role = cluster - .get_role(&AirflowRole::Webserver) - .expect("webserver role"); - - // For contrast: under the old `.extend()` layering the role-group `null` overwrites the - // role value, and the key is dropped on flatten — i.e. AUTH_TYPE is unset entirely. - let old_extend_behaviour: BTreeMap = { - let mut combined = role - .config - .config_overrides - .webserver_config_py - .overrides - .clone(); - let rg = role.role_groups.get("default").expect("default role group"); - combined.extend( - rg.config - .config_overrides - .webserver_config_py - .overrides - .clone(), - ); - combined - .into_iter() - .filter_map(|(key, value)| value.map(|value| (key, value))) - .collect() - }; + let result: Result = + serde_yaml::with::singleton_map_recursive::deserialize(deserializer); assert!( - !old_extend_behaviour.contains_key("AUTH_TYPE"), - "under the old `.extend()` behaviour the role-group `null` unsets AUTH_TYPE" - ); - - // What we do now (Merge): the role-group `null` inherits the role-level value, so - // AUTH_TYPE survives as the role's "AUTH_OID". - let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver); - let rolegroup = role.role_groups.get("default").expect("default role group"); - let config_overrides = validate_role_group(&role, rolegroup, &default_config) - .expect("validated role group") - .config_overrides; - assert_eq!( - config_overrides - .webserver_config_py - .overrides - .get("AUTH_TYPE"), - Some(&Some("AUTH_OID".to_string())), - "role-group `null` should inherit the role-level AUTH_TYPE under Merge semantics" + result.is_err(), + "a `null` configOverrides value should be rejected: values are typed as `String`" ); } From 04171b163fc504b5513e19c2760ba07b658c8b17 Mon Sep 17 00:00:00 2001 From: Andrew Kenworthy Date: Wed, 10 Jun 2026 13:00:14 +0200 Subject: [PATCH 32/35] regenerate charts --- extra/crds.yaml | 154 +++++++----------------------------------------- 1 file changed, 22 insertions(+), 132 deletions(-) diff --git a/extra/crds.yaml b/extra/crds.yaml index 0f42aa74..5b5ed9d4 100644 --- a/extra/crds.yaml +++ b/extra/crds.yaml @@ -483,14 +483,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -1002,14 +997,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -2000,14 +1990,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -2519,14 +2504,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -3073,14 +3053,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -3571,14 +3546,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -4090,14 +4060,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -4582,14 +4547,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -5101,14 +5061,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -5593,14 +5548,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -6117,14 +6067,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -6684,14 +6629,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -7203,14 +7143,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -8177,14 +8112,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -8696,14 +8626,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -9250,14 +9175,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -9748,14 +9668,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -10267,14 +10182,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -10759,14 +10669,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -11278,14 +11183,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -11770,14 +11670,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: @@ -12294,14 +12189,9 @@ spec: properties: webserver_config.py: additionalProperties: - nullable: true type: string default: {} - description: |- - Flat key-value overrides for `*.properties`, Hadoop XML, etc. - - This is backwards-compatible with the existing flat key-value YAML format - used by `HashMap`. + description: Flat key-value overrides for `*.properties`, Hadoop XML, etc. type: object type: object envOverrides: From 11c014d694d3b9a7e9578fb1f34b59073d9b4d43 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 10 Jun 2026 18:10:55 +0200 Subject: [PATCH 33/35] fix: consolidate ValidatedClusterConfig, use EnvarSet and operator-rs v2 RoleGroupConfig --- .../operator-binary/src/airflow_controller.rs | 65 ++++++------ .../src/config/webserver_config.rs | 15 +-- .../src/controller/build/config_map.rs | 17 ++- .../src/controller/validate.rs | 100 ++++++++++++------ rust/operator-binary/src/env_vars.rs | 17 ++- 5 files changed, 119 insertions(+), 95 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 5caad8b5..270bc4aa 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -118,29 +118,35 @@ pub struct ValidatedRoleConfig { /// Per-rolegroup configuration: the merged CRD config plus overrides. /// -/// `config_overrides` is kept as the typed [`AirflowConfigOverrides`] (role-group merged over -/// role); it is flattened into the rendered config file later, in the build step. This mirrors -/// hdfs-operator. `env_overrides` is already a flat map. +/// This is the generic [`stackable_operator::v2::role_utils::RoleGroupConfig`]: the merged config +/// fragment in `config`, the typed `config_overrides` (role-group merged over role) and the merged +/// `env_overrides`/`cli_overrides`/`pod_overrides`. The config overrides are kept typed +/// ([`AirflowConfigOverrides`]) and flattened into the rendered config file later, in the build step. +pub type AirflowRoleGroupConfig = stackable_operator::v2::role_utils::RoleGroupConfig< + AirflowConfig, + stackable_operator::v2::role_utils::GenericCommonConfig, + AirflowConfigOverrides, +>; + +/// Cluster-wide configuration that applies to every role and role group. +/// +/// Carries the dereferenced external references, so every downstream build step reads them from +/// here rather than from the raw cluster object. #[derive(Clone, Debug)] -pub struct ValidatedRoleGroupConfig { - pub merged_config: AirflowConfig, - pub config_overrides: AirflowConfigOverrides, - pub env_overrides: HashMap, - pub replicas: Option, - pub pod_overrides: PodTemplateSpec, +pub struct ValidatedClusterConfig { + pub executor: AirflowExecutor, + pub authentication_config: AirflowClientAuthenticationDetailsResolved, + pub authorization_config: AirflowAuthorizationResolved, } /// The validated cluster: proves that config merging succeeded for every role and -/// role group before any resources are created. It also carries the dereferenced -/// external references, so every downstream build step reads them from here. +/// role group before any resources are created. #[derive(Clone, Debug)] -pub struct ValidatedAirflowCluster { +pub struct ValidatedCluster { pub image: ResolvedProductImage, - pub role_groups: BTreeMap>, + pub cluster_config: ValidatedClusterConfig, + pub role_groups: BTreeMap>, pub role_configs: BTreeMap, - pub executor: AirflowExecutor, - pub authentication_config: AirflowClientAuthenticationDetailsResolved, - pub authorization_config: AirflowAuthorizationResolved, } #[derive(Snafu, Debug, EnumDiscriminants)] @@ -450,7 +456,7 @@ pub async fn reconcile_airflow( // collection there will be a pod template created to be used for pod provisioning if let AirflowExecutor::KubernetesExecutors { common_configuration, - } = &validated_cluster.executor + } = &validated_cluster.cluster_config.executor { build_executor_template( airflow, @@ -506,11 +512,11 @@ pub async fn reconcile_airflow( let git_sync_resources = git_sync::v1alpha2::GitSyncResources::new( &airflow.spec.cluster_config.dags_git_sync, &validated_cluster.image, - &env_vars_from_overrides(&validated_rg_config.env_overrides), + &Vec::::from(validated_rg_config.env_overrides.clone()), &airflow.volume_mounts(), LOG_VOLUME_NAME, &validated_rg_config - .merged_config + .config .logging .for_container(&Container::GitSync), ) @@ -566,7 +572,7 @@ pub async fn reconcile_airflow( &validated_cluster, &rolegroup, &validated_rg_config.config_overrides, - &validated_rg_config.merged_config.logging, + &validated_rg_config.config.logging, &Container::Airflow, ) .context(BuildConfigMapSnafu)?; @@ -624,7 +630,7 @@ async fn build_executor_template( airflow: &v1alpha2::AirflowCluster, common_config: &AirflowExecutorCommonConfiguration, metadata_database_connection_details: &SqlAlchemyDatabaseConnectionDetails, - validated_cluster: &ValidatedAirflowCluster, + validated_cluster: &ValidatedCluster, cluster_resources: &mut ClusterResources<'_>, client: &stackable_operator::client::Client, rbac_sa: &stackable_operator::k8s_openapi::api::core::v1::ServiceAccount, @@ -671,7 +677,7 @@ async fn build_executor_template( let worker_pod_template_config_map = build_executor_template_config_map( airflow, &validated_cluster.image, - &validated_cluster.authentication_config, + &validated_cluster.cluster_config.authentication_config, metadata_database_connection_details, &rbac_sa.name_unchecked(), &merged_executor_config, @@ -750,10 +756,10 @@ fn listener_ports() -> Vec { #[allow(clippy::too_many_arguments)] fn build_server_rolegroup_statefulset( airflow: &v1alpha2::AirflowCluster, - validated_cluster: &ValidatedAirflowCluster, + validated_cluster: &ValidatedCluster, airflow_role: &AirflowRole, rolegroup_ref: &RoleGroupRef, - validated_rg_config: &ValidatedRoleGroupConfig, + validated_rg_config: &AirflowRoleGroupConfig, metadata_database_connection_details: &SqlAlchemyDatabaseConnectionDetails, celery_database_connection_details: &Option<( CeleryDatabaseConnectionDetails, @@ -762,13 +768,13 @@ fn build_server_rolegroup_statefulset( service_account: &ServiceAccount, git_sync_resources: &git_sync::v1alpha2::GitSyncResources, ) -> Result { - let merged_airflow_config = &validated_rg_config.merged_config; + let merged_airflow_config = &validated_rg_config.config; let env_overrides = &validated_rg_config.env_overrides; let resolved_product_image = &validated_cluster.image; - let authentication_config = &validated_cluster.authentication_config; - let authorization_config = &validated_cluster.authorization_config; - let executor = &validated_cluster.executor; + let authentication_config = &validated_cluster.cluster_config.authentication_config; + let authorization_config = &validated_cluster.cluster_config.authorization_config; + let executor = &validated_cluster.cluster_config.executor; let mut pb = PodBuilder::new(); let recommended_object_labels = build_recommended_labels( @@ -1044,7 +1050,7 @@ fn build_server_rolegroup_statefulset( } .to_string(), ), - replicas: validated_rg_config.replicas.map(i32::from), + replicas: Some(i32::from(validated_rg_config.replicas)), selector: LabelSelector { match_labels: Some(statefulset_match_labels.into()), ..LabelSelector::default() @@ -1123,7 +1129,6 @@ fn build_executor_template_config_map( let mut airflow_container = ContainerBuilder::new(&Container::Base.to_string()).context(InvalidContainerNameSnafu)?; - // Works too, had been changed add_authentication_volumes_and_volume_mounts( authentication_config, &mut airflow_container, diff --git a/rust/operator-binary/src/config/webserver_config.rs b/rust/operator-binary/src/config/webserver_config.rs index 914e2a50..37f5794f 100644 --- a/rust/operator-binary/src/config/webserver_config.rs +++ b/rust/operator-binary/src/config/webserver_config.rs @@ -7,10 +7,7 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::v2::flask_config_writer; use super::{PYTHON_IMPORTS, add_airflow_config}; -use crate::crd::{ - AirflowConfigOptions, authentication::AirflowClientAuthenticationDetailsResolved, - authorization::AirflowAuthorizationResolved, -}; +use crate::{airflow_controller::ValidatedCluster, crd::AirflowConfigOptions}; /// Marks arbitrary Python code to prepend verbatim to the generated file. const CONFIG_OVERRIDE_FILE_HEADER_KEY: &str = "FILE_HEADER"; @@ -35,9 +32,7 @@ pub enum Error { /// resolved authentication/authorization config) with the user's `config_overrides` /// applied last, wrapped by the optional `FILE_HEADER`/`FILE_FOOTER` Python blocks. pub fn build( - authentication_config: &AirflowClientAuthenticationDetailsResolved, - authorization_config: &AirflowAuthorizationResolved, - product_version: &str, + validated_cluster: &ValidatedCluster, config_file_overrides: &BTreeMap, ) -> Result { let mut config: BTreeMap = BTreeMap::new(); @@ -45,9 +40,9 @@ pub fn build( // this will call default values from AirflowClientAuthenticationDetails add_airflow_config( &mut config, - authentication_config, - authorization_config, - product_version, + &validated_cluster.cluster_config.authentication_config, + &validated_cluster.cluster_config.authorization_config, + &validated_cluster.image.product_version, ) .context(ConstructConfigSnafu)?; diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index 80f5205b..b9c4441d 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -15,7 +15,7 @@ use stackable_operator::{ }; use crate::{ - airflow_controller::{AIRFLOW_CONTROLLER_NAME, ValidatedAirflowCluster}, + airflow_controller::{AIRFLOW_CONTROLLER_NAME, ValidatedCluster}, config::webserver_config, crd::{ AIRFLOW_CONFIG_FILENAME, AirflowConfigOverrides, Container, STACKABLE_LOG_DIR, @@ -58,7 +58,7 @@ pub enum Error { /// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator pub fn build_rolegroup_config_map( airflow: &v1alpha2::AirflowCluster, - validated_cluster: &ValidatedAirflowCluster, + validated_cluster: &ValidatedCluster, rolegroup: &RoleGroupRef, config_overrides: &AirflowConfigOverrides, logging: &Logging, @@ -68,15 +68,10 @@ pub fn build_rolegroup_config_map( let config_file_overrides: BTreeMap = config_overrides.webserver_config_py.overrides.clone(); - let config_file = webserver_config::build( - &validated_cluster.authentication_config, - &validated_cluster.authorization_config, - &validated_cluster.image.product_version, - &config_file_overrides, - ) - .with_context(|_| BuildWebserverConfigSnafu { - rolegroup: rolegroup.clone(), - })?; + let config_file = webserver_config::build(validated_cluster, &config_file_overrides) + .with_context(|_| BuildWebserverConfigSnafu { + rolegroup: rolegroup.clone(), + })?; let mut cm_builder = ConfigMapBuilder::new(); diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index dbf16e54..d5c962ba 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeMap; +use std::{collections::BTreeMap, str::FromStr}; use snafu::{ResultExt, Snafu}; use stackable_operator::{ @@ -6,15 +6,18 @@ use stackable_operator::{ config::fragment, kube::ResourceExt, role_utils::{GenericRoleConfig, RoleGroup}, - v2::role_utils::{GenericCommonConfig, with_validated_config}, + v2::{ + builder::pod::container::{EnvVarName, EnvVarSet}, + role_utils::{GenericCommonConfig, RoleGroupConfig, with_validated_config}, + }, }; use strum::IntoEnumIterator; use super::dereference::DereferencedObjects; use crate::{ airflow_controller::{ - CONTAINER_IMAGE_BASE_NAME, ValidatedAirflowCluster, ValidatedRoleConfig, - ValidatedRoleGroupConfig, + AirflowRoleGroupConfig, CONTAINER_IMAGE_BASE_NAME, ValidatedCluster, + ValidatedClusterConfig, ValidatedRoleConfig, }, crd::{ AirflowConfig, AirflowConfigFragment, AirflowConfigOverrides, AirflowRole, AirflowRoleType, @@ -34,13 +37,18 @@ pub enum Error { source: fragment::ValidationError, role_group: String, }, + + #[snafu(display("failed to parse an environment variable override name"))] + ParseEnvVarName { + source: stackable_operator::v2::builder::pod::container::Error, + }, } pub fn validate_cluster( airflow: &v1alpha2::AirflowCluster, image_repository: &str, dereferenced: DereferencedObjects, -) -> Result { +) -> Result { let resolved_product_image = airflow .spec .image @@ -76,10 +84,8 @@ pub fn validate_cluster( let mut group_configs = BTreeMap::new(); for (rolegroup_name, rolegroup) in &resolved_role.role_groups { - let validated = validate_role_group(&resolved_role, rolegroup, &default_config) - .with_context(|_| FailedToResolveConfigSnafu { - role_group: rolegroup_name.clone(), - })?; + let validated = + validate_role_group(&resolved_role, rolegroup_name, rolegroup, &default_config)?; group_configs.insert(rolegroup_name.clone(), validated); } @@ -92,18 +98,21 @@ pub fn validate_cluster( authorization_config, } = dereferenced; - Ok(ValidatedAirflowCluster { + Ok(ValidatedCluster { image: resolved_product_image, + cluster_config: ValidatedClusterConfig { + executor: airflow.spec.executor.clone(), + authentication_config, + authorization_config, + }, role_groups, role_configs, - executor: airflow.spec.executor.clone(), - authentication_config, - authorization_config, }) } /// Validate and merge one role group against its role, via the shared -/// [`with_validated_config`] from `operator-rs`. +/// [`with_validated_config`] from `operator-rs`, returning the generic +/// [`stackable_operator::v2::role_utils::RoleGroupConfig`]. /// /// This performs the full `default → role → role-group` merge of the config fragment (then /// validates it) *and* the role←role-group merge of the overrides in one step. The config @@ -114,23 +123,38 @@ pub fn validate_cluster( /// than unsetting it (config overrides), and env overrides layer role-group on top of role. fn validate_role_group( role: &AirflowRoleType, + role_group_name: &str, rolegroup: &RoleGroup, default_config: &AirflowConfigFragment, -) -> Result { +) -> Result { let validated = with_validated_config::< AirflowConfig, GenericCommonConfig, AirflowConfigFragment, GenericRoleConfig, AirflowConfigOverrides, - >(rolegroup, role, default_config)?; + >(rolegroup, role, default_config) + .with_context(|_| FailedToResolveConfigSnafu { + role_group: role_group_name.to_owned(), + })?; - Ok(ValidatedRoleGroupConfig { - merged_config: validated.config.config, + let mut env_overrides = EnvVarSet::new(); + for (env_var_name, env_var_value) in validated.config.env_overrides { + env_overrides = env_overrides.with_value( + &EnvVarName::from_str(&env_var_name).context(ParseEnvVarNameSnafu)?, + env_var_value, + ); + } + + Ok(RoleGroupConfig { + // Kubernetes defaults to 1 if `replicas` is not set + replicas: validated.replicas.unwrap_or(1), + config: validated.config.config, config_overrides: validated.config.config_overrides, - env_overrides: validated.config.env_overrides, - replicas: validated.replicas, + env_overrides, + cli_overrides: validated.config.cli_overrides, pod_overrides: validated.config.pod_overrides, + product_specific_common_config: validated.config.product_specific_common_config, }) } @@ -138,6 +162,8 @@ fn validate_role_group( mod tests { use std::collections::BTreeMap; + use stackable_operator::k8s_openapi::api::core::v1::EnvVar; + use super::validate_role_group; use crate::crd::{AirflowConfig, AirflowRole, v1alpha2}; @@ -197,10 +223,9 @@ mod tests { let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver); let rolegroup = role.role_groups.get("default").expect("default role group"); - let validated = - validate_role_group(&role, rolegroup, &default_config).expect("validated role group"); + let validated = validate_role_group(&role, "default", rolegroup, &default_config) + .expect("validated role group"); let config_overrides = validated.config_overrides; - let env_overrides = validated.env_overrides; // configOverrides are kept typed. The role-group AUTH_TYPE overrides the role-level one; // both role-only and group-only keys are kept. @@ -213,11 +238,20 @@ mod tests { ]) ); + // env overrides layer role-group on top of role. + let env_overrides: BTreeMap> = + Vec::::from(validated.env_overrides) + .into_iter() + .map(|env_var| (env_var.name, env_var.value)) + .collect(); assert_eq!(env_overrides.len(), 2); - assert_eq!(env_overrides.get("ROLE_ENV_VAR").unwrap(), "role-env-value"); assert_eq!( - env_overrides.get("GROUP_ENV_VAR").unwrap(), - "group-env-value" + env_overrides.get("ROLE_ENV_VAR").unwrap().as_deref(), + Some("role-env-value") + ); + assert_eq!( + env_overrides.get("GROUP_ENV_VAR").unwrap().as_deref(), + Some("group-env-value") ); } @@ -281,8 +315,8 @@ mod tests { let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Scheduler); let rolegroup = role.role_groups.get("default").expect("default role group"); - let validated = - validate_role_group(&role, rolegroup, &default_config).expect("validated role group"); + let validated = validate_role_group(&role, "default", rolegroup, &default_config) + .expect("validated role group"); assert!( validated @@ -291,11 +325,11 @@ mod tests { .overrides .is_empty() ); - assert!(validated.env_overrides.is_empty()); + assert!(Vec::::from(validated.env_overrides).is_empty()); } /// `replicas` and the role←role-group merged `pod_overrides` are produced by - /// `with_validated_config` and must be carried on `ValidatedRoleGroupConfig`, so the build + /// `with_validated_config` and must be carried on `AirflowRoleGroupConfig`, so the build /// step reads them from here rather than re-deriving from the raw cluster. #[test] fn role_group_carries_merged_pod_overrides_and_replicas() { @@ -349,11 +383,11 @@ mod tests { let default_config = AirflowConfig::default_config("airflow", &AirflowRole::Webserver); let rolegroup = role.role_groups.get("default").expect("default role group"); - let validated = - validate_role_group(&role, rolegroup, &default_config).expect("validated role group"); + let validated = validate_role_group(&role, "default", rolegroup, &default_config) + .expect("validated role group"); // replicas is carried through from the role group. - assert_eq!(validated.replicas, Some(3)); + assert_eq!(validated.replicas, 3); // pod_overrides is merged role←role-group (role-group wins on shared keys, both levels' // unique keys survive). diff --git a/rust/operator-binary/src/env_vars.rs b/rust/operator-binary/src/env_vars.rs index 595eb1ec..0b05a1f3 100644 --- a/rust/operator-binary/src/env_vars.rs +++ b/rust/operator-binary/src/env_vars.rs @@ -13,6 +13,7 @@ use stackable_operator::{ k8s_openapi::api::core::v1::EnvVar, kube::ResourceExt, product_logging::framework::create_vector_shutdown_file_command, + v2::builder::pod::container::EnvVarSet, }; use crate::{ @@ -78,7 +79,7 @@ pub enum Error { pub fn build_airflow_statefulset_envs( airflow: &v1alpha2::AirflowCluster, airflow_role: &AirflowRole, - env_overrides: &HashMap, + env_overrides: &EnvVarSet, executor: &AirflowExecutor, auth_config: &AirflowClientAuthenticationDetailsResolved, authorization_config: &AirflowAuthorizationResolved, @@ -261,16 +262,10 @@ pub fn build_airflow_statefulset_envs( _ => {} } - // apply overrides last of all with a fixed ordering - for (k, v) in env_overrides.iter().collect::>() { - env.insert( - k.into(), - EnvVar { - name: k.to_string(), - value: Some(v.to_string()), - ..Default::default() - }, - ); + // apply overrides last of all; `EnvVarSet` is keyed by name, so iteration is already + // in a fixed (sorted-by-name) order + for env_var in env_overrides.clone() { + env.insert(env_var.name.clone(), env_var); } // Needed for the `containerdebug` process to log it's tracing information to. From f0dd42b1acba0af0268a0625c26171bcb7d524ed Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 10 Jun 2026 18:26:50 +0200 Subject: [PATCH 34/35] refactor: add private metadata to ValidatedCluster to use for ownerreferences --- .../operator-binary/src/airflow_controller.rs | 61 ++++++++++++++++++- .../src/controller/build/config_map.rs | 7 +-- .../src/controller/validate.rs | 9 +-- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index 270bc4aa..d54fad1e 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -143,12 +143,71 @@ pub struct ValidatedClusterConfig { /// role group before any resources are created. #[derive(Clone, Debug)] pub struct ValidatedCluster { + /// `ObjectMeta` carrying `name`, `namespace` and `uid`, captured during validation, so this + /// struct can stand in as the owner [`Resource`] for child objects. + metadata: ObjectMeta, pub image: ResolvedProductImage, pub cluster_config: ValidatedClusterConfig, pub role_groups: BTreeMap>, pub role_configs: BTreeMap, } +impl ValidatedCluster { + pub fn new( + airflow: &v1alpha2::AirflowCluster, + image: ResolvedProductImage, + cluster_config: ValidatedClusterConfig, + role_groups: BTreeMap>, + role_configs: BTreeMap, + ) -> Self { + Self { + // Capture only the identity fields needed to own child objects. + metadata: ObjectMeta { + name: Some(airflow.name_any()), + namespace: airflow.namespace(), + uid: airflow.uid(), + ..ObjectMeta::default() + }, + image, + cluster_config, + role_groups, + role_configs, + } + } +} + +/// Lets [`ValidatedCluster`] stand in for the raw [`v1alpha2::AirflowCluster`] when building owner +/// references and metadata for child objects. Kind/group/version are delegated to the CRD; the +/// `metadata` (name, namespace, uid) is captured during validation. +impl Resource for ValidatedCluster { + type DynamicType = ::DynamicType; + type Scope = ::Scope; + + fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::kind(dt) + } + + fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::group(dt) + } + + fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::version(dt) + } + + fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::plural(dt) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata + } +} + #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(IntoStaticStr))] pub enum Error { @@ -568,7 +627,6 @@ pub async fn reconcile_airflow( })?; let rg_configmap = config_map::build_rolegroup_config_map( - airflow, &validated_cluster, &rolegroup, &validated_rg_config.config_overrides, @@ -645,7 +703,6 @@ async fn build_executor_template( }; let rg_configmap = config_map::build_rolegroup_config_map( - airflow, validated_cluster, &rolegroup, // The kubernetes-executor pod template does not apply webserver_config.py overrides diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index b9c4441d..b823ca44 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -57,7 +57,6 @@ pub enum Error { /// The rolegroup [`ConfigMap`] configures the rolegroup based on the configuration given by the administrator pub fn build_rolegroup_config_map( - airflow: &v1alpha2::AirflowCluster, validated_cluster: &ValidatedCluster, rolegroup: &RoleGroupRef, config_overrides: &AirflowConfigOverrides, @@ -78,12 +77,12 @@ pub fn build_rolegroup_config_map( cm_builder .metadata( ObjectMetaBuilder::new() - .name_and_namespace(airflow) + .name_and_namespace(validated_cluster) .name(rolegroup.object_name()) - .ownerreference_from_resource(airflow, None, Some(true)) + .ownerreference_from_resource(validated_cluster, None, Some(true)) .context(ObjectMissingMetadataForOwnerRefSnafu)? .with_recommended_labels(&build_recommended_labels( - airflow, + validated_cluster, AIRFLOW_CONTROLLER_NAME, &validated_cluster.image.app_version_label_value, &rolegroup.role, diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index d5c962ba..c13a738b 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -98,16 +98,17 @@ pub fn validate_cluster( authorization_config, } = dereferenced; - Ok(ValidatedCluster { - image: resolved_product_image, - cluster_config: ValidatedClusterConfig { + Ok(ValidatedCluster::new( + airflow, + resolved_product_image, + ValidatedClusterConfig { executor: airflow.spec.executor.clone(), authentication_config, authorization_config, }, role_groups, role_configs, - }) + )) } /// Validate and merge one role group against its role, via the shared From ae62c32084384e0e0e389fcd843a33146c8dd036 Mon Sep 17 00:00:00 2001 From: Malte Sander Date: Wed, 10 Jun 2026 18:34:37 +0200 Subject: [PATCH 35/35] refactor: move ValidatedCluster structs to controller/mod.rs --- .../operator-binary/src/airflow_controller.rs | 113 +----------------- .../src/config/webserver_config.rs | 2 +- .../src/controller/build/config_map.rs | 3 +- rust/operator-binary/src/controller/mod.rs | 113 ++++++++++++++++++ .../src/controller/validate.rs | 10 +- 5 files changed, 127 insertions(+), 114 deletions(-) diff --git a/rust/operator-binary/src/airflow_controller.rs b/rust/operator-binary/src/airflow_controller.rs index d54fad1e..02e2021a 100644 --- a/rust/operator-binary/src/airflow_controller.rs +++ b/rust/operator-binary/src/airflow_controller.rs @@ -67,18 +67,17 @@ use stackable_operator::{ use strum::{EnumDiscriminants, IntoStaticStr}; use crate::{ - controller::build::config_map, + controller::{AirflowRoleGroupConfig, ValidatedCluster, build::config_map}, controller_commons::{self, CONFIG_VOLUME_NAME, LOG_CONFIG_VOLUME_NAME, LOG_VOLUME_NAME}, crd::{ - self, APP_NAME, AirflowClusterStatus, AirflowConfig, AirflowConfigOverrides, - AirflowExecutor, AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, - ExecutorConfig, HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, - LOG_CONFIG_DIR, METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, - TEMPLATE_LOCATION, TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, + self, APP_NAME, AirflowClusterStatus, AirflowConfigOverrides, AirflowExecutor, + AirflowExecutorCommonConfiguration, AirflowRole, CONFIG_PATH, Container, ExecutorConfig, + HTTP_PORT, HTTP_PORT_NAME, LISTENER_VOLUME_DIR, LISTENER_VOLUME_NAME, LOG_CONFIG_DIR, + METRICS_PORT, METRICS_PORT_NAME, OPERATOR_NAME, STACKABLE_LOG_DIR, TEMPLATE_LOCATION, + TEMPLATE_NAME, TEMPLATE_VOLUME_NAME, authentication::{ AirflowAuthenticationClassResolved, AirflowClientAuthenticationDetailsResolved, }, - authorization::AirflowAuthorizationResolved, build_recommended_labels, internal_secret::{ FERNET_KEY_SECRET_KEY, INTERNAL_SECRET_SECRET_KEY, JWT_SECRET_SECRET_KEY, @@ -108,106 +107,6 @@ pub struct Ctx { pub operator_environment: OperatorEnvironmentOptions, } -/// Per-role configuration extracted during validation. -#[derive(Clone, Debug)] -pub struct ValidatedRoleConfig { - pub pdb: Option, - pub listener_class: Option, - pub group_listener_name: Option, -} - -/// Per-rolegroup configuration: the merged CRD config plus overrides. -/// -/// This is the generic [`stackable_operator::v2::role_utils::RoleGroupConfig`]: the merged config -/// fragment in `config`, the typed `config_overrides` (role-group merged over role) and the merged -/// `env_overrides`/`cli_overrides`/`pod_overrides`. The config overrides are kept typed -/// ([`AirflowConfigOverrides`]) and flattened into the rendered config file later, in the build step. -pub type AirflowRoleGroupConfig = stackable_operator::v2::role_utils::RoleGroupConfig< - AirflowConfig, - stackable_operator::v2::role_utils::GenericCommonConfig, - AirflowConfigOverrides, ->; - -/// Cluster-wide configuration that applies to every role and role group. -/// -/// Carries the dereferenced external references, so every downstream build step reads them from -/// here rather than from the raw cluster object. -#[derive(Clone, Debug)] -pub struct ValidatedClusterConfig { - pub executor: AirflowExecutor, - pub authentication_config: AirflowClientAuthenticationDetailsResolved, - pub authorization_config: AirflowAuthorizationResolved, -} - -/// The validated cluster: proves that config merging succeeded for every role and -/// role group before any resources are created. -#[derive(Clone, Debug)] -pub struct ValidatedCluster { - /// `ObjectMeta` carrying `name`, `namespace` and `uid`, captured during validation, so this - /// struct can stand in as the owner [`Resource`] for child objects. - metadata: ObjectMeta, - pub image: ResolvedProductImage, - pub cluster_config: ValidatedClusterConfig, - pub role_groups: BTreeMap>, - pub role_configs: BTreeMap, -} - -impl ValidatedCluster { - pub fn new( - airflow: &v1alpha2::AirflowCluster, - image: ResolvedProductImage, - cluster_config: ValidatedClusterConfig, - role_groups: BTreeMap>, - role_configs: BTreeMap, - ) -> Self { - Self { - // Capture only the identity fields needed to own child objects. - metadata: ObjectMeta { - name: Some(airflow.name_any()), - namespace: airflow.namespace(), - uid: airflow.uid(), - ..ObjectMeta::default() - }, - image, - cluster_config, - role_groups, - role_configs, - } - } -} - -/// Lets [`ValidatedCluster`] stand in for the raw [`v1alpha2::AirflowCluster`] when building owner -/// references and metadata for child objects. Kind/group/version are delegated to the CRD; the -/// `metadata` (name, namespace, uid) is captured during validation. -impl Resource for ValidatedCluster { - type DynamicType = ::DynamicType; - type Scope = ::Scope; - - fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha2::AirflowCluster::kind(dt) - } - - fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha2::AirflowCluster::group(dt) - } - - fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha2::AirflowCluster::version(dt) - } - - fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { - v1alpha2::AirflowCluster::plural(dt) - } - - fn meta(&self) -> &ObjectMeta { - &self.metadata - } - - fn meta_mut(&mut self) -> &mut ObjectMeta { - &mut self.metadata - } -} - #[derive(Snafu, Debug, EnumDiscriminants)] #[strum_discriminants(derive(IntoStaticStr))] pub enum Error { diff --git a/rust/operator-binary/src/config/webserver_config.rs b/rust/operator-binary/src/config/webserver_config.rs index 37f5794f..21c4c3c5 100644 --- a/rust/operator-binary/src/config/webserver_config.rs +++ b/rust/operator-binary/src/config/webserver_config.rs @@ -7,7 +7,7 @@ use snafu::{ResultExt, Snafu}; use stackable_operator::v2::flask_config_writer; use super::{PYTHON_IMPORTS, add_airflow_config}; -use crate::{airflow_controller::ValidatedCluster, crd::AirflowConfigOptions}; +use crate::{controller::ValidatedCluster, crd::AirflowConfigOptions}; /// Marks arbitrary Python code to prepend verbatim to the generated file. const CONFIG_OVERRIDE_FILE_HEADER_KEY: &str = "FILE_HEADER"; diff --git a/rust/operator-binary/src/controller/build/config_map.rs b/rust/operator-binary/src/controller/build/config_map.rs index b823ca44..c095348d 100644 --- a/rust/operator-binary/src/controller/build/config_map.rs +++ b/rust/operator-binary/src/controller/build/config_map.rs @@ -15,8 +15,9 @@ use stackable_operator::{ }; use crate::{ - airflow_controller::{AIRFLOW_CONTROLLER_NAME, ValidatedCluster}, + airflow_controller::AIRFLOW_CONTROLLER_NAME, config::webserver_config, + controller::ValidatedCluster, crd::{ AIRFLOW_CONFIG_FILENAME, AirflowConfigOverrides, Container, STACKABLE_LOG_DIR, build_recommended_labels, v1alpha2, diff --git a/rust/operator-binary/src/controller/mod.rs b/rust/operator-binary/src/controller/mod.rs index a1196d5d..6902baf0 100644 --- a/rust/operator-binary/src/controller/mod.rs +++ b/rust/operator-binary/src/controller/mod.rs @@ -1,3 +1,116 @@ +use std::collections::BTreeMap; + +use stackable_operator::{ + commons::product_image_selection::ResolvedProductImage, + kube::{Resource, ResourceExt, api::ObjectMeta}, +}; + +use crate::crd::{ + AirflowConfig, AirflowConfigOverrides, AirflowExecutor, AirflowRole, + authentication::AirflowClientAuthenticationDetailsResolved, + authorization::AirflowAuthorizationResolved, v1alpha2, +}; + pub mod build; pub mod dereference; pub mod validate; + +/// Per-role configuration extracted during validation. +#[derive(Clone, Debug)] +pub struct ValidatedRoleConfig { + pub pdb: Option, + pub listener_class: Option, + pub group_listener_name: Option, +} + +/// Per-rolegroup configuration: the merged CRD config plus overrides. +/// +/// This is the generic [`stackable_operator::v2::role_utils::RoleGroupConfig`]: the merged config +/// fragment in `config`, the typed `config_overrides` (role-group merged over role) and the merged +/// `env_overrides`/`cli_overrides`/`pod_overrides`. The config overrides are kept typed +/// ([`AirflowConfigOverrides`]) and flattened into the rendered config file later, in the build step. +pub type AirflowRoleGroupConfig = stackable_operator::v2::role_utils::RoleGroupConfig< + AirflowConfig, + stackable_operator::v2::role_utils::GenericCommonConfig, + AirflowConfigOverrides, +>; + +/// Cluster-wide configuration that applies to every role and role group. +/// +/// Carries the dereferenced external references, so every downstream build step reads them from +/// here rather than from the raw cluster object. +#[derive(Clone, Debug)] +pub struct ValidatedClusterConfig { + pub executor: AirflowExecutor, + pub authentication_config: AirflowClientAuthenticationDetailsResolved, + pub authorization_config: AirflowAuthorizationResolved, +} + +/// The validated cluster: proves that config merging succeeded for every role and +/// role group before any resources are created. +#[derive(Clone, Debug)] +pub struct ValidatedCluster { + /// `ObjectMeta` carrying `name`, `namespace` and `uid`, captured during validation, so this + /// struct can stand in as the owner [`Resource`] for child objects. + metadata: ObjectMeta, + pub image: ResolvedProductImage, + pub cluster_config: ValidatedClusterConfig, + pub role_groups: BTreeMap>, + pub role_configs: BTreeMap, +} + +impl ValidatedCluster { + pub fn new( + airflow: &v1alpha2::AirflowCluster, + image: ResolvedProductImage, + cluster_config: ValidatedClusterConfig, + role_groups: BTreeMap>, + role_configs: BTreeMap, + ) -> Self { + Self { + // Capture only the identity fields needed to own child objects. + metadata: ObjectMeta { + name: Some(airflow.name_any()), + namespace: airflow.namespace(), + uid: airflow.uid(), + ..ObjectMeta::default() + }, + image, + cluster_config, + role_groups, + role_configs, + } + } +} + +/// Lets [`ValidatedCluster`] stand in for the raw [`v1alpha2::AirflowCluster`] when building owner +/// references and metadata for child objects. Kind/group/version are delegated to the CRD; the +/// `metadata` (name, namespace, uid) is captured during validation. +impl Resource for ValidatedCluster { + type DynamicType = ::DynamicType; + type Scope = ::Scope; + + fn kind(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::kind(dt) + } + + fn group(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::group(dt) + } + + fn version(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::version(dt) + } + + fn plural(dt: &Self::DynamicType) -> std::borrow::Cow<'_, str> { + v1alpha2::AirflowCluster::plural(dt) + } + + fn meta(&self) -> &ObjectMeta { + &self.metadata + } + + fn meta_mut(&mut self) -> &mut ObjectMeta { + &mut self.metadata + } +} diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index c13a738b..38c2b94b 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -13,12 +13,12 @@ use stackable_operator::{ }; use strum::IntoEnumIterator; -use super::dereference::DereferencedObjects; +use super::{ + AirflowRoleGroupConfig, ValidatedCluster, ValidatedClusterConfig, ValidatedRoleConfig, + dereference::DereferencedObjects, +}; use crate::{ - airflow_controller::{ - AirflowRoleGroupConfig, CONTAINER_IMAGE_BASE_NAME, ValidatedCluster, - ValidatedClusterConfig, ValidatedRoleConfig, - }, + airflow_controller::CONTAINER_IMAGE_BASE_NAME, crd::{ AirflowConfig, AirflowConfigFragment, AirflowConfigOverrides, AirflowRole, AirflowRoleType, v1alpha2,