From abc4ad950f19eeb7e8b4cfff0aa3c1877b5a4de1 Mon Sep 17 00:00:00 2001 From: Omer Tuchfeld Date: Wed, 29 Nov 2023 22:33:28 +0100 Subject: [PATCH] Fix dep annotations This should avoid some rollouts --- src/ocp_postprocess.rs | 112 ++++++++++++++++++++++++++++++++++++- src/ocp_postprocess/fnv.rs | 8 +++ 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 src/ocp_postprocess/fnv.rs diff --git a/src/ocp_postprocess.rs b/src/ocp_postprocess.rs index f9cd5412..e722443f 100644 --- a/src/ocp_postprocess.rs +++ b/src/ocp_postprocess.rs @@ -5,13 +5,17 @@ use crate::{ k8s_etcd::{self, get_etcd_json, put_etcd_yaml}, }; use anyhow::{Context, Result}; -use base64::{engine::general_purpose::STANDARD as base64_standard, Engine as _}; +use base64::{ + engine::general_purpose::{STANDARD as base64_standard, URL_SAFE as base64_url}, + Engine as _, +}; use futures_util::future::join_all; use k8s_etcd::InMemoryK8sEtcd; use sha2::Digest; use std::sync::Arc; pub(crate) mod cluster_domain_rename; +mod fnv; /// Perform some OCP-related post-processing to make some OCP operators happy pub(crate) async fn ocp_postprocess( @@ -38,6 +42,20 @@ pub(crate) async fn ocp_postprocess( .context("renaming cluster")?; } + fix_deployment_dep_annotations( + in_memory_etcd_client, + K8sResourceLocation::new(Some("openshift-apiserver"), "Deployment", "apiserver", "v1"), + ) + .await + .context("fixing dep annotations for openshift-apiserver")?; + + fix_deployment_dep_annotations( + in_memory_etcd_client, + K8sResourceLocation::new(Some("openshift-oauth-apiserver"), "Deployment", "apiserver", "v1"), + ) + .await + .context("fixing dep annotations for openshift-oauth-apiserver")?; + Ok(()) } @@ -92,6 +110,98 @@ pub(crate) async fn fix_olm_secret_hash_annotation(in_memory_etcd_client: &Arc, + k8s_resource_location: K8sResourceLocation, +) -> Result<()> { + let etcd_client = in_memory_etcd_client; + + let mut deployment = get_etcd_json(etcd_client, &k8s_resource_location) + .await? + .context(format!("couldn't find {}", k8s_resource_location))?; + + let metadata_annotations = deployment + .pointer_mut("/metadata/annotations") + .context("no .metadata.annotations")? + .as_object_mut() + .context("annotations not an object")?; + + fix_dep_annotations(metadata_annotations, &k8s_resource_location, etcd_client).await?; + + let spec_template_metadata_annotations = deployment + .pointer_mut("/spec/template/metadata/annotations") + .context("no .spec.template.metadata.annotations")? + .as_object_mut() + .context("pod template annotations not an object")?; + + fix_dep_annotations(spec_template_metadata_annotations, &k8s_resource_location, etcd_client).await?; + + put_etcd_yaml(etcd_client, &k8s_resource_location, deployment).await?; + + Ok(()) +} + +async fn fix_dep_annotations( + annotations: &mut serde_json::Map, + k8s_resource_location: &K8sResourceLocation, + etcd_client: &Arc, +) -> Result<(), anyhow::Error> { + for annotation_key in annotations.keys().cloned().collect::>() { + if !annotation_key.starts_with("operator.openshift.io/dep-") { + continue; + } + + let annotation_parts = annotation_key + .split('/') + .nth(1) + .context("couldn't parse annotation")? + .strip_prefix("dep-") + .context("couldn't parse annotation")? + .split('.') + .collect::>(); + + if annotation_parts.len() != 3 { + // This avoids the operator.openshift.io/dep-desired.generation annotation + continue; + } + + let resource_k8s_resource_location = K8sResourceLocation::new( + Some(annotation_parts[0]), + match annotation_parts[2] { + "secret" => "secret", + "configmap" => "ConfigMap", + kind => { + log::warn!( + "unsupported resource kind {} in annotation {} at {}", + kind, + annotation_key, + k8s_resource_location + ); + continue; + } + }, + annotation_parts[1], + "v1", + ); + + let data_json = &serde_json::to_string( + get_etcd_json(etcd_client, &resource_k8s_resource_location) + .await? + .context(format!("couldn't find {}", resource_k8s_resource_location))? + .pointer("/data") + .context("no .data")?, + ) + .context("couldn't serialize data")?; + + annotations.insert( + annotation_key, + serde_json::Value::String(base64_url.encode(fnv::fnv1_32((format!("{}\n", data_json)).as_bytes()).to_be_bytes())), + ); + } + + Ok(()) +} + /// These kubeconfigs nested inside a secret are far too complicated to handle in recert, so we /// just delete them and hope that a reconcile will take care of them. pub(crate) async fn delete_node_kubeconfigs(in_memory_etcd_client: &Arc) -> Result<()> { diff --git a/src/ocp_postprocess/fnv.rs b/src/ocp_postprocess/fnv.rs new file mode 100644 index 00000000..fe44616d --- /dev/null +++ b/src/ocp_postprocess/fnv.rs @@ -0,0 +1,8 @@ +pub(crate) fn fnv1_32(data: &[u8]) -> u32 { + let mut hash = 0x811c9dc5u32; + for byte in data { + hash = hash.wrapping_mul(0x01000193); + hash ^= u32::from(*byte); + } + hash +}