Compare commits

...

3 Commits

Author SHA1 Message Date
528ee8a696 wip: argocd discovery
Some checks failed
Run Check Script / check (pull_request) Failing after 13s
2025-10-14 21:16:02 -04:00
69a159711a feat: Support tls enabled by default on rust web app
Some checks failed
Run Check Script / check (pull_request) Failing after 15s
2025-10-14 15:53:23 -04:00
b0ad7bb4c4 feat(application): Webapp feature with production dns
Some checks failed
Run Check Script / check (pull_request) Failing after 17s
2025-10-14 15:19:12 -04:00
9 changed files with 79 additions and 30 deletions

View File

@@ -186,7 +186,7 @@ impl TopologyState {
} }
} }
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum DeploymentTarget { pub enum DeploymentTarget {
LocalDev, LocalDev,
Staging, Staging,

View File

@@ -57,8 +57,10 @@ impl<T: Topology + K8sclient + HelmCommand + Ingress> Interpret<T> for ArgoInter
let k8s_client = topology.k8s_client().await?; let k8s_client = topology.k8s_client().await?;
let svc = format!("argo-{}", self.score.namespace.clone()); let svc = format!("argo-{}", self.score.namespace.clone());
let domain = topology.get_domain(&svc).await?; let domain = topology.get_domain(&svc).await?;
// FIXME we now have a way to know if we're running on openshift family
let helm_score = let helm_score =
argo_helm_chart_score(&self.score.namespace, self.score.openshift, &domain); argo_helm_chart_score(&self.score.namespace, self.score.openshift, &domain);
helm_score.interpret(inventory, topology).await?; helm_score.interpret(inventory, topology).await?;

View File

@@ -10,12 +10,11 @@ use crate::{
data::Version, data::Version,
inventory::Inventory, inventory::Inventory,
modules::application::{ modules::application::{
ApplicationFeature, HelmPackage, InstallationError, InstallationOutcome, OCICompliant, features::{ArgoApplication, ArgoHelmScore}, webapp::Webapp, ApplicationFeature, HelmPackage, InstallationError, InstallationOutcome, OCICompliant
features::{ArgoApplication, ArgoHelmScore},
}, },
score::Score, score::Score,
topology::{ topology::{
DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, ingress::Ingress, ingress::Ingress, DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology
}, },
}; };
@@ -47,11 +46,11 @@ use crate::{
/// - ArgoCD to install/upgrade/rollback/inspect k8s resources /// - ArgoCD to install/upgrade/rollback/inspect k8s resources
/// - Kubernetes for runtime orchestration /// - Kubernetes for runtime orchestration
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct PackagingDeployment<A: OCICompliant + HelmPackage> { pub struct PackagingDeployment<A: OCICompliant + HelmPackage + Webapp> {
pub application: Arc<A>, pub application: Arc<A>,
} }
impl<A: OCICompliant + HelmPackage> PackagingDeployment<A> { impl<A: OCICompliant + HelmPackage + Webapp> PackagingDeployment<A> {
async fn deploy_to_local_k3d( async fn deploy_to_local_k3d(
&self, &self,
app_name: String, app_name: String,
@@ -137,7 +136,7 @@ impl<A: OCICompliant + HelmPackage> PackagingDeployment<A> {
#[async_trait] #[async_trait]
impl< impl<
A: OCICompliant + HelmPackage + Clone + 'static, A: OCICompliant + HelmPackage + Webapp + Clone + 'static,
T: Topology + HelmCommand + MultiTargetTopology + K8sclient + Ingress + 'static, T: Topology + HelmCommand + MultiTargetTopology + K8sclient + Ingress + 'static,
> ApplicationFeature<T> for PackagingDeployment<A> > ApplicationFeature<T> for PackagingDeployment<A>
{ {
@@ -146,10 +145,15 @@ impl<
topology: &T, topology: &T,
) -> Result<InstallationOutcome, InstallationError> { ) -> Result<InstallationOutcome, InstallationError> {
let image = self.application.image_name(); let image = self.application.image_name();
let domain = topology
let domain = if topology.current_target() == DeploymentTarget::Production {
self.application.dns()
} else {
topology
.get_domain(&self.application.name()) .get_domain(&self.application.name())
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?
};
// TODO Write CI/CD workflow files // TODO Write CI/CD workflow files
// we can autotedect the CI type using the remote url (default to github action for github // we can autotedect the CI type using the remote url (default to github action for github

View File

@@ -2,6 +2,7 @@ mod feature;
pub mod features; pub mod features;
pub mod oci; pub mod oci;
mod rust; mod rust;
mod webapp;
use std::sync::Arc; use std::sync::Arc;
pub use feature::*; pub use feature::*;

View File

@@ -16,6 +16,7 @@ use tar::{Builder, Header};
use walkdir::WalkDir; use walkdir::WalkDir;
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL}; use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
use crate::modules::application::webapp::Webapp;
use crate::{score::Score, topology::Topology}; use crate::{score::Score, topology::Topology};
use super::{Application, ApplicationFeature, ApplicationInterpret, HelmPackage, OCICompliant}; use super::{Application, ApplicationFeature, ApplicationInterpret, HelmPackage, OCICompliant};
@@ -60,6 +61,10 @@ pub struct RustWebapp {
pub project_root: PathBuf, pub project_root: PathBuf,
pub service_port: u32, pub service_port: u32,
pub framework: Option<RustWebFramework>, pub framework: Option<RustWebFramework>,
/// Host name that will be used in production environment.
///
/// This is the place to put the public host name if this is a public facing webapp.
pub dns: String,
} }
impl Application for RustWebapp { impl Application for RustWebapp {
@@ -68,6 +73,12 @@ impl Application for RustWebapp {
} }
} }
impl Webapp for RustWebapp {
fn dns(&self) -> String {
self.dns.clone()
}
}
#[async_trait] #[async_trait]
impl HelmPackage for RustWebapp { impl HelmPackage for RustWebapp {
async fn build_push_helm_package( async fn build_push_helm_package(
@@ -257,7 +268,6 @@ impl RustWebapp {
".harmony_generated", ".harmony_generated",
"harmony", "harmony",
"node_modules", "node_modules",
"Dockerfile.harmony",
]; ];
let mut entries: Vec<_> = WalkDir::new(project_root) let mut entries: Vec<_> = WalkDir::new(project_root)
.into_iter() .into_iter()
@@ -461,52 +471,53 @@ impl RustWebapp {
let (image_repo, image_tag) = image_url.rsplit_once(':').unwrap_or((image_url, "latest")); let (image_repo, image_tag) = image_url.rsplit_once(':').unwrap_or((image_url, "latest"));
let app_name = &self.name;
let service_port = self.service_port;
// Create Chart.yaml // Create Chart.yaml
let chart_yaml = format!( let chart_yaml = format!(
r#" r#"
apiVersion: v2 apiVersion: v2
name: {} name: {chart_name}
description: A Helm chart for the {} web application. description: A Helm chart for the {app_name} web application.
type: application type: application
version: 0.1.0 version: 0.2.0
appVersion: "{}" appVersion: "{image_tag}"
"#, "#,
chart_name, self.name, image_tag
); );
fs::write(chart_dir.join("Chart.yaml"), chart_yaml)?; fs::write(chart_dir.join("Chart.yaml"), chart_yaml)?;
// Create values.yaml // Create values.yaml
let values_yaml = format!( let values_yaml = format!(
r#" r#"
# Default values for {}. # Default values for {chart_name}.
# This is a YAML-formatted file. # This is a YAML-formatted file.
# Declare variables to be passed into your templates. # Declare variables to be passed into your templates.
replicaCount: 1 replicaCount: 1
image: image:
repository: {} repository: {image_repo}
pullPolicy: IfNotPresent pullPolicy: IfNotPresent
# Overridden by the chart's appVersion # Overridden by the chart's appVersion
tag: "{}" tag: "{image_tag}"
service: service:
type: ClusterIP type: ClusterIP
port: {} port: {service_port}
ingress: ingress:
enabled: true enabled: true
tls: true
# Annotations for cert-manager to handle SSL. # Annotations for cert-manager to handle SSL.
annotations: annotations:
# Add other annotations like nginx ingress class if needed # Add other annotations like nginx ingress class if needed
# kubernetes.io/ingress.class: nginx # kubernetes.io/ingress.class: nginx
hosts: hosts:
- host: {} - host: {domain}
paths: paths:
- path: / - path: /
pathType: ImplementationSpecific pathType: ImplementationSpecific
"#, "#,
chart_name, image_repo, image_tag, self.service_port, domain,
); );
fs::write(chart_dir.join("values.yaml"), values_yaml)?; fs::write(chart_dir.join("values.yaml"), values_yaml)?;
@@ -583,7 +594,11 @@ spec:
); );
fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?; fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?;
let service_port = self.service_port;
// Create templates/ingress.yaml // Create templates/ingress.yaml
// TODO get issuer name and tls config from topology as it may be different from one
// cluster to another, also from one version to another
let ingress_yaml = format!( let ingress_yaml = format!(
r#" r#"
{{{{- if $.Values.ingress.enabled -}}}} {{{{- if $.Values.ingress.enabled -}}}}
@@ -596,13 +611,11 @@ metadata:
spec: spec:
{{{{- if $.Values.ingress.tls }}}} {{{{- if $.Values.ingress.tls }}}}
tls: tls:
{{{{- range $.Values.ingress.tls }}}} - secretName: {{{{ include "chart.fullname" . }}}}-tls
- hosts: hosts:
{{{{- range .hosts }}}} {{{{- range $.Values.ingress.hosts }}}}
- {{{{ . | quote }}}} - {{{{ .host | quote }}}}
{{{{- end }}}} {{{{- end }}}}
secretName: {{{{ .secretName }}}}
{{{{- end }}}}
{{{{- end }}}} {{{{- end }}}}
rules: rules:
{{{{- range $.Values.ingress.hosts }}}} {{{{- range $.Values.ingress.hosts }}}}
@@ -616,12 +629,11 @@ spec:
service: service:
name: {{{{ include "chart.fullname" $ }}}} name: {{{{ include "chart.fullname" $ }}}}
port: port:
number: {{{{ $.Values.service.port | default {} }}}} number: {{{{ $.Values.service.port | default {service_port} }}}}
{{{{- end }}}} {{{{- end }}}}
{{{{- end }}}} {{{{- end }}}}
{{{{- end }}}} {{{{- end }}}}
"#, "#,
self.service_port
); );
fs::write(templates_dir.join("ingress.yaml"), ingress_yaml)?; fs::write(templates_dir.join("ingress.yaml"), ingress_yaml)?;

View File

@@ -0,0 +1,7 @@
use super::Application;
use async_trait::async_trait;
#[async_trait]
pub trait Webapp: Application {
fn dns(&self) -> String;
}

View File

@@ -0,0 +1,20 @@
/// Discover the current ArgoCD setup
///
/// 1. No argo installed
/// 2. Argo installed in current namespace
/// 3. Argo installed in different namespace (assuming cluster wide access)
///
/// For now we will go ahead with this very basic logic, there are many intricacies that can be
/// dealt with later, such as multitenant management in a single argo instance, credentials setup t
#[async_trait]
pub trait ArgoCD {
async fn ensure_installed() {
}
}
struct CurrentNamespaceArgo;
impl ArgoCD for CurrentNamespaceArgo {
}

View File

@@ -0,0 +1,2 @@
mod discover;
pub use discover::*;

View File

@@ -17,3 +17,4 @@ pub mod prometheus;
pub mod storage; pub mod storage;
pub mod tenant; pub mod tenant;
pub mod tftp; pub mod tftp;
pub mod argocd;