|
| 1 | +//! Vendored variant of `stackable_operator::v2::role_utils` from the |
| 2 | +//! `smooth-operator` branch, with simplifications appropriate for |
| 3 | +//! zookeeper-operator. |
| 4 | +//! |
| 5 | +//! Differences from upstream: |
| 6 | +//! - `env_overrides` is `BTreeMap<String, String>` instead of `EnvVarSet`. |
| 7 | +//! - No `cli_overrides_to_vec` helper, `ResourceNames`, or service-account helpers. |
| 8 | +//! - The `CommonConfig` (a.k.a. `product_specific_common_config`) does NOT need to |
| 9 | +//! implement `Merge`. ZooKeeper uses `JavaCommonConfig`, which intentionally does |
| 10 | +//! not implement `Merge` because its inner `JvmArgumentOverrides::try_merge` is |
| 11 | +//! fallible (regex validation). The `RoleGroupConfig::product_specific_common_config` |
| 12 | +//! field here simply carries the role-group level value through. |
| 13 | +//! |
| 14 | +//! Replace with `stackable_operator::v2::role_utils::*` once upstream publishes |
| 15 | +//! the module. |
| 16 | +
|
| 17 | +use std::collections::BTreeMap; |
| 18 | + |
| 19 | +use serde::Serialize; |
| 20 | +use stackable_operator::{ |
| 21 | + config::{ |
| 22 | + fragment::{self, FromFragment}, |
| 23 | + merge::{Merge, merge}, |
| 24 | + }, |
| 25 | + k8s_openapi::{DeepMerge, api::core::v1::PodTemplateSpec}, |
| 26 | + role_utils::{Role, RoleGroup}, |
| 27 | + schemars::JsonSchema, |
| 28 | +}; |
| 29 | + |
| 30 | +/// A validated, merged view of a `RoleGroup`. |
| 31 | +/// |
| 32 | +/// Mirrors `stackable_operator::v2::role_utils::RoleGroupConfig` on the |
| 33 | +/// `smooth-operator` branch, with `env_overrides: BTreeMap<String, String>` |
| 34 | +/// instead of the upstream `EnvVarSet`. |
| 35 | +#[derive(Clone, Debug, PartialEq)] |
| 36 | +pub struct RoleGroupConfig<Config, CommonConfig, ConfigOverrides> { |
| 37 | + pub replicas: u16, |
| 38 | + pub config: Config, |
| 39 | + pub config_overrides: ConfigOverrides, |
| 40 | + pub env_overrides: BTreeMap<String, String>, |
| 41 | + pub cli_overrides: BTreeMap<String, String>, |
| 42 | + pub pod_overrides: PodTemplateSpec, |
| 43 | + pub product_specific_common_config: CommonConfig, |
| 44 | +} |
| 45 | + |
| 46 | +/// Merges and validates the `RoleGroup` with the given `role` and `default_config`, |
| 47 | +/// returning a `RoleGroupConfig`. |
| 48 | +/// |
| 49 | +/// Merge order matches `with_validated_config` on `smooth-operator`: |
| 50 | +/// - `Config` (Fragment): `default_config <- role.config <- rg.config` via `Merge::merge`, |
| 51 | +/// then validated to `ValidatedConfig` via `FromFragment`. |
| 52 | +/// - `ConfigOverrides`: `role.config_overrides <- rg.config_overrides` via `Merge::merge`. |
| 53 | +/// - `env_overrides` / `cli_overrides`: `extend` (rg keys overwrite role keys). |
| 54 | +/// - `pod_overrides`: `DeepMerge::merge_from` (rg overrides role). |
| 55 | +/// - `product_specific_common_config`: passes through the role-group level value |
| 56 | +/// (see module docs for rationale). |
| 57 | +pub fn with_validated_config<ValidatedConfig, CommonConfig, Config, RoleConfig, ConfigOverrides>( |
| 58 | + role_group: &RoleGroup<Config, CommonConfig, ConfigOverrides>, |
| 59 | + role: &Role<Config, ConfigOverrides, RoleConfig, CommonConfig>, |
| 60 | + default_config: &Config, |
| 61 | +) -> Result< |
| 62 | + RoleGroupConfig<ValidatedConfig, CommonConfig, ConfigOverrides>, |
| 63 | + fragment::ValidationError, |
| 64 | +> |
| 65 | +where |
| 66 | + ValidatedConfig: FromFragment<Fragment = Config>, |
| 67 | + CommonConfig: Clone + Default + JsonSchema + Serialize, |
| 68 | + Config: Clone + Merge, |
| 69 | + RoleConfig: Default + JsonSchema + Serialize, |
| 70 | + ConfigOverrides: Clone + Default + JsonSchema + Merge + Serialize, |
| 71 | +{ |
| 72 | + let validated_config = validate_config(role_group, role, default_config)?; |
| 73 | + Ok(RoleGroupConfig { |
| 74 | + replicas: role_group.replicas.unwrap_or(1), |
| 75 | + config: validated_config, |
| 76 | + config_overrides: merged_config_overrides( |
| 77 | + &role.config.config_overrides, |
| 78 | + role_group.config.config_overrides.clone(), |
| 79 | + ), |
| 80 | + env_overrides: merged_env_overrides( |
| 81 | + role.config |
| 82 | + .env_overrides |
| 83 | + .iter() |
| 84 | + .map(|(k, v)| (k.clone(), v.clone())) |
| 85 | + .collect(), |
| 86 | + role_group |
| 87 | + .config |
| 88 | + .env_overrides |
| 89 | + .iter() |
| 90 | + .map(|(k, v)| (k.clone(), v.clone())) |
| 91 | + .collect(), |
| 92 | + ), |
| 93 | + cli_overrides: merged_cli_overrides( |
| 94 | + role.config.cli_overrides.clone(), |
| 95 | + role_group.config.cli_overrides.clone(), |
| 96 | + ), |
| 97 | + pod_overrides: merged_pod_overrides( |
| 98 | + role.config.pod_overrides.clone(), |
| 99 | + role_group.config.pod_overrides.clone(), |
| 100 | + ), |
| 101 | + product_specific_common_config: role_group.config.product_specific_common_config.clone(), |
| 102 | + }) |
| 103 | +} |
| 104 | + |
| 105 | +fn validate_config<ValidatedConfig, CommonConfig, Config, RoleConfig, ConfigOverrides>( |
| 106 | + role_group: &RoleGroup<Config, CommonConfig, ConfigOverrides>, |
| 107 | + role: &Role<Config, ConfigOverrides, RoleConfig, CommonConfig>, |
| 108 | + default_config: &Config, |
| 109 | +) -> Result<ValidatedConfig, fragment::ValidationError> |
| 110 | +where |
| 111 | + ValidatedConfig: FromFragment<Fragment = Config>, |
| 112 | + CommonConfig: Default + JsonSchema + Serialize, |
| 113 | + Config: Clone + Merge, |
| 114 | + RoleConfig: Default + JsonSchema + Serialize, |
| 115 | + ConfigOverrides: Default + JsonSchema + Serialize, |
| 116 | +{ |
| 117 | + role_group.validate_config(role, default_config) |
| 118 | +} |
| 119 | + |
| 120 | +fn merged_config_overrides<ConfigOverrides>( |
| 121 | + role_config_overrides: &ConfigOverrides, |
| 122 | + role_group_config_overrides: ConfigOverrides, |
| 123 | +) -> ConfigOverrides |
| 124 | +where |
| 125 | + ConfigOverrides: Merge, |
| 126 | +{ |
| 127 | + merge(role_group_config_overrides, role_config_overrides) |
| 128 | +} |
| 129 | + |
| 130 | +fn merged_env_overrides( |
| 131 | + role_env_overrides: BTreeMap<String, String>, |
| 132 | + role_group_env_overrides: BTreeMap<String, String>, |
| 133 | +) -> BTreeMap<String, String> { |
| 134 | + let mut merged = role_env_overrides; |
| 135 | + merged.extend(role_group_env_overrides); |
| 136 | + merged |
| 137 | +} |
| 138 | + |
| 139 | +fn merged_cli_overrides( |
| 140 | + role_cli_overrides: BTreeMap<String, String>, |
| 141 | + role_group_cli_overrides: BTreeMap<String, String>, |
| 142 | +) -> BTreeMap<String, String> { |
| 143 | + let mut merged = role_cli_overrides; |
| 144 | + merged.extend(role_group_cli_overrides); |
| 145 | + merged |
| 146 | +} |
| 147 | + |
| 148 | +fn merged_pod_overrides( |
| 149 | + role_pod_overrides: PodTemplateSpec, |
| 150 | + role_group_pod_overrides: PodTemplateSpec, |
| 151 | +) -> PodTemplateSpec { |
| 152 | + let mut merged = role_pod_overrides; |
| 153 | + merged.merge_from(role_group_pod_overrides); |
| 154 | + merged |
| 155 | +} |
0 commit comments