Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ All notable changes to this project will be documented in this file.
- Set `maxSurge=1` and `maxUnavailable=0` on the OPA DaemonSet rolling update strategy to eliminate
availability gaps during rolling updates ([#819]).
- Document Helm deployed RBAC permissions and remove unnecessary permissions ([#820]).
- Internal operator refactoring: introduce dereference() and validate() steps in the reconciler ([#836]).

[#818]: https://github.com/stackabletech/opa-operator/pull/818
[#819]: https://github.com/stackabletech/opa-operator/pull/819
[#820]: https://github.com/stackabletech/opa-operator/pull/820
[#830]: https://github.com/stackabletech/opa-operator/pull/830
[#831]: https://github.com/stackabletech/opa-operator/pull/831
[#836]: https://github.com/stackabletech/opa-operator/pull/836

## [26.3.0] - 2026-03-16

Expand Down
18 changes: 9 additions & 9 deletions Cargo.nix

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 9 additions & 9 deletions crate-hashes.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

85 changes: 22 additions & 63 deletions rust/operator-binary/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ use stackable_operator::{
cli::OperatorEnvironmentOptions,
cluster_resources::{ClusterResourceApplyStrategy, ClusterResources},
commons::{
product_image_selection::{self, ResolvedProductImage},
product_image_selection::ResolvedProductImage,
rbac::build_rbac_resources,
secret_class::{
SecretClassVolume, SecretClassVolumeProvisionParts, SecretClassVolumeScope,
Expand Down Expand Up @@ -54,7 +54,6 @@ use stackable_operator::{
kvp::{LabelError, Labels, ObjectLabels},
logging::controller::ReconcilerError,
memory::{BinaryMultiple, MemoryQuantity},
product_config_utils::{transform_all_roles_to_config, validate_all_roles_and_groups_config},
product_logging::{
self,
framework::{
Expand Down Expand Up @@ -89,6 +88,8 @@ use crate::{
},
};

mod validate;

pub const OPA_CONTROLLER_NAME: &str = "opacluster";
pub const OPA_FULL_CONTROLLER_NAME: &str = concatcp!(OPA_CONTROLLER_NAME, '.', OPERATOR_NAME);

Expand Down Expand Up @@ -230,11 +231,6 @@ pub enum Error {
source: stackable_operator::client::Error,
},

#[snafu(display("invalid product config"))]
InvalidProductConfig {
source: stackable_operator::product_config_utils::Error,
},

#[snafu(display("object is missing metadata to build owner reference"))]
ObjectMissingMetadataForOwnerRef {
source: stackable_operator::builder::meta::Error,
Expand All @@ -248,11 +244,6 @@ pub enum Error {
source: stackable_operator::cluster_resources::Error,
},

#[snafu(display("failed to transform configs"))]
ProductConfigTransform {
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 },

Expand Down Expand Up @@ -332,14 +323,12 @@ pub enum Error {
source: builder::pod::container::Error,
},

#[snafu(display("failed to resolve product image"))]
ResolveProductImage {
source: product_image_selection::Error,
},

#[snafu(display("failed to build service"))]
BuildService { source: service::Error },

#[snafu(display("failed to validate cluster"))]
ValidateCluster { source: validate::Error },

#[snafu(display("failed to build TLS volume"))]
TlsVolumeBuild {
source: builder::pod::volume::SecretOperatorVolumeSourceBuilderError,
Expand Down Expand Up @@ -448,15 +437,12 @@ pub async fn reconcile_opa(
let opa_ref = ObjectRef::from_obj(opa);

let client = &ctx.client;
let resolved_product_image = opa
.spec
.image
.resolve(
CONTAINER_IMAGE_BASE_NAME,
&ctx.operator_environment.image_repository,
crate::built_info::PKG_VERSION,
)
.context(ResolveProductImageSnafu)?;

// NOTE(@maltesander): There currently is no dereference (client required) step for OPA.
// validate (no client required)
let validated = validate::validate(opa, &ctx.operator_environment, &ctx.product_config)
.context(ValidateClusterSnafu)?;

let opa_role = OpaRole::Server;

let mut cluster_resources = ClusterResources::new(
Expand All @@ -469,36 +455,14 @@ pub async fn reconcile_opa(
)
.context(FailedToCreateClusterResourcesSnafu)?;

let validated_config = validate_all_roles_and_groups_config(
&resolved_product_image.product_version,
&transform_all_roles_to_config(
opa,
&[(
opa_role.to_string(),
(
vec![
PropertyNameKind::File(CONFIG_FILE.to_string()),
PropertyNameKind::Env,
PropertyNameKind::Cli,
],
opa.spec.servers.clone(),
),
)]
.into(),
)
.context(ProductConfigTransformSnafu)?,
&ctx.product_config,
false,
false,
)
.context(InvalidProductConfigSnafu)?;
let role_server_config = validated_config
let role_server_config = validated
.validated_role_config
.get(&opa_role.to_string())
.map(Cow::Borrowed)
.unwrap_or_default();

let server_role_service =
build_server_role_service(opa, &resolved_product_image).context(BuildServiceSnafu)?;
build_server_role_service(opa, &validated.image).context(BuildServiceSnafu)?;
// required for discovery config map later
let server_role_service = cluster_resources
.add(client, server_role_service)
Expand Down Expand Up @@ -534,20 +498,15 @@ pub async fn reconcile_opa(
.merged_config(&opa_role, &rolegroup)
.context(FailedToResolveConfigSnafu)?;

let rg_configmap = build_server_rolegroup_config_map(
opa,
&resolved_product_image,
&rolegroup,
&merged_config,
)?;
let rg_service = build_rolegroup_headless_service(opa, &resolved_product_image, &rolegroup)
let rg_configmap =
build_server_rolegroup_config_map(opa, &validated.image, &rolegroup, &merged_config)?;
let rg_service = build_rolegroup_headless_service(opa, &validated.image, &rolegroup)
.context(BuildServiceSnafu)?;
let rg_metrics_service = build_rolegroup_metrics_service(opa, &validated.image, &rolegroup)
.context(BuildServiceSnafu)?;
let rg_metrics_service =
build_rolegroup_metrics_service(opa, &resolved_product_image, &rolegroup)
.context(BuildServiceSnafu)?;
let rg_daemonset = build_server_rolegroup_daemonset(
opa,
&resolved_product_image,
&validated.image,
&opa_role,
&rolegroup,
rolegroup_config,
Expand Down Expand Up @@ -610,7 +569,7 @@ pub async fn reconcile_opa(
for discovery_cm in build_discovery_configmaps(
opa,
opa,
&resolved_product_image,
&validated.image,
&server_role_service,
&client.kubernetes_cluster_info,
)
Expand Down
89 changes: 89 additions & 0 deletions rust/operator-binary/src/controller/validate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
//! The validate step in the OpaCluster controller
//!
//! Synchronously validates inputs that don't require a Kubernetes client. Produces
//! [`ValidatedInputs`], consumed by the rest of `reconcile_opa`.

use product_config::{ProductConfigManager, types::PropertyNameKind};
use snafu::{ResultExt, Snafu};
use stackable_operator::{
cli::OperatorEnvironmentOptions,
commons::product_image_selection::{self, ResolvedProductImage},
product_config_utils::{
ValidatedRoleConfigByPropertyKind, transform_all_roles_to_config,
validate_all_roles_and_groups_config,
},
};

use crate::crd::{OpaRole, v1alpha2};

#[derive(Snafu, Debug)]
pub enum Error {
#[snafu(display("failed to resolve product image"))]
ResolveProductImage {
source: product_image_selection::Error,
},

#[snafu(display("failed to transform configs"))]
ProductConfigTransform {
source: stackable_operator::product_config_utils::Error,
},

#[snafu(display("invalid product config"))]
InvalidProductConfig {
source: stackable_operator::product_config_utils::Error,
},
}

type Result<T, E = Error> = std::result::Result<T, E>;

/// Synchronous inputs the rest of `reconcile_opa` needs after dereferencing.
pub struct ValidatedInputs {
pub image: ResolvedProductImage,
pub validated_role_config: ValidatedRoleConfigByPropertyKind,
}

/// Validates the cluster spec and the dereferenced inputs.
pub fn validate(
opa: &v1alpha2::OpaCluster,
operator_environment: &OperatorEnvironmentOptions,
product_config: &ProductConfigManager,
) -> Result<ValidatedInputs> {
let image = opa
.spec
.image
.resolve(
super::CONTAINER_IMAGE_BASE_NAME,
&operator_environment.image_repository,
crate::built_info::PKG_VERSION,
)
.context(ResolveProductImageSnafu)?;

let validated_role_config = validate_all_roles_and_groups_config(
&image.product_version,
&transform_all_roles_to_config(
opa,
&[(
OpaRole::Server.to_string(),
(
vec![
PropertyNameKind::File(super::CONFIG_FILE.to_string()),
PropertyNameKind::Env,
PropertyNameKind::Cli,
],
opa.spec.servers.clone(),
),
)]
.into(),
)
.context(ProductConfigTransformSnafu)?,
product_config,
false,
false,
)
.context(InvalidProductConfigSnafu)?;

Ok(ValidatedInputs {
image,
validated_role_config,
})
}
Loading
Loading