feat(k8s): add Kubernetes deployment resource handling

Introduce new modules to handle Kubernetes resources specifically focusing on Deployment resources. Added `K8sResource` and `K8sDeployment` structs along with necessary traits implementations for interpretation and execution in the inventory system. Also, fixed module reordering issues in opnsense-config-xml and corrected some fields types within its data structures.
This commit is contained in:
Jean-Gabriel Gill-Couture 2025-01-24 10:44:27 -05:00
parent caec71f06d
commit d6c8650d52
14 changed files with 245 additions and 53 deletions

4
harmony-rs/Cargo.lock generated
View File

@ -931,6 +931,9 @@ dependencies = [
"env_logger", "env_logger",
"harmony_macros", "harmony_macros",
"harmony_types", "harmony_types",
"http 1.2.0",
"k8s-openapi",
"kube",
"libredfish", "libredfish",
"log", "log",
"opnsense-config", "opnsense-config",
@ -941,6 +944,7 @@ dependencies = [
"semver", "semver",
"serde", "serde",
"serde_json", "serde_json",
"serde_yaml",
"tokio", "tokio",
"url", "url",
"uuid", "uuid",

View File

@ -26,6 +26,10 @@ russh = "0.45.0"
russh-keys = "0.45.0" russh-keys = "0.45.0"
rand = "0.8.5" rand = "0.8.5"
url = "2.5.4" url = "2.5.4"
kube = "0.98.0"
k8s-openapi = { version = "0.24.0", features = [ "v1_30" ] }
serde_yaml = "0.9.34"
http = "1.2.0"
[workspace.dependencies.uuid] [workspace.dependencies.uuid]
version = "1.11.0" version = "1.11.0"

View File

@ -23,3 +23,7 @@ harmony_macros = { path = "../harmony_macros" }
harmony_types = { path = "../harmony_types" } harmony_types = { path = "../harmony_types" }
uuid = { workspace = true } uuid = { workspace = true }
url = { workspace = true } url = { workspace = true }
kube = { workspace = true }
k8s-openapi = { workspace = true }
serde_yaml = { workspace = true }
http = { workspace = true }

View File

@ -3,7 +3,6 @@ use std::sync::Arc;
use derive_new::new; use derive_new::new;
use harmony_types::net::MacAddress; use harmony_types::net::MacAddress;
pub type HostGroup = Vec<PhysicalHost>; pub type HostGroup = Vec<PhysicalHost>;
pub type SwitchGroup = Vec<Switch>; pub type SwitchGroup = Vec<Switch>;
pub type FirewallGroup = Vec<PhysicalHost>; pub type FirewallGroup = Vec<PhysicalHost>;
@ -29,7 +28,11 @@ impl PhysicalHost {
} }
pub fn cluster_mac(&self) -> MacAddress { pub fn cluster_mac(&self) -> MacAddress {
self.network.get(0).expect("Cluster physical host should have a network interface").mac_address.clone() self.network
.get(0)
.expect("Cluster physical host should have a network interface")
.mac_address
.clone()
} }
} }

View File

@ -108,3 +108,12 @@ impl From<ExecutorError> for InterpretError {
} }
} }
} }
impl From<kube::Error> for InterpretError{
fn from(value: kube::Error) -> Self {
Self {
msg: format!("InterpretError : {value}"),
}
}
}

View File

@ -1,9 +1,11 @@
mod host_binding; mod host_binding;
mod http; mod http;
mod load_balancer; mod load_balancer;
pub mod openshift;
mod router; mod router;
mod tftp; mod tftp;
pub use load_balancer::*; pub use load_balancer::*;
use openshift::OpenshiftClient;
pub use router::*; pub use router::*;
mod network; mod network;
pub use host_binding::*; pub use host_binding::*;
@ -29,6 +31,12 @@ pub struct HAClusterTopology {
pub switch: Vec<LogicalHost>, pub switch: Vec<LogicalHost>,
} }
impl HAClusterTopology {
pub async fn oc_client(&self) -> Result<Arc<OpenshiftClient>, kube::Error> {
Ok(Arc::new(OpenshiftClient::try_default().await?))
}
}
pub type IpAddress = IpAddr; pub type IpAddress = IpAddr;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -0,0 +1,55 @@
use k8s_openapi::NamespaceResourceScope;
use kube::{api::PostParams, Api, Client, Error, Resource};
use serde::de::DeserializeOwned;
pub struct OpenshiftClient {
client: Client,
}
impl OpenshiftClient {
pub async fn try_default() -> Result<Self, Error> {
Ok(Self {
client: Client::try_default().await?,
})
}
pub async fn apply_all<
K: Resource<Scope = NamespaceResourceScope>
+ std::fmt::Debug
+ Sync
+ DeserializeOwned
+ Default
+ serde::Serialize
+ Clone,
>(
&self,
resource: &Vec<K>,
) -> Result<Vec<K>, kube::Error>
where
<K as kube::Resource>::DynamicType: Default,
{
let mut result = vec![];
for r in resource.iter() {
let api: Api<K> = Api::all(self.client.clone());
result.push(api.create(&PostParams::default(), &r).await?);
}
Ok(result)
}
pub async fn apply_namespaced<K>(&self, resource: &Vec<K>) -> Result<K, Error>
where
K: Resource<Scope = NamespaceResourceScope>
+ Clone
+ std::fmt::Debug
+ DeserializeOwned
+ serde::Serialize
+ Default,
<K as kube::Resource>::DynamicType: Default,
{
for r in resource.iter() {
let api: Api<K> = Api::default_namespaced(self.client.clone());
api.create(&PostParams::default(), &r).await?;
}
todo!("")
}
}

View File

@ -1,42 +0,0 @@
use async_trait::async_trait;
use crate::{data::{Id, Version}, interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, inventory::Inventory, score::Score, topology::HAClusterTopology};
#[derive(Debug)]
pub struct K8sResourceScore {}
impl Score for K8sResourceScore {
type InterpretType = K8sResourceInterpret;
fn create_interpret(self) -> Self::InterpretType {
todo!()
}
}
#[derive(Debug)]
pub struct K8sResourceInterpret {
score: K8sResourceScore,
}
#[async_trait]
impl Interpret for K8sResourceInterpret {
async fn execute(
&self,
inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
todo!()
}
fn get_name(&self) -> InterpretName {
todo!()
}
fn get_version(&self) -> Version {
todo!()
}
fn get_status(&self) -> InterpretStatus {
todo!()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
}

View File

@ -0,0 +1,53 @@
use harmony_macros::yaml;
use k8s_openapi::api::apps::v1::Deployment;
use serde_json::json;
use crate::score::Score;
use super::resource::{K8sResourceInterpret, K8sResourceScore};
#[derive(Debug)]
pub struct K8sDeploymentScore {
pub name: String,
pub image: String,
}
impl Score for K8sDeploymentScore {
type InterpretType = K8sResourceInterpret<Deployment>;
fn create_interpret(self) -> Self::InterpretType {
let deployment: Deployment = serde_json::from_value(json!(
{
"metadata": {
"name": self.name
},
"spec": {
"selector": {
"matchLabels": {
"app": self.name
},
},
"template": {
"metadata": {
"labels": {
"app": self.name
},
},
"spec": {
"containers": [
{
"image": self.image,
"name": self.image
}
]
}
}
}
}
))
.unwrap();
K8sResourceInterpret {
score: K8sResourceScore::single(deployment),
}
}
}

View File

@ -1,3 +1,4 @@
pub mod Resource; pub mod resource;
pub mod deployment;

View File

@ -0,0 +1,92 @@
use async_trait::async_trait;
use k8s_openapi::NamespaceResourceScope;
use kube::Resource;
use serde::de::DeserializeOwned;
use crate::{
data::{Id, Version},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::Score,
topology::HAClusterTopology,
};
#[derive(Debug)]
pub struct K8sResourceScore<K: Resource + std::fmt::Debug> {
pub resource: Vec<K>,
}
impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> {
pub fn single(resource: K) -> Self {
Self {
resource: vec![resource],
}
}
}
impl<
K: Resource<Scope = NamespaceResourceScope>
+ std::fmt::Debug
+ Sync
+ DeserializeOwned
+ Default
+ serde::Serialize
+ Clone,
> Score for K8sResourceScore<K>
where
<K as kube::Resource>::DynamicType: Default,
{
type InterpretType = K8sResourceInterpret<K>;
fn create_interpret(self) -> Self::InterpretType {
todo!()
}
}
#[derive(Debug)]
pub struct K8sResourceInterpret<K: Resource + std::fmt::Debug + Sync> {
pub score: K8sResourceScore<K>,
}
#[async_trait]
impl<
K: Resource<Scope = NamespaceResourceScope>
+ Clone
+ std::fmt::Debug
+ DeserializeOwned
+ serde::Serialize
+ Default
+ Sync,
> Interpret for K8sResourceInterpret<K>
where
<K as kube::Resource>::DynamicType: Default,
{
async fn execute(
&self,
inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
topology
.oc_client()
.await
.expect("Environment should provide enough information to instanciate a client")
.apply_namespaced(&self.score.resource)
.await?;
Ok(Outcome::success(
"Successfully applied resource".to_string(),
))
}
fn get_name(&self) -> InterpretName {
todo!()
}
fn get_version(&self) -> Version {
todo!()
}
fn get_status(&self) -> InterpretStatus {
todo!()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
}

View File

@ -1,10 +1,10 @@
mod opnsense; mod caddy;
mod interfaces;
mod dhcpd; mod dhcpd;
mod haproxy; mod haproxy;
mod caddy; mod interfaces;
mod opnsense;
pub use caddy::*; pub use caddy::*;
pub use haproxy::*;
pub use opnsense::*;
pub use interfaces::*;
pub use dhcpd::*; pub use dhcpd::*;
pub use haproxy::*;
pub use interfaces::*;
pub use opnsense::*;

View File

@ -141,6 +141,7 @@ pub struct Rule {
pub struct Source { pub struct Source {
pub any: Option<u8>, pub any: Option<u8>,
pub network: Option<MaybeString>, pub network: Option<MaybeString>,
pub address: Option<MaybeString>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -164,7 +165,7 @@ pub struct Sysctl {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct SysctlItem { pub struct SysctlItem {
pub descr: String, pub descr: MaybeString,
pub tunable: String, pub tunable: String,
pub value: String, pub value: String,
} }

View File

@ -1,4 +1,4 @@
mod xml_utils;
mod data; mod data;
mod xml_utils;
pub use data::*; pub use data::*;
pub use yaserde::MaybeString; pub use yaserde::MaybeString;