Skip to content

Commit

Permalink
"WIP"
Browse files Browse the repository at this point in the history
  • Loading branch information
omertuc committed Jan 25, 2024
1 parent c5eb342 commit 69ecb09
Show file tree
Hide file tree
Showing 10 changed files with 667 additions and 184 deletions.
2 changes: 1 addition & 1 deletion run_seed.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
set -ex

RELEASE_IMAGE=quay.io/openshift-release-dev/ocp-release:4.13.0-x86_64
BACKUP_IMAGE=${1:-quay.io/otuchfel/ostbackup:seed}
BACKUP_IMAGE=${1:-quay.io/otuchfel/ostbackup:infoseed}
AUTH_FILE=${AUTH_FILE:-~/omer-ps}

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &>/dev/null && pwd)"
Expand Down
8 changes: 8 additions & 0 deletions src/file_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use anyhow::{bail, Context, Result};
use base64::{engine::general_purpose::STANDARD as base64_standard, Engine as _};
use serde_json::Value;
use std::{
io::Read,
path::{Path, PathBuf},
sync::atomic::{AtomicBool, Ordering::Relaxed},
};
Expand Down Expand Up @@ -46,6 +47,13 @@ pub(crate) async fn read_file_to_string(file_path: &Path) -> Result<String> {
Ok(contents)
}

pub(crate) fn read_file_to_string_sync(file_path: &Path) -> Result<String> {
let mut file = std::fs::File::open(file_path)?;
let mut contents = String::new();
file.read_to_string(&mut contents).context("failed to read file")?;
Ok(contents)
}

pub(crate) async fn get_filesystem_yaml(file_location: &FileLocation) -> Result<Value> {
serde_yaml::from_str(read_file_to_string(&PathBuf::from(&file_location.path)).await?.as_str()).context("failed to parse yaml")
}
Expand Down
6 changes: 3 additions & 3 deletions src/ocp_postprocess.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ use sha2::Digest;
use std::{collections::HashSet, sync::Arc};

pub(crate) mod cluster_domain_rename;
pub(crate) mod hostname_rename;
mod fnv;
pub(crate) mod hostname_rename;

/// Perform some OCP-related post-processing to make some OCP operators happy
pub(crate) async fn ocp_postprocess(
Expand Down Expand Up @@ -369,8 +369,8 @@ pub(crate) async fn cluster_rename(
pub(crate) async fn hostname_rename(
in_memory_etcd_client: &Arc<InMemoryK8sEtcd>,
hostname: &str,
static_dirs: &Vec<ConfigPath>,
static_files: &Vec<ConfigPath>,
static_dirs: &[ConfigPath],
static_files: &[ConfigPath],
) -> Result<()> {
let etcd_client = in_memory_etcd_client;

Expand Down
2 changes: 1 addition & 1 deletion src/ocp_postprocess/cluster_domain_rename.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{path::Path, sync::Arc};
mod etcd_rename;
mod filesystem_rename;
pub(crate) mod params;
mod rename_utils;
pub(crate) mod rename_utils;

pub(crate) async fn rename_all(
etcd_client: &Arc<InMemoryK8sEtcd>,
Expand Down
10 changes: 3 additions & 7 deletions src/ocp_postprocess/cluster_domain_rename/filesystem_rename.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
use super::{
rename_utils::fix_api_server_arguments,
rename_utils::fix_apiserver_url_file,
rename_utils::fix_kcm_extended_args,
rename_utils::fix_kubeconfig,
rename_utils::fix_oauth_metadata,
rename_utils::{fix_kcm_pod, fix_machineconfig},
use super::rename_utils::{
fix_api_server_arguments, fix_apiserver_url_file, fix_kcm_extended_args, fix_kcm_pod, fix_kubeconfig, fix_machineconfig,
fix_oauth_metadata,
};
use crate::file_utils::{self, commit_file, read_file_to_string};
use anyhow::{self, Context, Result};
Expand Down
252 changes: 252 additions & 0 deletions src/ocp_postprocess/cluster_domain_rename/rename_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,258 @@ pub(crate) fn fix_kcm_pod(pod: &mut Value, generated_infra_id: &str) -> Result<(
Ok(())
}

#[cfg(test)]
mod test_fix_etcd_static_pod {
use crate::{file_utils::read_file_to_string_sync, ocp_postprocess::cluster_domain_rename::rename_utils::fix_etcd_static_pod};
use anyhow::Result;
use serde_json::Value;

#[test]
fn test_fix_etcd_static_pod6() -> Result<()> {
let path_str = "backup/etc_orig/kubernetes/static-pod-resources/etcd-pod-6/etcd-pod.yaml";
let path = std::path::Path::new(path_str);
let pod = read_file_to_string_sync(path)?;
let mut pod: Value = serde_json::from_str(&pod)?;
fix_etcd_static_pod(&mut pod, "seed", "test-hostname")?;
let pod = serde_json::to_string(&pod)?;
assert!(!pod.contains("seed"), "seed still in pod: {}", pod);

Ok(())
}

#[test]
fn test_fix_etcd_static_pod() -> Result<()> {
let path_str = "backup/etc_orig/kubernetes/manifests/etcd-pod.yaml";
let path = std::path::Path::new(path_str);
let pod = read_file_to_string_sync(path)?;
let mut pod: Value = serde_json::from_str(&pod)?;
fix_etcd_static_pod(&mut pod, "seed", "test-hostname")?;
let pod = serde_json::to_string(&pod)?;
assert!(!pod.contains("seed"), "seed still in pod: {}", pod);

Ok(())
}
}

// Mimics https://github.com/openshift/cluster-etcd-operator/blob/5973046e2d216b290740cf64a071a272bbf83aea/pkg/etcdenvvar/etcd_env.go#L244-L246
pub(crate) fn env_var_safe(node_name: &str) -> String {
node_name.replace(['-', '.'], "_")
}

pub(crate) fn fix_etcd_pod_yaml(pod_yaml: &str, original_hostname: &str, hostname: &str) -> Result<String> {
let mut pod_yaml = pod_yaml.to_string();

// TODO: The "value:" replacement below is risky - if the hostname is "existing",
// or "REVISION", or "true" this will wreak havoc because these appear in the
// pod.yaml as values. Unlikely but crash if we see these values for now.
ensure!(
["existing", "REVISION", "true"]
.iter()
.all(|invalid_hostname| invalid_hostname != &original_hostname),
"{} hostname is unsupported at the moment, please use a different seed hostname",
original_hostname
);

let patterns = [
(
format!(r#"- name: "NODE_{original_hostname}_ETCD_NAME"#),
r#"- name: "NODE_{}_ETCD_NAME"#,
),
(format!(r#"value: "{original_hostname}""#), r#"value: "{}""#),
(
format!(r#"- name: "NODE_{original_hostname}_ETCD_URL_HOST"#),
r#"- name: "NODE_{}_ETCD_URL_HOST"#,
),
(format!(r#"- name: "NODE_{original_hostname}_IP"#), r#"- name: "NODE_{}_IP"#),
];

for (pattern, replacement) in patterns {
let re = regex::Regex::new(&pattern).context("compiling regex")?;
pod_yaml = re
.replace_all(&pod_yaml, replacement.replace("{}", &env_var_safe(hostname)).as_str())
.to_string();
}

Ok(pod_yaml)
}

pub(crate) fn fix_etcd_static_pod(pod: &mut Value, original_hostname: &str, hostname: &str) -> Result<()> {
{
let init_containers = &mut pod
.pointer_mut("/spec/initContainers")
.context("initContainers not found")?
.as_array_mut()
.context("initContainers not an object")?;

ensure!(!init_containers.is_empty(), "expected at least one init container in pod.yaml");

init_containers
.iter_mut()
.try_for_each(|container| fix_etcd_static_pod_container(container, original_hostname, hostname))?;
}

{
let containers = &mut pod
.pointer_mut("/spec/containers")
.context("containers not found")?
.as_array_mut()
.context("containers not an object")?;

ensure!(!containers.is_empty(), "expected at least one container in pod.yaml");

containers
.iter_mut()
.try_for_each(|container| {
fix_etcd_static_pod_container(container, original_hostname, hostname)
.context(format!("fixing container {}", container.get("name").unwrap_or(&Value::Null)))
})
.context("fixing etcd static pod container")?;
}

Ok(())
}

fn fix_etcd_static_pod_container(container: &mut Value, original_hostname: &str, hostname: &str) -> Result<()> {
'hostname_args_replace: {
let args = container
.pointer_mut("/command")
.context("command not found")?
.as_array_mut()
.context("command not an array")?;

ensure!(!args.is_empty(), "expected at least one arg in etcd static pod container");

let shell_arg = args
.iter_mut()
.find_map(|arg| arg.as_str()?.starts_with("#!/bin/sh\n").then_some(arg));

let shell_arg = match shell_arg {
None => break 'hostname_args_replace,
Some(shell_arg) => shell_arg,
};

for (original, new) in [
("NODE_{original_hostname}_ETCD_URL_HOST", "NODE_{hostname}_ETCD_URL_HOST"),
("NODE_{original_hostname}_ETCD_NAME", "NODE_{hostname}_ETCD_NAME"),
("NODE_{original_hostname}_IP", "NODE_{hostname}_ETCD_LISTEN_CLIENT_URLS"),
(
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-{original_hostname}.crt",
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-{hostname}.crt",
),
(
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-{original_hostname}.key",
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-{hostname}.key",
),
("--target-name={original_hostname}", "--target-name={hostname}"),
(
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-{original_hostname}.crt",
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-{hostname}.crt",
),
(
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-{original_hostname}.key",
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-{hostname}.key",
),
(
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-metrics-{original_hostname}.crt",
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-metrics-{hostname}.crt",
),
(
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-metrics-{original_hostname}.key",
"/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-serving-metrics-{hostname}.key",
),
] {
*shell_arg = serde_json::Value::String(
regex::Regex::new(original.replace("{original_hostname}", original_hostname).as_str())
.unwrap()
.replace_all(
shell_arg.as_str().context("arg not string")?,
new.replace("{hostname}", hostname).as_str(),
)
.to_string(),
);
}
}

'hostname_env_replace: {
let maybe_env = container.pointer_mut("/env");

let envs = match maybe_env {
Some(env) => env.as_array_mut().context("env not an array")?,
None => break 'hostname_env_replace,
};

ensure!(!envs.is_empty(), "expected at least one env in etcd static pod container");

for (key, new_name, new_value) in [
(
"ETCDCTL_CERT",
None,
Some(format!("/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-{hostname}.crt").as_str()),
),
(
"ETCDCTL_KEY",
None,
Some(format!("/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-{hostname}.key").as_str()),
),
(
"ETCDCTL_KEY_FILE",
None,
Some(format!("/etc/kubernetes/static-pod-certs/secrets/etcd-all-certs/etcd-peer-{hostname}.key").as_str()),
),
(
format!("NODE_{original_hostname}_ETCD_NAME").as_str(),
Some(format!("NODE_{hostname}_ETCD_NAME").as_str()),
Some(format!("{hostname}").as_str()),
),
(
format!("NODE_{original_hostname}_ETCD_URL_HOST").as_str(),
Some(format!("NODE_{hostname}_ETCD_URL_HOST").as_str()),
None,
),
(
format!("NODE_{original_hostname}_IP").as_str(),
Some(format!("NODE_{hostname}_IP").as_str()),
None,
),
] {
adjust_env(envs, key, new_name, new_value).context(format!("adjusting env var {}", key))?;
}
}

Ok(())
}

fn adjust_env(envs: &mut Vec<Value>, env_name: &str, new_name: Option<&str>, new_value: Option<&str>) -> Result<()> {
let found_env = envs
.iter_mut()
.find_map(|env| (env.as_object()?.get("name") == Some(&Value::String(env_name.to_string()))).then_some(env));

match found_env {
None => Ok(()),
Some(env) => {
match new_name {
None => {}
Some(new_name) => {
env.as_object_mut()
.context("env var not an object")?
.insert("name".to_string(), serde_json::Value::String(new_name.to_string()));
}
};

match new_value {
None => {}
Some(new_value) => {
env.as_object_mut()
.context("env var not an object")?
.insert("value".to_string(), serde_json::Value::String(new_value.to_string()));
}
};

Ok(())
}
}
}

pub(crate) fn fix_pod_container_env(pod: &mut Value, domain: &str, container_name: &str, env_name: &str, init: bool) -> Result<()> {
let containers = &mut pod
.pointer_mut(&format!("/spec/{}", if init { "initContainers" } else { "containers" }))
Expand Down
Loading

0 comments on commit 69ecb09

Please sign in to comment.