fix(host_network): adjust bond & port-channel configuration (partial) #175
@ -102,80 +102,58 @@ impl HAClusterTopology {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn ensure_nmstate_operator_installed(&self) -> Result<(), String> {
|
async fn ensure_nmstate_operator_installed(&self) -> Result<(), String> {
|
||||||
// FIXME: Find a way to check nmstate is already available (get pod -n nmstate)
|
|
||||||
debug!("Installing NMState operator...");
|
|
||||||
let k8s_client = self.k8s_client().await?;
|
let k8s_client = self.k8s_client().await?;
|
||||||
|
|
||||||
let nmstate_namespace = Namespace {
|
debug!("Installing NMState controller...");
|
||||||
metadata: ObjectMeta {
|
k8s_client.apply_url(url::Url::parse("https://github.com/nmstate/kubernetes-nmstate/releases/download/v0.84.0/nmstate.io_nmstates.yaml
|
||||||
name: Some("nmstate".to_string()),
|
").unwrap(), Some("nmstate"))
|
||||||
finalizers: Some(vec!["kubernetes".to_string()]),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
debug!("Creating NMState namespace: {nmstate_namespace:#?}");
|
|
||||||
k8s_client
|
|
||||||
.apply(&nmstate_namespace, None)
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let nmstate_operator_group = OperatorGroup {
|
debug!("Creating NMState namespace...");
|
||||||
metadata: ObjectMeta {
|
k8s_client.apply_url(url::Url::parse("https://github.com/nmstate/kubernetes-nmstate/releases/download/v0.84.0/namespace.yaml
|
||||||
name: Some("nmstate".to_string()),
|
").unwrap(), Some("nmstate"))
|
||||||
namespace: Some("nmstate".to_string()),
|
|
||||||
..Default::default()
|
|
||||||
},
|
|
||||||
spec: OperatorGroupSpec {
|
|
||||||
target_namespaces: vec!["nmstate".to_string()],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
debug!("Creating NMState operator group: {nmstate_operator_group:#?}");
|
|
||||||
k8s_client
|
|
||||||
.apply(&nmstate_operator_group, Some("nmstate"))
|
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
let nmstate_subscription = Subscription {
|
debug!("Creating NMState service account...");
|
||||||
metadata: ObjectMeta {
|
k8s_client.apply_url(url::Url::parse("https://github.com/nmstate/kubernetes-nmstate/releases/download/v0.84.0/service_account.yaml
|
||||||
name: Some("kubernetes-nmstate-operator".to_string()),
|
").unwrap(), Some("nmstate"))
|
||||||
namespace: Some("nmstate".to_string()),
|
.await
|
||||||
..Default::default()
|
.map_err(|e| e.to_string())?;
|
||||||
},
|
|
||||||
spec: SubscriptionSpec {
|
debug!("Creating NMState role...");
|
||||||
channel: Some("alpha".to_string()),
|
k8s_client.apply_url(url::Url::parse("https://github.com/nmstate/kubernetes-nmstate/releases/download/v0.84.0/role.yaml
|
||||||
name: "kubernetes-nmstate-operator".to_string(),
|
").unwrap(), Some("nmstate"))
|
||||||
source: "operatorhubio-catalog".to_string(),
|
.await
|
||||||
source_namespace: "openshift-marketplace".to_string(),
|
.map_err(|e| e.to_string())?;
|
||||||
install_plan_approval: Some(InstallPlanApproval::Automatic),
|
|
||||||
},
|
debug!("Creating NMState role binding...");
|
||||||
};
|
k8s_client.apply_url(url::Url::parse("https://github.com/nmstate/kubernetes-nmstate/releases/download/v0.84.0/role_binding.yaml
|
||||||
debug!("Subscribing to NMState Operator: {nmstate_subscription:#?}");
|
").unwrap(), Some("nmstate"))
|
||||||
k8s_client
|
.await
|
||||||
.apply(&nmstate_subscription, Some("nmstate"))
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
debug!("Creating NMState operator...");
|
||||||
|
k8s_client.apply_url(url::Url::parse("https://github.com/nmstate/kubernetes-nmstate/releases/download/v0.84.0/operator.yaml
|
||||||
|
").unwrap(), Some("nmstate"))
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
k8s_client
|
k8s_client
|
||||||
.wait_for_operator(
|
.wait_until_deployment_ready("nmstate-operator", Some("nmstate"), None)
|
||||||
"kubernetes-nmstate-operator",
|
|
||||||
Some("nmstate"),
|
|
||||||
Some(Duration::from_secs(30)),
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let nmstate = NMState {
|
let nmstate = NMState {
|
||||||
metadata: ObjectMeta {
|
metadata: ObjectMeta {
|
||||||
name: Some("nmstate".to_string()),
|
name: Some("nmstate".to_string()),
|
||||||
namespace: Some("nmstate".to_string()),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
debug!("Creating NMState: {nmstate:#?}");
|
debug!("Creating NMState: {nmstate:#?}");
|
||||||
k8s_client
|
k8s_client
|
||||||
.apply(&nmstate, Some("nmstate"))
|
.apply(&nmstate, None)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
|||||||
@ -15,6 +15,7 @@ use kube::{
|
|||||||
},
|
},
|
||||||
config::{KubeConfigOptions, Kubeconfig},
|
config::{KubeConfigOptions, Kubeconfig},
|
||||||
core::ErrorResponse,
|
core::ErrorResponse,
|
||||||
|
discovery::{ApiCapabilities, Scope},
|
||||||
error::DiscoveryError,
|
error::DiscoveryError,
|
||||||
runtime::{reflector::Lookup, wait::Condition},
|
runtime::{reflector::Lookup, wait::Condition},
|
||||||
};
|
};
|
||||||
@ -23,11 +24,13 @@ use kube::{
|
|||||||
api::{ApiResource, GroupVersionKind},
|
api::{ApiResource, GroupVersionKind},
|
||||||
runtime::wait::await_condition,
|
runtime::wait::await_condition,
|
||||||
};
|
};
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, info, trace, warn};
|
||||||
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
use serde::{Deserialize, Serialize, de::DeserializeOwned};
|
||||||
use serde_json::{Value, json};
|
use serde_json::{Value, json};
|
||||||
|
use serde_value::DeserializerError;
|
||||||
use similar::TextDiff;
|
use similar::TextDiff;
|
||||||
use tokio::{io::AsyncReadExt, time::sleep};
|
use tokio::{io::AsyncReadExt, time::sleep};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::modules::okd::crd::ClusterServiceVersion;
|
use crate::modules::okd::crd::ClusterServiceVersion;
|
||||||
|
|
||||||
@ -140,9 +143,9 @@ impl K8sClient {
|
|||||||
|
|
||||||
pub async fn wait_until_deployment_ready(
|
pub async fn wait_until_deployment_ready(
|
||||||
&self,
|
&self,
|
||||||
name: String,
|
name: &str,
|
||||||
namespace: Option<&str>,
|
namespace: Option<&str>,
|
||||||
timeout: Option<u64>,
|
timeout: Option<Duration>,
|
||||||
) -> Result<(), String> {
|
) -> Result<(), String> {
|
||||||
let api: Api<Deployment>;
|
let api: Api<Deployment>;
|
||||||
|
|
||||||
@ -152,9 +155,9 @@ impl K8sClient {
|
|||||||
api = Api::default_namespaced(self.client.clone());
|
api = Api::default_namespaced(self.client.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
let establish = await_condition(api, name.as_str(), conditions::is_deployment_completed());
|
let establish = await_condition(api, name, conditions::is_deployment_completed());
|
||||||
let t = timeout.unwrap_or(300);
|
let timeout = timeout.unwrap_or(Duration::from_secs(120));
|
||||||
let res = tokio::time::timeout(std::time::Duration::from_secs(t), establish).await;
|
let res = tokio::time::timeout(timeout, establish).await;
|
||||||
|
|
||||||
if res.is_ok() {
|
if res.is_ok() {
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -451,7 +454,7 @@ impl K8sClient {
|
|||||||
where
|
where
|
||||||
K: Resource + Clone + std::fmt::Debug + DeserializeOwned + serde::Serialize,
|
K: Resource + Clone + std::fmt::Debug + DeserializeOwned + serde::Serialize,
|
||||||
<K as Resource>::Scope: ApplyStrategy<K>,
|
<K as Resource>::Scope: ApplyStrategy<K>,
|
||||||
<K as kube::Resource>::DynamicType: Default,
|
<K as Resource>::DynamicType: Default,
|
||||||
{
|
{
|
||||||
let mut result = Vec::new();
|
let mut result = Vec::new();
|
||||||
for r in resource.iter() {
|
for r in resource.iter() {
|
||||||
@ -516,10 +519,7 @@ impl K8sClient {
|
|||||||
|
|
||||||
// 6. Apply the object to the cluster using Server-Side Apply.
|
// 6. Apply the object to the cluster using Server-Side Apply.
|
||||||
// This will create the resource if it doesn't exist, or update it if it does.
|
// This will create the resource if it doesn't exist, or update it if it does.
|
||||||
println!(
|
println!("Applying Argo Application '{name}' in namespace '{namespace}'...",);
|
||||||
"Applying Argo Application '{}' in namespace '{}'...",
|
|
||||||
name, namespace
|
|
||||||
);
|
|
||||||
let patch_params = PatchParams::apply("harmony"); // Use a unique field manager name
|
let patch_params = PatchParams::apply("harmony"); // Use a unique field manager name
|
||||||
let result = api.patch(name, &patch_params, &Patch::Apply(&obj)).await?;
|
let result = api.patch(name, &patch_params, &Patch::Apply(&obj)).await?;
|
||||||
|
|
||||||
@ -528,6 +528,51 @@ impl K8sClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Apply a resource from a URL
|
||||||
|
///
|
||||||
|
/// It is the equivalent of `kubectl apply -f <url>`
|
||||||
|
pub async fn apply_url(&self, url: Url, ns: Option<&str>) -> Result<(), Error> {
|
||||||
|
let patch_params = PatchParams::apply("harmony");
|
||||||
|
let discovery = kube::Discovery::new(self.client.clone()).run().await?;
|
||||||
|
|
||||||
|
let yaml = reqwest::get(url)
|
||||||
|
.await
|
||||||
|
.expect("Could not get URL")
|
||||||
|
.text()
|
||||||
|
.await
|
||||||
|
.expect("Could not get content from URL");
|
||||||
|
|
||||||
|
for doc in multidoc_deserialize(&yaml).expect("failed to parse YAML from file") {
|
||||||
|
let obj: DynamicObject =
|
||||||
|
serde_yaml::from_value(doc).expect("cannot apply without valid YAML");
|
||||||
|
let namespace = obj.metadata.namespace.as_deref().or(ns);
|
||||||
|
let type_meta = obj
|
||||||
|
.types
|
||||||
|
.as_ref()
|
||||||
|
.expect("cannot apply object without valid TypeMeta");
|
||||||
|
let gvk = GroupVersionKind::try_from(type_meta)
|
||||||
|
.expect("cannot apply object without valid GroupVersionKind");
|
||||||
|
let name = obj.name_any();
|
||||||
|
|
||||||
|
if let Some((ar, caps)) = discovery.resolve_gvk(&gvk) {
|
||||||
|
let api = get_dynamic_api(ar, caps, self.client.clone(), namespace, false);
|
||||||
|
trace!(
|
||||||
|
"Applying {}: \n{}",
|
||||||
|
gvk.kind,
|
||||||
|
serde_yaml::to_string(&obj).expect("Failed to serialize YAML")
|
||||||
|
);
|
||||||
|
let data: serde_json::Value =
|
||||||
|
serde_json::to_value(&obj).expect("Failed to serialize JSON");
|
||||||
|
let _r = api.patch(&name, &patch_params, &Patch::Apply(data)).await?;
|
||||||
|
debug!("applied {} {}", gvk.kind, name);
|
||||||
|
} else {
|
||||||
|
warn!("Cannot apply document for unknown {gvk:?}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) async fn from_kubeconfig(path: &str) -> Option<K8sClient> {
|
pub(crate) async fn from_kubeconfig(path: &str) -> Option<K8sClient> {
|
||||||
let k = match Kubeconfig::read_from(path) {
|
let k = match Kubeconfig::read_from(path) {
|
||||||
Ok(k) => k,
|
Ok(k) => k,
|
||||||
@ -547,6 +592,31 @@ impl K8sClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_dynamic_api(
|
||||||
|
resource: ApiResource,
|
||||||
|
capabilities: ApiCapabilities,
|
||||||
|
client: Client,
|
||||||
|
ns: Option<&str>,
|
||||||
|
all: bool,
|
||||||
|
) -> Api<DynamicObject> {
|
||||||
|
if capabilities.scope == Scope::Cluster || all {
|
||||||
|
Api::all_with(client, &resource)
|
||||||
|
} else if let Some(namespace) = ns {
|
||||||
|
Api::namespaced_with(client, namespace, &resource)
|
||||||
|
} else {
|
||||||
|
Api::default_namespaced_with(client, &resource)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn multidoc_deserialize(data: &str) -> Result<Vec<serde_yaml::Value>, serde_yaml::Error> {
|
||||||
|
use serde::Deserialize;
|
||||||
|
let mut docs = vec![];
|
||||||
|
for de in serde_yaml::Deserializer::from_str(data) {
|
||||||
|
docs.push(serde_yaml::Value::deserialize(de)?);
|
||||||
|
}
|
||||||
|
Ok(docs)
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ApplyStrategy<K: Resource> {
|
pub trait ApplyStrategy<K: Resource> {
|
||||||
fn get_api(client: &Client, ns: Option<&str>) -> Api<K>;
|
fn get_api(client: &Client, ns: Option<&str>) -> Api<K>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1 +0,0 @@
|
|||||||
pub mod types;
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
|
|
||||||
use kube::CustomResource;
|
|
||||||
use schemars::JsonSchema;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(CustomResource, Default, Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
|
||||||
#[kube(
|
|
||||||
group = "operators.coreos.com",
|
|
||||||
version = "v1alpha1",
|
|
||||||
kind = "CatalogSource",
|
|
||||||
namespaced
|
|
||||||
)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct CatalogSourceSpec {
|
|
||||||
pub source_type: String,
|
|
||||||
pub image: String,
|
|
||||||
pub display_name: String,
|
|
||||||
pub publisher: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub grpc_pod_config: Option<GrpcPodConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CatalogSource {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
metadata: Default::default(),
|
|
||||||
spec: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize, Clone, Debug, JsonSchema)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct GrpcPodConfig {
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub memory_target: Option<String>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub node_selector: Option<BTreeMap<String, String>>,
|
|
||||||
}
|
|
||||||
@ -3,6 +3,5 @@ pub mod executors;
|
|||||||
pub mod hp_ilo;
|
pub mod hp_ilo;
|
||||||
pub mod intel_amt;
|
pub mod intel_amt;
|
||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
pub mod kubers;
|
|
||||||
pub mod opnsense;
|
pub mod opnsense;
|
||||||
mod sqlx;
|
mod sqlx;
|
||||||
|
|||||||
@ -100,11 +100,7 @@ impl<T: Topology + HelmCommand + K8sclient + MultiTargetTopology> Interpret<T> f
|
|||||||
|
|
||||||
info!("deploying ntfy...");
|
info!("deploying ntfy...");
|
||||||
client
|
client
|
||||||
.wait_until_deployment_ready(
|
.wait_until_deployment_ready("ntfy", Some(self.score.namespace.as_str()), None)
|
||||||
"ntfy".to_string(),
|
|
||||||
Some(self.score.namespace.as_str()),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await?;
|
.await?;
|
||||||
info!("ntfy deployed");
|
info!("ntfy deployed");
|
||||||
|
|
||||||
|
|||||||
@ -6,9 +6,16 @@ use serde::{Deserialize, Serialize};
|
|||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
#[derive(CustomResource, Deserialize, Serialize, Clone, Debug, JsonSchema)]
|
||||||
#[kube(group = "nmstate.io", version = "v1", kind = "NMState", namespaced)]
|
#[kube(
|
||||||
|
group = "nmstate.io",
|
||||||
|
version = "v1",
|
||||||
|
kind = "NMState",
|
||||||
|
plural = "nmstates",
|
||||||
|
namespaced = false
|
||||||
|
)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct NMStateSpec {
|
pub struct NMStateSpec {
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub probe_configuration: Option<ProbeConfig>,
|
pub probe_configuration: Option<ProbeConfig>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user