feat: implement k3d cluster management
- Adds functionality to download, install, and manage k3d clusters. - Includes methods for downloading the latest release, creating clusters, and verifying cluster existence. - Implements `ensure_k3d_installed`, `get_latest_release_tag`, `download_latest_release`, `is_k3d_installed`, `verify_cluster_exists`, `create_cluster` and `create_kubernetes_client`. - Provides a `get_client` method to access the Kubernetes client. - Includes unit tests for download and installation. - Adds handling for different operating systems. - Improves error handling and logging. - Introduces a `K3d` struct to encapsulate k3d cluster management logic. - Adds the ability to specify the cluster name during K3d initialization.
This commit is contained in:
@@ -33,3 +33,6 @@ serde-value = { workspace = true }
|
||||
inquire.workspace = true
|
||||
helm-wrapper-rs = "0.4.0"
|
||||
non-blank-string-rs = "1.0.4"
|
||||
k3d-rs = { path = "../k3d" }
|
||||
directories = "6.0.0"
|
||||
lazy_static = "1.5.0"
|
||||
|
||||
9
harmony/src/domain/config.rs
Normal file
9
harmony/src/domain/config.rs
Normal file
@@ -0,0 +1,9 @@
|
||||
use lazy_static::lazy_static;
|
||||
use std::path::PathBuf;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref HARMONY_CONFIG_DIR: PathBuf = directories::BaseDirs::new()
|
||||
.unwrap()
|
||||
.data_dir()
|
||||
.join("harmony");
|
||||
}
|
||||
@@ -52,27 +52,6 @@ impl<T: Topology> Maestro<T> {
|
||||
Ok(outcome)
|
||||
}
|
||||
|
||||
// Load the inventory and inventory from environment.
|
||||
// This function is able to discover the context that it is running in, such as k8s clusters, aws cloud, linux host, etc.
|
||||
// When the HARMONY_TOPOLOGY environment variable is not set, it will default to install k3s
|
||||
// locally (lazily, if not installed yet, when the first execution occurs) and use that as a topology
|
||||
// So, by default, the inventory is a single host that the binary is running on, and the
|
||||
// topology is a single node k3s
|
||||
//
|
||||
// By default :
|
||||
// - Linux => k3s
|
||||
// - macos, windows => docker compose
|
||||
//
|
||||
// To run more complex cases like OKDHACluster, either provide the default target in the
|
||||
// harmony infrastructure as code or as an environment variable
|
||||
pub fn load_from_env() -> Self {
|
||||
// Load env var HARMONY_TOPOLOGY
|
||||
match std::env::var("HARMONY_TOPOLOGY") {
|
||||
Ok(_) => todo!(),
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_all(&mut self, mut scores: ScoreVec<T>) {
|
||||
let mut score_mut = self.scores.write().expect("Should acquire lock");
|
||||
score_mut.append(&mut scores);
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod config;
|
||||
pub mod data;
|
||||
pub mod executors;
|
||||
pub mod filter;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use derive_new::new;
|
||||
use k8s_openapi::NamespaceResourceScope;
|
||||
use kube::{Api, Client, Error, Resource, api::PostParams};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
#[derive(new)]
|
||||
pub struct K8sClient {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
@@ -61,13 +61,15 @@ impl K8sAnywhereTopology {
|
||||
todo!("Use kube-rs to load kubeconfig at path {path}");
|
||||
}
|
||||
|
||||
async fn try_install_k3d(&self) -> Result<K8sClient, InterpretError> {
|
||||
fn get_k3d_installation_score(&self) -> K3DInstallationScore {
|
||||
K3DInstallationScore::default()
|
||||
}
|
||||
|
||||
async fn try_install_k3d(&self) -> Result<(), InterpretError> {
|
||||
let maestro = Maestro::initialize(Inventory::autoload(), LocalhostTopology::new()).await?;
|
||||
let k3d_score = K3DInstallationScore::new();
|
||||
let k3d_score = self.get_k3d_installation_score();
|
||||
maestro.interpret(Box::new(k3d_score)).await?;
|
||||
todo!(
|
||||
"Create Maestro with LocalDockerTopology or something along these lines and run a K3dInstallationScore on it"
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn try_get_or_install_k8s_client(&self) -> Result<Option<K8sState>, InterpretError> {
|
||||
@@ -112,9 +114,14 @@ impl K8sAnywhereTopology {
|
||||
}
|
||||
|
||||
info!("Starting K8sAnywhere installation");
|
||||
match self.try_install_k3d().await {
|
||||
self.try_install_k3d().await?;
|
||||
let k3d_score = self.get_k3d_installation_score();
|
||||
match k3d_rs::K3d::new(k3d_score.installation_path, Some(k3d_score.cluster_name))
|
||||
.get_client()
|
||||
.await
|
||||
{
|
||||
Ok(client) => Ok(Some(K8sState {
|
||||
_client: client,
|
||||
_client: K8sClient::new(client),
|
||||
_source: K8sSource::LocalK3d,
|
||||
message: "Successfully installed K3D cluster and acquired client".to_string(),
|
||||
})),
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::info;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::{
|
||||
config::HARMONY_CONFIG_DIR,
|
||||
data::{Id, Version},
|
||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||
inventory::Inventory,
|
||||
@@ -10,26 +14,25 @@ use crate::{
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct K3DInstallationScore {}
|
||||
pub struct K3DInstallationScore {
|
||||
pub installation_path: PathBuf,
|
||||
pub cluster_name: String,
|
||||
}
|
||||
|
||||
impl K3DInstallationScore {
|
||||
pub fn new() -> Self {
|
||||
Self {}
|
||||
impl Default for K3DInstallationScore {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
installation_path: HARMONY_CONFIG_DIR.join("k3d"),
|
||||
cluster_name: "harmony".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Topology> Score<T> for K3DInstallationScore {
|
||||
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
||||
todo!("
|
||||
1. Decide if I create a new crate for k3d management, especially to avoid the ocrtograb dependency
|
||||
2. Implement k3d management
|
||||
3. Find latest tag
|
||||
4. Download k3d to some path managed by harmony (or not?)
|
||||
5. Bootstrap cluster
|
||||
6. Get kubeconfig
|
||||
7. Load kubeconfig in k8s anywhere
|
||||
8. Complete k8sanywhere setup
|
||||
")
|
||||
Box::new(K3dInstallationInterpret {
|
||||
score: self.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
@@ -38,7 +41,9 @@ impl<T: Topology> Score<T> for K3DInstallationScore {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct K3dInstallationInterpret {}
|
||||
pub struct K3dInstallationInterpret {
|
||||
score: K3DInstallationScore,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Topology> Interpret<T> for K3dInstallationInterpret {
|
||||
@@ -47,7 +52,20 @@ impl<T: Topology> Interpret<T> for K3dInstallationInterpret {
|
||||
_inventory: &Inventory,
|
||||
_topology: &T,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
todo!()
|
||||
let k3d = k3d_rs::K3d::new(
|
||||
self.score.installation_path.clone(),
|
||||
Some(self.score.cluster_name.clone()),
|
||||
);
|
||||
match k3d.ensure_installed().await {
|
||||
Ok(_client) => {
|
||||
let msg = format!("k3d cluster {} is installed ", self.score.cluster_name);
|
||||
info!("{msg}");
|
||||
Ok(Outcome::success(msg))
|
||||
}
|
||||
Err(msg) => Err(InterpretError::new(format!(
|
||||
"K3dInstallationInterpret failed to ensure k3d is installed : {msg}"
|
||||
))),
|
||||
}
|
||||
}
|
||||
fn get_name(&self) -> InterpretName {
|
||||
InterpretName::K3dInstallation
|
||||
|
||||
Reference in New Issue
Block a user