Skip to content

Instantly share code, notes, and snippets.

@alpeb
Created February 17, 2023 16:58
Show Gist options
  • Save alpeb/74ff4886ab19b48f21ef9344aa8e6f25 to your computer and use it in GitHub Desktop.
Save alpeb/74ff4886ab19b48f21ef9344aa8e6f25 to your computer and use it in GitHub Desktop.
[package]
name = "ext-ns-metadata"
version = "0.1.0"
authors = ["Linkerd Authors <cncf-linkerd-dev@lists.cncf.io>"]
edition = "2021"
license = "Apache-2.0"
publish = false
[dependencies]
anyhow = "1"
serde = "1"
serde_json = "1"
serde_yaml = "0.8"
thiserror = "1"
tracing = "0.1"
[dependencies.clap]
version = "4"
default-features = false
features = ["derive", "env", "std"]
[dependencies.kube]
version = "0.78"
default-features = false
features = ["client", "rustls-tls"]
[dependencies.k8s-openapi]
version = "0.17"
features = ["v1_21"]
[dependencies.tokio]
version = "1"
features = ["io-util", "macros", "net", "rt", "signal", "time"]
use anyhow::Result;
use clap::Parser;
use kube::{
Client,
api::{Api, Patch, PatchParams},
};
use k8s_openapi::api::core::v1::{ConfigMap, Namespace};
use serde::Deserialize;
use thiserror::Error;
use tokio::time;
#[derive(Parser)]
#[clap(version)]
struct Args {
#[arg(long, short = 'n')]
namespace: String,
#[arg(long)]
linkerd_namespace: String,
#[arg(long)]
prometheus_url: Option<String>,
#[arg(long)]
extension: String,
}
#[derive(Debug, Error)]
enum Error {
#[error("data not found")]
DataNotFound,
#[error("values not found")]
ValuesNotFound,
}
#[derive(Deserialize)]
struct ConfigValues {
cni_enabled: bool
}
const LINKERD_CONFIG_CM: &str = "linkerd-config";
const WRITE_TIMEOUT: time::Duration = time::Duration::from_secs(10);
const FIELD_MANAGER: &str = "kubectl-label";
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<()> {
let Args {
namespace,
linkerd_namespace,
prometheus_url,
extension,
} = Args::parse();
tracing::info!("patching namespace {}", namespace);
let mut ops: Vec<serde_json::Value> = Vec::new();
let client: Client = Client::try_default().await?;
let namespace_api: Api<Namespace> = Api::all(client);
let ns: Namespace = namespace_api.get(&namespace).await?;
if ns.metadata.annotations
.filter(|x| !x.is_empty())
.is_none() {
ops.push(serde_json::json!({
"op": "add",
"path": "/metadata/annotations",
"value": {}
}));
}
if ns.metadata.labels
.filter(|x| !x.is_empty())
.is_none() {
ops.push(serde_json::json!({
"op": "add",
"path": "/metadata/labels",
"value": {}
}));
}
prometheus_url
.into_iter()
.for_each(|url| {
ops.push(serde_json::json!({
"op": "add",
"path": "/metadata/annotations/viz.linkerd.io~1external-prometheus",
"value": url,
}));
});
ops.push(serde_json::json!({
"op": "add",
"path": "/metadata/labels/linkerd.io~1extension",
"value": extension,
}));
let level = match cni_enabled(client, &linkerd_namespace).await? {
true => "restricted",
false => "privileged",
};
ops.push(serde_json::json!({
"op": "add",
"path": "/metadata/labels/pod-security.kubernetes.io~1enforce",
"value": level,
}));
let params: PatchParams = PatchParams::apply(FIELD_MANAGER);
match time::timeout(WRITE_TIMEOUT, namespace_api.patch(namespace.as_str(), &params, &Patch::Merge(ops)))
.await?
.map(|| tracing::info!("successfully patched namespace"))
.map_err(|e| {
tracing::error!(%e, "failed patching namespace");
e
})
}
async fn cni_enabled(client: Client, ns: &str) -> Result<bool> {
let cm_api: Api<ConfigMap> = Api::namespaced(client, ns);
let cm: ConfigMap = cm_api
.get(LINKERD_CONFIG_CM)
.await?;
let data = cm.data.ok_or(Error::DataNotFound)?;
let values: &str= data.get("values").ok_or(Error::ValuesNotFound)?;
let config_values: ConfigValues = serde_yaml::from_str(&values)?;
Ok(config_values.cni_enabled)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment