forked from NationTech/harmony
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,
|
K3dInstallation,
|
||||||
TenantInterpret,
|
TenantInterpret,
|
||||||
Application,
|
Application,
|
||||||
|
ArgoCD,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for InterpretName {
|
impl std::fmt::Display for InterpretName {
|
||||||
@ -39,6 +40,7 @@ impl std::fmt::Display for InterpretName {
|
|||||||
InterpretName::K3dInstallation => f.write_str("K3dInstallation"),
|
InterpretName::K3dInstallation => f.write_str("K3dInstallation"),
|
||||||
InterpretName::TenantInterpret => f.write_str("Tenant"),
|
InterpretName::TenantInterpret => f.write_str("Tenant"),
|
||||||
InterpretName::Application => f.write_str("Application"),
|
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,
|
ClusterResourceScope, NamespaceResourceScope,
|
||||||
api::{apps::v1::Deployment, core::v1::Pod},
|
api::{apps::v1::Deployment, core::v1::Pod},
|
||||||
};
|
};
|
||||||
use kube::runtime::conditions;
|
|
||||||
use kube::runtime::wait::await_condition;
|
|
||||||
use kube::{
|
use kube::{
|
||||||
Client, Config, Error, Resource,
|
Client, Config, Error, Resource,
|
||||||
api::{Api, AttachParams, ListParams, Patch, PatchParams, ResourceExt},
|
api::{Api, AttachParams, ListParams, Patch, PatchParams, ResourceExt},
|
||||||
@ -13,6 +11,11 @@ use kube::{
|
|||||||
core::ErrorResponse,
|
core::ErrorResponse,
|
||||||
runtime::reflector::Lookup,
|
runtime::reflector::Lookup,
|
||||||
};
|
};
|
||||||
|
use kube::{api::DynamicObject, runtime::conditions};
|
||||||
|
use kube::{
|
||||||
|
api::{ApiResource, GroupVersionKind},
|
||||||
|
runtime::wait::await_condition,
|
||||||
|
};
|
||||||
use log::{debug, error, trace};
|
use log::{debug, error, trace};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use similar::{DiffableStr, TextDiff};
|
use similar::{DiffableStr, TextDiff};
|
||||||
@ -239,6 +242,54 @@ impl K8sClient {
|
|||||||
Ok(result)
|
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> {
|
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,
|
||||||
|
|||||||
@ -246,7 +246,7 @@ pub struct K8sAnywhereConfig {
|
|||||||
///
|
///
|
||||||
/// default: true
|
/// default: true
|
||||||
pub use_local_k3d: bool,
|
pub use_local_k3d: bool,
|
||||||
harmony_profile: String,
|
pub harmony_profile: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl K8sAnywhereConfig {
|
impl K8sAnywhereConfig {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use std::{backtrace, collections::HashMap};
|
use std::{backtrace, collections::HashMap};
|
||||||
|
|
||||||
|
use k8s_openapi::{Metadata, NamespaceResourceScope, Resource};
|
||||||
|
use log::debug;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_yaml::{Mapping, Value};
|
use serde_yaml::{Mapping, Value};
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -174,15 +176,15 @@ impl From<CDApplicationConfig> for ArgoApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ArgoApplication {
|
impl ArgoApplication {
|
||||||
fn to_yaml(self) -> serde_yaml::Value {
|
pub fn to_yaml(&self) -> serde_yaml::Value {
|
||||||
let name = self.name;
|
let name = &self.name;
|
||||||
let namespace = if let Some(ns) = self.namespace {
|
let namespace = if let Some(ns) = self.namespace.as_ref() {
|
||||||
ns
|
&ns
|
||||||
} else {
|
} else {
|
||||||
"argocd".to_string()
|
"argocd"
|
||||||
};
|
};
|
||||||
let project = self.project;
|
let project = &self.project;
|
||||||
let source = self.source;
|
let source = &self.source;
|
||||||
|
|
||||||
let mut yaml_str = format!(
|
let mut yaml_str = format!(
|
||||||
r#"
|
r#"
|
||||||
@ -221,6 +223,7 @@ spec:
|
|||||||
.expect("couldn't serialize revision history to yaml string"),
|
.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")
|
serde_yaml::from_str(&yaml_str).expect("Couldn't parse YAML")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,11 +10,14 @@ use crate::{
|
|||||||
data::Version,
|
data::Version,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
application::{Application, ApplicationFeature, HelmPackage, OCICompliant},
|
application::{
|
||||||
|
Application, ApplicationFeature, HelmPackage, OCICompliant,
|
||||||
|
features::{ArgoApplication, ArgoHelmScore},
|
||||||
|
},
|
||||||
helm::chart::HelmChartScore,
|
helm::chart::HelmChartScore,
|
||||||
},
|
},
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{DeploymentTarget, HelmCommand, MultiTargetTopology, Topology, Url},
|
topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// ContinuousDelivery in Harmony provides this functionality :
|
/// ContinuousDelivery in Harmony provides this functionality :
|
||||||
@ -139,7 +142,7 @@ impl<A: OCICompliant + HelmPackage> ContinuousDelivery<A> {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<
|
impl<
|
||||||
A: OCICompliant + HelmPackage + Clone + 'static,
|
A: OCICompliant + HelmPackage + Clone + 'static,
|
||||||
T: Topology + HelmCommand + MultiTargetTopology + 'static,
|
T: Topology + HelmCommand + MultiTargetTopology + K8sclient + 'static,
|
||||||
> ApplicationFeature<T> for ContinuousDelivery<A>
|
> ApplicationFeature<T> for ContinuousDelivery<A>
|
||||||
{
|
{
|
||||||
async fn ensure_installed(&self, topology: &T) -> Result<(), String> {
|
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?;
|
let helm_chart = self.application.build_push_helm_package(&image).await?;
|
||||||
info!("Pushed new helm chart {helm_chart}");
|
info!("Pushed new helm chart {helm_chart}");
|
||||||
|
|
||||||
// let image = self.application.build_push_oci_image().await?;
|
let image = self.application.build_push_oci_image().await?;
|
||||||
// info!("Pushed new docker image {image}");
|
info!("Pushed new docker image {image}");
|
||||||
error!("uncomment above");
|
|
||||||
|
|
||||||
info!("Installing ContinuousDelivery feature");
|
info!("Installing ContinuousDelivery feature");
|
||||||
// TODO this is a temporary hack for demo purposes, the deployment target should be driven
|
// TODO this is a temporary hack for demo purposes, the deployment target should be driven
|
||||||
@ -178,29 +180,33 @@ impl<
|
|||||||
}
|
}
|
||||||
target => {
|
target => {
|
||||||
info!("Deploying to target {target:?}");
|
info!("Deploying to target {target:?}");
|
||||||
let cd_server = HelmChartScore {
|
let score = ArgoHelmScore {
|
||||||
namespace: todo!(
|
namespace: "harmonydemo-staging".to_string(),
|
||||||
"ArgoCD Helm chart with proper understanding of Tenant, see how Will did it for Monitoring for now"
|
openshift: true,
|
||||||
),
|
domain: "argo.harmonydemo.apps.st.mcd".to_string(),
|
||||||
release_name: todo!("argocd helm chart whatever"),
|
argo_apps: vec![ArgoApplication::from(CDApplicationConfig {
|
||||||
chart_name: todo!(),
|
// helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart/harmony-example-rust-webapp-chart --version 0.1.0
|
||||||
chart_version: todo!(),
|
version: Version::from("0.1.0").unwrap(),
|
||||||
values_overrides: todo!(),
|
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()),
|
||||||
values_yaml: todo!(),
|
helm_chart_name: "harmony-example-rust-webapp-chart".to_string(),
|
||||||
create_namespace: todo!(),
|
values_overrides: Value::Null,
|
||||||
install_only: todo!(),
|
name: "harmony-demo-rust-webapp".to_string(),
|
||||||
repository: todo!(),
|
namespace: "harmonydemo-staging".to_string(),
|
||||||
|
})],
|
||||||
};
|
};
|
||||||
let interpret = cd_server.create_interpret();
|
score
|
||||||
interpret.execute(&Inventory::empty(), topology);
|
.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
|
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] Package app (docker image, helm chart)
|
||||||
- [X] Push to registry
|
- [X] Push to registry
|
||||||
- [ ] Push only if staging or prod
|
- [X] Push only if staging or prod
|
||||||
- [ ] Deploy to local k3d when target is local
|
- [X] Deploy to local k3d when target is local
|
||||||
- [ ] Poke Argo
|
- [ ] Poke Argo
|
||||||
- [ ] Ensure app is up")
|
- [ ] Ensure app is up")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,89 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use k8s_openapi::Resource;
|
||||||
use non_blank_string_rs::NonBlankString;
|
use non_blank_string_rs::NonBlankString;
|
||||||
|
use serde::Serialize;
|
||||||
use std::str::FromStr;
|
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!(
|
let values = format!(
|
||||||
r#"
|
r#"
|
||||||
# -- Create aggregated roles that extend existing cluster roles to interact with argo-cd resources
|
# -- Create aggregated roles that extend existing cluster roles to interact with argo-cd resources
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user