feat: create Argo interpret and kube client apply_yaml to install Argo Applications. Very messy implementation though, must be refactored/improved
This commit is contained in:
parent
d9935e20cb
commit
6149249a6c
@ -22,6 +22,7 @@ pub enum InterpretName {
|
||||
K3dInstallation,
|
||||
TenantInterpret,
|
||||
Application,
|
||||
ArgoCD,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InterpretName {
|
||||
@ -39,6 +40,7 @@ impl std::fmt::Display for InterpretName {
|
||||
InterpretName::K3dInstallation => f.write_str("K3dInstallation"),
|
||||
InterpretName::TenantInterpret => f.write_str("Tenant"),
|
||||
InterpretName::Application => f.write_str("Application"),
|
||||
InterpretName::ArgoCD => f.write_str("ArgoCD"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
59
harmony/src/domain/score_with_dep.rs
Normal file
59
harmony/src/domain/score_with_dep.rs
Normal file
@ -0,0 +1,59 @@
|
||||
////////////////////
|
||||
/// Working idea
|
||||
///
|
||||
///
|
||||
trait ScoreWithDep<T> {
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>>;
|
||||
fn name(&self) -> String;
|
||||
fn get_dependencies(&self) -> Vec<TypeId>; // Force T to impl Installer<TypeId> or something
|
||||
// like that
|
||||
}
|
||||
|
||||
struct PrometheusAlertScore;
|
||||
|
||||
impl <T> ScoreWithDep<T> for PrometheusAlertScore {
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_dependencies(&self) -> Vec<TypeId> {
|
||||
// We have to find a way to constrait here so at compile time we are only allowed to return
|
||||
// TypeId for types which can be installed by T
|
||||
//
|
||||
// This means, for example that T must implement HelmCommand if the impl <T: HelmCommand> Installable<T> for
|
||||
// KubePrometheus calls for HelmCommand.
|
||||
vec![TypeId::of::<KubePrometheus>()]
|
||||
}
|
||||
}
|
||||
|
||||
trait Installable{}
|
||||
|
||||
struct KubePrometheus;
|
||||
|
||||
impl Installable for KubePrometheus;
|
||||
|
||||
|
||||
struct Maestro<T> {
|
||||
topology: T
|
||||
}
|
||||
|
||||
impl <T>Maestro<T> {
|
||||
fn execute_store(&self, score: ScoreWithDep<T>) {
|
||||
score.get_dependencies().iter().for_each(|dep| {
|
||||
self.topology.ensure_dependency_ready(dep);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct TopologyWithDep {
|
||||
}
|
||||
|
||||
impl TopologyWithDep {
|
||||
fn ensure_dependency_ready(&self, type_id: TypeId) -> Result<(), String> {
|
||||
self.installer
|
||||
}
|
||||
}
|
@ -4,8 +4,6 @@ use k8s_openapi::{
|
||||
ClusterResourceScope, NamespaceResourceScope,
|
||||
api::{apps::v1::Deployment, core::v1::Pod},
|
||||
};
|
||||
use kube::runtime::conditions;
|
||||
use kube::runtime::wait::await_condition;
|
||||
use kube::{
|
||||
Client, Config, Error, Resource,
|
||||
api::{Api, AttachParams, ListParams, Patch, PatchParams, ResourceExt},
|
||||
@ -13,6 +11,11 @@ use kube::{
|
||||
core::ErrorResponse,
|
||||
runtime::reflector::Lookup,
|
||||
};
|
||||
use kube::{api::DynamicObject, runtime::conditions};
|
||||
use kube::{
|
||||
api::{ApiResource, GroupVersionKind},
|
||||
runtime::wait::await_condition,
|
||||
};
|
||||
use log::{debug, error, trace};
|
||||
use serde::de::DeserializeOwned;
|
||||
use similar::{DiffableStr, TextDiff};
|
||||
@ -239,6 +242,54 @@ impl K8sClient {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn apply_yaml_many(
|
||||
&self,
|
||||
yaml: &Vec<serde_yaml::Value>,
|
||||
ns: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
for y in yaml.iter() {
|
||||
self.apply_yaml(y, ns).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn apply_yaml(
|
||||
&self,
|
||||
yaml: &serde_yaml::Value,
|
||||
ns: Option<&str>,
|
||||
) -> Result<(), Error> {
|
||||
let obj: DynamicObject = serde_yaml::from_value(yaml.clone()).expect("TODO do not unwrap");
|
||||
let name = obj.metadata.name.as_ref().expect("YAML must have a name");
|
||||
let namespace = obj
|
||||
.metadata
|
||||
.namespace
|
||||
.as_ref()
|
||||
.expect("YAML must have a namespace");
|
||||
|
||||
// 4. Define the API resource type using the GVK from the object.
|
||||
// The plural name 'applications' is taken from your CRD definition.
|
||||
error!("This only supports argocd application harcoded, very rrrong");
|
||||
let gvk = GroupVersionKind::gvk("argoproj.io", "v1alpha1", "Application");
|
||||
let api_resource = ApiResource::from_gvk_with_plural(&gvk, "applications");
|
||||
|
||||
// 5. Create a dynamic API client for this resource type.
|
||||
let api: Api<DynamicObject> =
|
||||
Api::namespaced_with(self.client.clone(), namespace, &api_resource);
|
||||
|
||||
// 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.
|
||||
println!(
|
||||
"Applying Argo Application '{}' in namespace '{}'...",
|
||||
name, namespace
|
||||
);
|
||||
let patch_params = PatchParams::apply("harmony"); // Use a unique field manager name
|
||||
let result = api.patch(name, &patch_params, &Patch::Apply(&obj)).await?;
|
||||
|
||||
println!("Successfully applied '{}'.", result.name_any());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn from_kubeconfig(path: &str) -> Option<K8sClient> {
|
||||
let k = match Kubeconfig::read_from(path) {
|
||||
Ok(k) => k,
|
||||
|
@ -246,7 +246,7 @@ pub struct K8sAnywhereConfig {
|
||||
///
|
||||
/// default: true
|
||||
pub use_local_k3d: bool,
|
||||
harmony_profile: String,
|
||||
pub harmony_profile: String,
|
||||
}
|
||||
|
||||
impl K8sAnywhereConfig {
|
||||
|
@ -1,5 +1,7 @@
|
||||
use std::{backtrace, collections::HashMap};
|
||||
|
||||
use k8s_openapi::{Metadata, NamespaceResourceScope, Resource};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use serde_yaml::{Mapping, Value};
|
||||
use url::Url;
|
||||
@ -174,15 +176,15 @@ impl From<CDApplicationConfig> for ArgoApplication {
|
||||
}
|
||||
|
||||
impl ArgoApplication {
|
||||
fn to_yaml(self) -> serde_yaml::Value {
|
||||
let name = self.name;
|
||||
let namespace = if let Some(ns) = self.namespace {
|
||||
ns
|
||||
pub fn to_yaml(&self) -> serde_yaml::Value {
|
||||
let name = &self.name;
|
||||
let namespace = if let Some(ns) = self.namespace.as_ref() {
|
||||
&ns
|
||||
} else {
|
||||
"argocd".to_string()
|
||||
"argocd"
|
||||
};
|
||||
let project = self.project;
|
||||
let source = self.source;
|
||||
let project = &self.project;
|
||||
let source = &self.source;
|
||||
|
||||
let mut yaml_str = format!(
|
||||
r#"
|
||||
@ -221,6 +223,7 @@ spec:
|
||||
.expect("couldn't serialize revision history to yaml string"),
|
||||
);
|
||||
|
||||
debug!("yaml serialize of :\n{yaml_str}");
|
||||
serde_yaml::from_str(&yaml_str).expect("Couldn't parse YAML")
|
||||
}
|
||||
}
|
||||
|
@ -10,11 +10,14 @@ use crate::{
|
||||
data::Version,
|
||||
inventory::Inventory,
|
||||
modules::{
|
||||
application::{Application, ApplicationFeature, HelmPackage, OCICompliant},
|
||||
application::{
|
||||
Application, ApplicationFeature, HelmPackage, OCICompliant,
|
||||
features::{ArgoApplication, ArgoHelmScore},
|
||||
},
|
||||
helm::chart::HelmChartScore,
|
||||
},
|
||||
score::Score,
|
||||
topology::{DeploymentTarget, HelmCommand, MultiTargetTopology, Topology, Url},
|
||||
topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url},
|
||||
};
|
||||
|
||||
/// ContinuousDelivery in Harmony provides this functionality :
|
||||
@ -139,7 +142,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> {
|
||||
#[async_trait]
|
||||
impl<
|
||||
A: OCICompliant + HelmPackage + Clone + 'static,
|
||||
T: Topology + HelmCommand + MultiTargetTopology + 'static,
|
||||
T: Topology + HelmCommand + MultiTargetTopology + K8sclient + 'static,
|
||||
> ApplicationFeature<T> for ContinuousDelivery<A>
|
||||
{
|
||||
async fn ensure_installed(&self, topology: &T) -> Result<(), String> {
|
||||
@ -153,9 +156,8 @@ impl<
|
||||
let helm_chart = self.application.build_push_helm_package(&image).await?;
|
||||
info!("Pushed new helm chart {helm_chart}");
|
||||
|
||||
// let image = self.application.build_push_oci_image().await?;
|
||||
// info!("Pushed new docker image {image}");
|
||||
error!("uncomment above");
|
||||
let image = self.application.build_push_oci_image().await?;
|
||||
info!("Pushed new docker image {image}");
|
||||
|
||||
info!("Installing ContinuousDelivery feature");
|
||||
// TODO this is a temporary hack for demo purposes, the deployment target should be driven
|
||||
@ -178,29 +180,33 @@ impl<
|
||||
}
|
||||
target => {
|
||||
info!("Deploying to target {target:?}");
|
||||
let cd_server = HelmChartScore {
|
||||
namespace: todo!(
|
||||
"ArgoCD Helm chart with proper understanding of Tenant, see how Will did it for Monitoring for now"
|
||||
),
|
||||
release_name: todo!("argocd helm chart whatever"),
|
||||
chart_name: todo!(),
|
||||
chart_version: todo!(),
|
||||
values_overrides: todo!(),
|
||||
values_yaml: todo!(),
|
||||
create_namespace: todo!(),
|
||||
install_only: todo!(),
|
||||
repository: todo!(),
|
||||
let score = ArgoHelmScore {
|
||||
namespace: "harmonydemo-staging".to_string(),
|
||||
openshift: true,
|
||||
domain: "argo.harmonydemo.apps.st.mcd".to_string(),
|
||||
argo_apps: vec![ArgoApplication::from(CDApplicationConfig {
|
||||
// helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart/harmony-example-rust-webapp-chart --version 0.1.0
|
||||
version: Version::from("0.1.0").unwrap(),
|
||||
helm_chart_repo_url: Url::Url(url::Url::parse("oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart/harmony-example-rust-webapp-chart").unwrap()),
|
||||
helm_chart_name: "harmony-example-rust-webapp-chart".to_string(),
|
||||
values_overrides: Value::Null,
|
||||
name: "harmony-demo-rust-webapp".to_string(),
|
||||
namespace: "harmonydemo-staging".to_string(),
|
||||
})],
|
||||
};
|
||||
let interpret = cd_server.create_interpret();
|
||||
interpret.execute(&Inventory::empty(), topology);
|
||||
score
|
||||
.create_interpret()
|
||||
.execute(&Inventory::empty(), topology)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
};
|
||||
|
||||
todo!("1. Create ArgoCD score that installs argo using helm chart, see if Taha's already done it
|
||||
- [X] Package app (docker image, helm chart)
|
||||
- [X] Push to registry
|
||||
- [ ] Push only if staging or prod
|
||||
- [ ] Deploy to local k3d when target is local
|
||||
- [X] Push only if staging or prod
|
||||
- [X] Deploy to local k3d when target is local
|
||||
- [ ] Poke Argo
|
||||
- [ ] Ensure app is up")
|
||||
}
|
||||
|
@ -1,9 +1,89 @@
|
||||
use async_trait::async_trait;
|
||||
use k8s_openapi::Resource;
|
||||
use non_blank_string_rs::NonBlankString;
|
||||
use serde::Serialize;
|
||||
use std::str::FromStr;
|
||||
|
||||
use crate::modules::helm::chart::{HelmChartScore, HelmRepository};
|
||||
use crate::{
|
||||
data::{Id, Version},
|
||||
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
|
||||
inventory::Inventory,
|
||||
modules::helm::chart::{HelmChartScore, HelmRepository},
|
||||
score::Score,
|
||||
topology::{HelmCommand, K8sclient, Topology},
|
||||
};
|
||||
|
||||
pub fn argo_helm_chart_score(namespace: String, openshift: bool, domain: String) -> HelmChartScore {
|
||||
use super::ArgoApplication;
|
||||
|
||||
#[derive(Debug, Serialize, Clone)]
|
||||
pub struct ArgoHelmScore {
|
||||
pub namespace: String,
|
||||
pub openshift: bool,
|
||||
pub domain: String,
|
||||
pub argo_apps: Vec<ArgoApplication>,
|
||||
}
|
||||
|
||||
impl<T: Topology + HelmCommand + K8sclient> Score<T> for ArgoHelmScore {
|
||||
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
||||
let helm_score = argo_helm_chart_score(&self.namespace, self.openshift, &self.domain);
|
||||
Box::new(ArgoInterpret {
|
||||
score: helm_score,
|
||||
argo_apps: self.argo_apps.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
fn name(&self) -> String {
|
||||
"ArgoHelmScore".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ArgoInterpret {
|
||||
score: HelmChartScore,
|
||||
argo_apps: Vec<ArgoApplication>,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for ArgoInterpret {
|
||||
async fn execute(
|
||||
&self,
|
||||
inventory: &Inventory,
|
||||
topology: &T,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
self.score
|
||||
.create_interpret()
|
||||
.execute(inventory, topology)
|
||||
.await?;
|
||||
|
||||
let k8s_client = topology.k8s_client().await?;
|
||||
k8s_client
|
||||
.apply_yaml_many(&self.argo_apps.iter().map(|a| a.to_yaml()).collect(), None)
|
||||
.await
|
||||
.unwrap();
|
||||
Ok(Outcome::success(format!(
|
||||
"Successfully installed ArgoCD and {} Applications",
|
||||
self.argo_apps.len()
|
||||
)))
|
||||
}
|
||||
|
||||
fn get_name(&self) -> InterpretName {
|
||||
InterpretName::ArgoCD
|
||||
}
|
||||
|
||||
fn get_version(&self) -> Version {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_status(&self) -> InterpretStatus {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_children(&self) -> Vec<Id> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn argo_helm_chart_score(namespace: &str, openshift: bool, domain: &str) -> HelmChartScore {
|
||||
let values = format!(
|
||||
r#"
|
||||
# -- Create aggregated roles that extend existing cluster roles to interact with argo-cd resources
|
||||
|
Loading…
Reference in New Issue
Block a user