feat: Introduce K8sAnywhereTopology and refactor Kubernetes interactions
This commit introduces a new topology, `K8sAnywhereTopology`, designed to handle Kubernetes deployments more flexibly. Key changes include: - Introduced `K8sAnywhereTopology` to encapsulate Kubernetes client management and configuration. - Refactored existing Kubernetes-related code to utilize the new topology. - Updated `OcK8sclient` to `K8sclient` across modules (k8s, lamp, deployment, resource) for consistency. - Ensured all relevant modules now interface with Kubernetes through the `K8sclient` trait. This change promotes a more modular and maintainable codebase for Kubernetes integrations within Harmony.
This commit is contained in:
parent
027114c48c
commit
6812d05849
@ -3,6 +3,8 @@ use harmony_macros::ip;
|
||||
use harmony_types::net::MacAddress;
|
||||
|
||||
use crate::executors::ExecutorError;
|
||||
use crate::interpret::InterpretError;
|
||||
use crate::interpret::Outcome;
|
||||
|
||||
use super::DHCPStaticEntry;
|
||||
use super::DhcpServer;
|
||||
@ -15,13 +17,13 @@ use super::IpAddress;
|
||||
use super::LoadBalancer;
|
||||
use super::LoadBalancerService;
|
||||
use super::LogicalHost;
|
||||
use super::OcK8sclient;
|
||||
use super::K8sclient;
|
||||
use super::Router;
|
||||
use super::TftpServer;
|
||||
|
||||
use super::Topology;
|
||||
use super::Url;
|
||||
use super::openshift::OpenshiftClient;
|
||||
use super::k8s::K8sClient;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@ -40,16 +42,20 @@ pub struct HAClusterTopology {
|
||||
pub switch: Vec<LogicalHost>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Topology for HAClusterTopology {
|
||||
fn name(&self) -> &str {
|
||||
todo!()
|
||||
}
|
||||
async fn ensure_ready(&self) -> Result<Outcome, InterpretError> {
|
||||
todo!("ensure_ready, not entirely sure what it should do here, probably something like verify that the hosts are reachable and all services are up and ready.")
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl OcK8sclient for HAClusterTopology {
|
||||
async fn oc_client(&self) -> Result<Arc<OpenshiftClient>, kube::Error> {
|
||||
Ok(Arc::new(OpenshiftClient::try_default().await?))
|
||||
impl K8sclient for HAClusterTopology {
|
||||
async fn k8s_client(&self) -> Result<Arc<K8sClient>, kube::Error> {
|
||||
Ok(Arc::new(K8sClient::try_default().await?))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -2,11 +2,11 @@ use k8s_openapi::NamespaceResourceScope;
|
||||
use kube::{Api, Client, Error, Resource, api::PostParams};
|
||||
use serde::de::DeserializeOwned;
|
||||
|
||||
pub struct OpenshiftClient {
|
||||
pub struct K8sClient {
|
||||
client: Client,
|
||||
}
|
||||
|
||||
impl OpenshiftClient {
|
||||
impl K8sClient {
|
||||
pub async fn try_default() -> Result<Self, Error> {
|
||||
Ok(Self {
|
||||
client: Client::try_default().await?,
|
||||
119
harmony/src/domain/topology/k8s_anywhere.rs
Normal file
119
harmony/src/domain/topology/k8s_anywhere.rs
Normal file
@ -0,0 +1,119 @@
|
||||
use async_trait::async_trait;
|
||||
use log::info;
|
||||
use tokio::sync::OnceCell;
|
||||
|
||||
use crate::interpret::{InterpretError, Outcome};
|
||||
|
||||
use super::{Topology, k8s::K8sClient};
|
||||
|
||||
struct K8sState {
|
||||
client: K8sClient,
|
||||
source: K8sSource,
|
||||
message: String,
|
||||
}
|
||||
|
||||
enum K8sSource {
|
||||
Existing,
|
||||
K3d,
|
||||
// TODO: Add variants for cloud providers like AwsEks, Gke, Aks
|
||||
}
|
||||
|
||||
pub struct K8sAnywhereTopology {
|
||||
k8s_state: OnceCell<Option<K8sState>>,
|
||||
}
|
||||
|
||||
impl K8sAnywhereTopology {
|
||||
async fn try_load_default_kubeconfig(&self) -> Option<K8sClient> {
|
||||
todo!("Use kube-rs default behavior to load system kubeconfig");
|
||||
}
|
||||
|
||||
async fn try_load_kubeconfig(&self, path: &str) -> Option<K8sClient> {
|
||||
todo!("Use kube-rs to load kubeconfig at path {path}");
|
||||
}
|
||||
|
||||
async fn try_install_k3d(&self) -> Result<K8sClient, InterpretError> {
|
||||
todo!(
|
||||
"Create Maestro with LocalDockerTopology or something along these lines and run a K3dInstallationScore on it"
|
||||
)
|
||||
}
|
||||
|
||||
async fn try_get_or_install_k8s_client(&self) -> Result<Option<K8sState>, InterpretError> {
|
||||
let k8s_anywhere_config = K8sAnywhereConfig {
|
||||
kubeconfig: std::env::var("HARMONY_KUBECONFIG")
|
||||
.ok()
|
||||
.map(|v| v.to_string()),
|
||||
use_system_kubeconfig: std::env::var("HARMONY_USE_SYSTEM_KUBECONFIG")
|
||||
.map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)),
|
||||
autoinstall: std::env::var("HARMONY_AUTOINSTALL")
|
||||
.map_or_else(|_| false, |v| v.parse().ok().unwrap_or(false)),
|
||||
};
|
||||
|
||||
if k8s_anywhere_config.use_system_kubeconfig {
|
||||
match self.try_load_default_kubeconfig().await {
|
||||
Some(client) => todo!(),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(kubeconfig) = k8s_anywhere_config.kubeconfig {
|
||||
match self.try_load_kubeconfig(&kubeconfig).await {
|
||||
Some(client) => todo!(),
|
||||
None => todo!(),
|
||||
}
|
||||
}
|
||||
info!("No kubernetes configuration found");
|
||||
|
||||
if !k8s_anywhere_config.autoinstall {
|
||||
info!(
|
||||
"Harmony autoinstallation is not activated, do you wish to launch autoinstallation?"
|
||||
);
|
||||
todo!("Prompt user");
|
||||
}
|
||||
|
||||
match self.try_install_k3d().await {
|
||||
Ok(client) => todo!(),
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct K8sAnywhereConfig {
|
||||
/// The path of the KUBECONFIG file that Harmony should use to interact with the Kubernetes
|
||||
/// cluster
|
||||
///
|
||||
/// Default : None
|
||||
kubeconfig: Option<String>,
|
||||
|
||||
/// Whether to use the system KUBECONFIG, either the environment variable or the file in the
|
||||
/// default or configured location
|
||||
///
|
||||
/// Default : false
|
||||
use_system_kubeconfig: bool,
|
||||
|
||||
/// Whether to install automatically a kubernetes cluster
|
||||
///
|
||||
/// When enabled, autoinstall will setup a K3D cluster on the localhost. https://k3d.io/stable/
|
||||
///
|
||||
/// Default: true
|
||||
autoinstall: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Topology for K8sAnywhereTopology {
|
||||
fn name(&self) -> &str {
|
||||
todo!()
|
||||
}
|
||||
|
||||
async fn ensure_ready(&self) -> Result<Outcome, InterpretError> {
|
||||
match self
|
||||
.k8s_state
|
||||
.get_or_try_init(|| self.try_get_or_install_k8s_client())
|
||||
.await?
|
||||
{
|
||||
Some(k8s_state) => Ok(Outcome::success(k8s_state.message.clone())),
|
||||
None => Err(InterpretError::new(
|
||||
"No K8s client could be found or installed".to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,10 @@
|
||||
mod ha_cluster;
|
||||
mod host_binding;
|
||||
mod http;
|
||||
mod k8s_anywhere;
|
||||
pub use k8s_anywhere::*;
|
||||
mod load_balancer;
|
||||
pub mod openshift;
|
||||
pub mod k8s;
|
||||
mod router;
|
||||
mod tftp;
|
||||
use async_trait::async_trait;
|
||||
|
||||
@ -6,7 +6,7 @@ use serde::Serialize;
|
||||
|
||||
use crate::executors::ExecutorError;
|
||||
|
||||
use super::{IpAddress, LogicalHost, openshift::OpenshiftClient};
|
||||
use super::{IpAddress, LogicalHost, k8s::K8sClient};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DHCPStaticEntry {
|
||||
@ -42,8 +42,8 @@ pub struct NetworkDomain {
|
||||
pub name: String,
|
||||
}
|
||||
#[async_trait]
|
||||
pub trait OcK8sclient: Send + Sync + std::fmt::Debug {
|
||||
async fn oc_client(&self) -> Result<Arc<OpenshiftClient>, kube::Error>;
|
||||
pub trait K8sclient: Send + Sync + std::fmt::Debug {
|
||||
async fn k8s_client(&self) -> Result<Arc<K8sClient>, kube::Error>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
||||
@ -5,7 +5,7 @@ use serde_json::json;
|
||||
use crate::{
|
||||
interpret::Interpret,
|
||||
score::Score,
|
||||
topology::{OcK8sclient, Topology},
|
||||
topology::{K8sclient, Topology},
|
||||
};
|
||||
|
||||
use super::resource::{K8sResourceInterpret, K8sResourceScore};
|
||||
@ -16,7 +16,7 @@ pub struct K8sDeploymentScore {
|
||||
pub image: String,
|
||||
}
|
||||
|
||||
impl<T: Topology + OcK8sclient> Score<T> for K8sDeploymentScore {
|
||||
impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||
let deployment: Deployment = serde_json::from_value(json!(
|
||||
{
|
||||
|
||||
@ -8,7 +8,7 @@ use crate::{
|
||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||
inventory::Inventory,
|
||||
score::Score,
|
||||
topology::{OcK8sclient, Topology},
|
||||
topology::{K8sclient, Topology},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@ -63,7 +63,7 @@ impl<
|
||||
+ Default
|
||||
+ Send
|
||||
+ Sync,
|
||||
T: Topology + OcK8sclient,
|
||||
T: Topology + K8sclient,
|
||||
> Interpret<T> for K8sResourceInterpret<K>
|
||||
where
|
||||
<K as kube::Resource>::DynamicType: Default,
|
||||
@ -74,7 +74,7 @@ where
|
||||
topology: &T,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
topology
|
||||
.oc_client()
|
||||
.k8s_client()
|
||||
.await
|
||||
.expect("Environment should provide enough information to instanciate a client")
|
||||
.apply_namespaced(&self.score.resource)
|
||||
|
||||
@ -9,7 +9,7 @@ use crate::{
|
||||
inventory::Inventory,
|
||||
modules::k8s::deployment::K8sDeploymentScore,
|
||||
score::Score,
|
||||
topology::{OcK8sclient, Topology, Url},
|
||||
topology::{K8sclient, Topology, Url},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
@ -51,7 +51,7 @@ pub struct LAMPInterpret {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Topology + OcK8sclient> Interpret<T> for LAMPInterpret {
|
||||
impl<T: Topology + K8sclient> Interpret<T> for LAMPInterpret {
|
||||
async fn execute(
|
||||
&self,
|
||||
inventory: &Inventory,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user