use crate::{
API_SERVER_PORT, Config,
kubectl::Kubectl,
pki::{Identity, Pki},
};
use anyhow::{Context, Result};
use log::{debug, info};
use nix::sys::stat::{Mode, fchmod};
use rayon::prelude::*;
use std::{
fs::{File, create_dir_all},
net::Ipv4Addr,
path::{Path, PathBuf},
};
#[must_use]
pub struct KubeConfig {
kubelets: Vec<PathBuf>,
proxy: PathBuf,
controller_manager: PathBuf,
scheduler: PathBuf,
admin: PathBuf,
}
impl KubeConfig {
pub fn kubelets(&self) -> &[PathBuf] {
&self.kubelets
}
pub fn proxy(&self) -> &Path {
&self.proxy
}
pub fn controller_manager(&self) -> &Path {
&self.controller_manager
}
pub fn scheduler(&self) -> &Path {
&self.scheduler
}
pub fn admin(&self) -> &Path {
&self.admin
}
pub fn new(config: &Config, pki: &Pki) -> Result<KubeConfig> {
let dir = config.root().join("kubeconfig");
if dir.exists() {
info!("Kubeconfig directory already exists, skipping generation");
let kubelets = pki
.kubelets()
.iter()
.map(|i| Self::target_config(&dir, i))
.collect();
Ok(KubeConfig {
kubelets,
proxy: Self::target_config(&dir, pki.proxy()),
controller_manager: Self::target_config(&dir, pki.controller_manager()),
scheduler: Self::target_config(&dir, pki.scheduler()),
admin: Self::target_config(&dir, pki.admin()),
})
} else {
info!("Creating kubeconfigs");
create_dir_all(&dir)?;
let ca = pki.ca().cert();
let identities = [
pki.proxy(),
pki.controller_manager(),
pki.scheduler(),
pki.admin(),
];
let (configs, kubelets) = rayon::join(
|| {
identities
.into_par_iter()
.map(|id| Self::setup_kubeconfig(&dir, id, ca))
.collect::<Vec<_>>()
},
|| {
pki.kubelets()
.par_iter()
.map(|id| Self::setup_kubeconfig(&dir, id, ca))
.collect::<Result<Vec<_>>>()
},
);
let mut it = configs.into_iter();
let proxy = it.next().unwrap()?;
let controller_manager = it.next().unwrap()?;
let scheduler = it.next().unwrap()?;
let admin = it.next().unwrap()?;
debug_assert!(it.next().is_none());
Ok(KubeConfig {
proxy,
controller_manager,
scheduler,
admin,
kubelets: kubelets?,
})
}
}
fn setup_kubeconfig(dir: &Path, identity: &Identity, ca: &Path) -> Result<PathBuf> {
debug!("Creating kubeconfig for {}", identity.name());
let kubeconfig = Self::target_config(dir, identity);
let embed_certs = "--embed-certs=true";
let cluster = "kubernetes";
let kubectl = Kubectl::new(&kubeconfig);
kubectl.config(&[
"set-cluster",
cluster,
&format!("--certificate-authority={}", ca.display()),
&format!("--server=https://{}:{API_SERVER_PORT}", Ipv4Addr::LOCALHOST),
embed_certs,
])?;
kubectl.config(&[
"set-credentials",
identity.user(),
&format!("--client-certificate={}", identity.cert().display()),
&format!("--client-key={}", identity.key().display()),
embed_certs,
])?;
let context = "kubernix";
kubectl.config(&[
"set-context",
context,
&format!("--cluster={}", cluster),
&format!("--user={}", identity.user()),
])?;
kubectl.config(&["use-context", context])?;
let file = File::open(&kubeconfig).context("Unable to open kubeconfig")?;
fchmod(&file, Mode::from_bits_truncate(0o644))
.context("Unable to set kubeconfig permissions")?;
debug!("Kubeconfig created for {}", identity.name());
Ok(kubeconfig)
}
fn target_config(dir: &Path, identity: &Identity) -> PathBuf {
dir.join(format!("{}.kubeconfig", identity.name()))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{config::tests::test_config, network::tests::test_network};
#[test]
fn new_success() -> Result<()> {
let c = test_config()?;
let n = test_network()?;
let p = Pki::new(&c, &n)?;
let _kc = KubeConfig::new(&c, &p)?;
Ok(())
}
}