fix: unjank the demo #85
1
.gitignore
vendored
@ -2,3 +2,4 @@ target
|
|||||||
private_repos
|
private_repos
|
||||||
log/
|
log/
|
||||||
*.tgz
|
*.tgz
|
||||||
|
.gitignore
|
||||||
|
|||||||
|
38
Cargo.lock
generated
@ -1355,6 +1355,7 @@ dependencies = [
|
|||||||
name = "example-rust"
|
name = "example-rust"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"harmony",
|
"harmony",
|
||||||
"harmony_cli",
|
"harmony_cli",
|
||||||
@ -1427,6 +1428,18 @@ version = "0.2.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "filetime"
|
||||||
|
version = "0.2.25"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"libredox",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.1.2"
|
version = "1.1.2"
|
||||||
@ -1726,6 +1739,8 @@ name = "harmony"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
"base64 0.22.1",
|
||||||
|
"bollard",
|
||||||
"chrono",
|
"chrono",
|
||||||
"cidr",
|
"cidr",
|
||||||
"convert_case",
|
"convert_case",
|
||||||
@ -1765,6 +1780,7 @@ dependencies = [
|
|||||||
"serde_yaml",
|
"serde_yaml",
|
||||||
"similar",
|
"similar",
|
||||||
"strum 0.27.1",
|
"strum 0.27.1",
|
||||||
|
"tar",
|
||||||
"temp-dir",
|
"temp-dir",
|
||||||
"temp-file",
|
"temp-file",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
@ -2728,6 +2744,7 @@ checksum = "1580801010e535496706ba011c15f8532df6b42297d2e471fec38ceadd8c0638"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.9.1",
|
"bitflags 2.9.1",
|
||||||
"libc",
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4696,6 +4713,17 @@ version = "1.0.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tar"
|
||||||
|
version = "0.4.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1d863878d212c87a19c1a610eb53bb01fe12951c0501cf5a0d65f724914a667a"
|
||||||
|
dependencies = [
|
||||||
|
"filetime",
|
||||||
|
"libc",
|
||||||
|
"xattr",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "temp-dir"
|
name = "temp-dir"
|
||||||
version = "0.1.16"
|
version = "0.1.16"
|
||||||
@ -5741,6 +5769,16 @@ dependencies = [
|
|||||||
"tap",
|
"tap",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "xattr"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af3a19837351dc82ba89f8a125e22a3c475f05aba604acc023d62b2739ae2909"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"rustix 1.0.7",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "xml-rs"
|
name = "xml-rs"
|
||||||
version = "0.8.26"
|
version = "0.8.26"
|
||||||
|
@ -53,3 +53,6 @@ chrono = "0.4"
|
|||||||
similar = "2"
|
similar = "2"
|
||||||
uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] }
|
uuid = { version = "1.11", features = ["v4", "fast-rng", "macro-diagnostics"] }
|
||||||
pretty_assertions = "1.4.1"
|
pretty_assertions = "1.4.1"
|
||||||
|
bollard = "0.19.1"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
tar = "0.4.44"
|
||||||
|
@ -14,6 +14,7 @@ async fn main() {
|
|||||||
|
|
||||||
maestro.register_all(vec![Box::new(NtfyScore {
|
maestro.register_all(vec![Box::new(NtfyScore {
|
||||||
namespace: "monitoring".to_string(),
|
namespace: "monitoring".to_string(),
|
||||||
|
host: "localhost".to_string(),
|
||||||
})]);
|
})]);
|
||||||
harmony_cli::init(maestro, None).await.unwrap();
|
harmony_cli::init(maestro, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -12,3 +12,4 @@ tokio = { workspace = true }
|
|||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
env_logger = { workspace = true }
|
env_logger = { workspace = true }
|
||||||
url = { workspace = true }
|
url = { workspace = true }
|
||||||
|
base64.workspace = true
|
||||||
|
@ -13,6 +13,12 @@ use harmony::{
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
env_logger::init();
|
env_logger::init();
|
||||||
|
|
||||||
|
let topology = K8sAnywhereTopology::from_env();
|
||||||
|
let mut maestro = Maestro::initialize(Inventory::autoload(), topology)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let application = Arc::new(RustWebapp {
|
let application = Arc::new(RustWebapp {
|
||||||
name: "harmony-example-rust-webapp".to_string(),
|
name: "harmony-example-rust-webapp".to_string(),
|
||||||
domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()),
|
domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()),
|
||||||
@ -24,17 +30,14 @@ async fn main() {
|
|||||||
features: vec![
|
features: vec![
|
||||||
Box::new(ContinuousDelivery {
|
Box::new(ContinuousDelivery {
|
||||||
johnride
commented
The Tenant should not be visible at the applcation scope. An application will auto detect the tenant it is supposed to be in from the environment (such as the content of the loaded kubeconfig). For example, a client cannot provision its own tenant as it may not have permission on the host cluster to do so. There may be exceptions and more complex use cases coming up soon, but for the sake of this example we want to keep it 100% focused on the application and especially how simple it it to deploy a fully featured infrastructure around an application with as close to zero config as possible. The Tenant should not be visible at the applcation scope. An application will auto detect the tenant it is supposed to be in from the environment (such as the content of the loaded kubeconfig).
For example, a client cannot provision its own tenant as it may not have permission on the host cluster to do so.
There may be exceptions and more complex use cases coming up soon, but for the sake of this example we want to keep it 100% focused on the application and especially how **simple** it it to deploy a fully featured infrastructure around an application with as close to zero config as possible.
|
|||||||
application: application.clone(),
|
application: application.clone(),
|
||||||
|
}), // TODO add monitoring, backups, multisite ha, etc
|
||||||
|
Box::new(Monitoring {
|
||||||
|
application: application.clone(),
|
||||||
}),
|
}),
|
||||||
Box::new(Monitoring {}),
|
|
||||||
// TODO add monitoring, backups, multisite ha, etc
|
|
||||||
],
|
],
|
||||||
application,
|
application,
|
||||||
};
|
};
|
||||||
|
|
||||||
let topology = K8sAnywhereTopology::from_env();
|
|
||||||
let mut maestro = Maestro::initialize(Inventory::autoload(), topology)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
maestro.register_all(vec![Box::new(app)]);
|
maestro.register_all(vec![Box::new(app)]);
|
||||||
harmony_cli::init(maestro, None).await.unwrap();
|
harmony_cli::init(maestro, None).await.unwrap();
|
||||||
}
|
}
|
||||||
|
@ -59,6 +59,9 @@ tokio-util = "0.7.15"
|
|||||||
strum = { version = "0.27.1", features = ["derive"] }
|
strum = { version = "0.27.1", features = ["derive"] }
|
||||||
tempfile = "3.20.0"
|
tempfile = "3.20.0"
|
||||||
serde_with = "3.14.0"
|
serde_with = "3.14.0"
|
||||||
|
bollard.workspace = true
|
||||||
|
tar.workspace = true
|
||||||
|
base64.workspace = true
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
pretty_assertions.workspace = true
|
pretty_assertions.workspace = true
|
||||||
|
@ -260,17 +260,33 @@ impl K8sClient {
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let obj: DynamicObject = serde_yaml::from_value(yaml.clone()).expect("TODO do not unwrap");
|
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 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.
|
let api_version = yaml
|
||||||
// The plural name 'applications' is taken from your CRD definition.
|
.get("apiVersion")
|
||||||
error!("This only supports argocd application harcoded, very rrrong");
|
.expect("couldn't get apiVersion from YAML")
|
||||||
let gvk = GroupVersionKind::gvk("argoproj.io", "v1alpha1", "Application");
|
.as_str()
|
||||||
let api_resource = ApiResource::from_gvk_with_plural(&gvk, "applications");
|
.expect("couldn't get apiVersion as str");
|
||||||
|
let kind = yaml
|
||||||
|
.get("kind")
|
||||||
|
.expect("couldn't get kind from YAML")
|
||||||
|
.as_str()
|
||||||
|
.expect("couldn't get kind as str");
|
||||||
|
|
||||||
|
let split: Vec<&str> = api_version.splitn(2, "/").collect();
|
||||||
|
let g = split[0];
|
||||||
|
let v = split[1];
|
||||||
|
|
||||||
|
let gvk = GroupVersionKind::gvk(g, v, kind);
|
||||||
|
let api_resource = ApiResource::from_gvk(&gvk);
|
||||||
|
|
||||||
|
let namespace = match ns {
|
||||||
|
Some(n) => n,
|
||||||
|
None => obj
|
||||||
|
.metadata
|
||||||
|
.namespace
|
||||||
|
.as_ref()
|
||||||
|
.expect("YAML must have a namespace"),
|
||||||
|
};
|
||||||
|
|
||||||
// 5. Create a dynamic API client for this resource type.
|
// 5. Create a dynamic API client for this resource type.
|
||||||
let api: Api<DynamicObject> =
|
let api: Api<DynamicObject> =
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
use std::{backtrace, collections::HashMap};
|
|
||||||
|
|
||||||
use k8s_openapi::{Metadata, NamespaceResourceScope, Resource};
|
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_with::skip_serializing_none;
|
use serde_with::skip_serializing_none;
|
||||||
use serde_yaml::Value;
|
use serde_yaml::Value;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::modules::application::features::CDApplicationConfig;
|
use crate::modules::application::features::CDApplicationConfig;
|
||||||
|
|
||||||
@ -33,11 +29,14 @@ pub struct Helm {
|
|||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct Source {
|
pub struct Source {
|
||||||
|
// Using string for this because URL enforces a URL scheme at the beginning but Helm, ArgoCD, etc do not, and it can be counterproductive,
|
||||||
|
// as the only way I've found to get OCI working isn't by using oci:// but rather no scheme at all
|
||||||
#[serde(rename = "repoURL")]
|
#[serde(rename = "repoURL")]
|
||||||
pub repo_url: Url,
|
pub repo_url: String,
|
||||||
pub target_revision: Option<String>,
|
pub target_revision: Option<String>,
|
||||||
pub chart: String,
|
pub chart: String,
|
||||||
pub helm: Helm,
|
pub helm: Helm,
|
||||||
|
pub path: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize)]
|
#[derive(Clone, Debug, Serialize)]
|
||||||
@ -90,7 +89,7 @@ impl Default for ArgoApplication {
|
|||||||
namespace: Default::default(),
|
namespace: Default::default(),
|
||||||
project: Default::default(),
|
project: Default::default(),
|
||||||
wjro
commented
I would write some documentation for why you replaced url with string I would write some documentation for why you replaced url with string
|
|||||||
source: Source {
|
source: Source {
|
||||||
repo_url: Url::parse("http://asdf").expect("Couldn't parse to URL"),
|
repo_url: "http://asdf".to_string(),
|
||||||
target_revision: None,
|
target_revision: None,
|
||||||
chart: "".to_string(),
|
chart: "".to_string(),
|
||||||
helm: Helm {
|
helm: Helm {
|
||||||
@ -109,6 +108,7 @@ impl Default for ArgoApplication {
|
|||||||
api_versions: vec![],
|
api_versions: vec![],
|
||||||
namespace: None,
|
namespace: None,
|
||||||
},
|
},
|
||||||
|
path: "".to_string(),
|
||||||
},
|
},
|
||||||
sync_policy: SyncPolicy {
|
sync_policy: SyncPolicy {
|
||||||
automated: Automated {
|
automated: Automated {
|
||||||
@ -138,10 +138,10 @@ impl From<CDApplicationConfig> for ArgoApplication {
|
|||||||
namespace: Some(value.namespace),
|
namespace: Some(value.namespace),
|
||||||
project: "default".to_string(),
|
project: "default".to_string(),
|
||||||
source: Source {
|
source: Source {
|
||||||
repo_url: Url::parse(value.helm_chart_repo_url.to_string().as_str())
|
repo_url: value.helm_chart_repo_url,
|
||||||
.expect("couldn't convert to URL"),
|
|
||||||
target_revision: Some(value.version.to_string()),
|
target_revision: Some(value.version.to_string()),
|
||||||
chart: value.helm_chart_name,
|
chart: value.helm_chart_name.clone(),
|
||||||
|
path: value.helm_chart_name,
|
||||||
helm: Helm {
|
helm: Helm {
|
||||||
pass_credentials: None,
|
pass_credentials: None,
|
||||||
parameters: vec![],
|
parameters: vec![],
|
||||||
@ -218,7 +218,7 @@ spec:
|
|||||||
let mut yaml_value: Value =
|
let mut yaml_value: Value =
|
||||||
serde_yaml::from_str(yaml_str.as_str()).expect("couldn't parse string to YAML");
|
serde_yaml::from_str(yaml_str.as_str()).expect("couldn't parse string to YAML");
|
||||||
|
|
||||||
let mut spec = yaml_value
|
let spec = yaml_value
|
||||||
.get_mut("spec")
|
.get_mut("spec")
|
||||||
.expect("couldn't get spec from yaml")
|
.expect("couldn't get spec from yaml")
|
||||||
.as_mapping_mut()
|
.as_mapping_mut()
|
||||||
@ -258,7 +258,6 @@ spec:
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use url::Url;
|
|
||||||
|
|
||||||
use crate::modules::application::features::{
|
use crate::modules::application::features::{
|
||||||
ArgoApplication, Automated, Backoff, Helm, Retry, Source, SyncPolicy,
|
ArgoApplication, Automated, Backoff, Helm, Retry, Source, SyncPolicy,
|
||||||
@ -271,7 +270,7 @@ mod tests {
|
|||||||
namespace: Some("test-ns".to_string()),
|
namespace: Some("test-ns".to_string()),
|
||||||
project: "test-project".to_string(),
|
project: "test-project".to_string(),
|
||||||
source: Source {
|
source: Source {
|
||||||
repo_url: Url::parse("http://test").unwrap(),
|
repo_url: "http://test".to_string(),
|
||||||
target_revision: None,
|
target_revision: None,
|
||||||
chart: "test-chart".to_string(),
|
chart: "test-chart".to_string(),
|
||||||
helm: Helm {
|
helm: Helm {
|
||||||
@ -290,6 +289,7 @@ mod tests {
|
|||||||
api_versions: vec![],
|
api_versions: vec![],
|
||||||
namespace: None,
|
namespace: None,
|
||||||
},
|
},
|
||||||
|
path: "".to_string(),
|
||||||
},
|
},
|
||||||
sync_policy: SyncPolicy {
|
sync_policy: SyncPolicy {
|
||||||
automated: Automated {
|
automated: Automated {
|
||||||
@ -321,7 +321,7 @@ spec:
|
|||||||
server: https://kubernetes.default.svc
|
server: https://kubernetes.default.svc
|
||||||
namespace: test-ns
|
namespace: test-ns
|
||||||
source:
|
source:
|
||||||
repoURL: http://test/
|
repoURL: http://test
|
||||||
chart: test-chart
|
chart: test-chart
|
||||||
helm:
|
helm:
|
||||||
parameters: []
|
parameters: []
|
||||||
@ -329,6 +329,7 @@ spec:
|
|||||||
releaseName: test-release-neame
|
releaseName: test-release-neame
|
||||||
valueFiles: []
|
valueFiles: []
|
||||||
apiVersions: []
|
apiVersions: []
|
||||||
|
path: ''
|
||||||
syncPolicy:
|
syncPolicy:
|
||||||
automated:
|
automated:
|
||||||
prune: false
|
prune: false
|
||||||
|
@ -9,15 +9,12 @@ use crate::{
|
|||||||
config::HARMONY_DATA_DIR,
|
config::HARMONY_DATA_DIR,
|
||||||
data::Version,
|
data::Version,
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::application::{
|
||||||
application::{
|
Application, ApplicationFeature, HelmPackage, OCICompliant,
|
||||||
Application, ApplicationFeature, HelmPackage, OCICompliant,
|
features::{ArgoApplication, ArgoHelmScore},
|
||||||
features::{ArgoApplication, ArgoHelmScore},
|
|
||||||
},
|
|
||||||
helm::chart::HelmChartScore,
|
|
||||||
},
|
},
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology, Url},
|
topology::{DeploymentTarget, HelmCommand, K8sclient, MultiTargetTopology, Topology},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// ContinuousDelivery in Harmony provides this functionality :
|
/// ContinuousDelivery in Harmony provides this functionality :
|
||||||
@ -162,8 +159,8 @@ impl<
|
|||||||
info!("Pushed new helm chart {helm_chart}");
|
info!("Pushed new helm chart {helm_chart}");
|
||||||
|
|
||||||
error!("TODO Make building image configurable/skippable");
|
error!("TODO Make building image configurable/skippable");
|
||||||
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}");
|
||||||
|
|
||||||
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
|
||||||
@ -188,12 +185,12 @@ impl<
|
|||||||
info!("Deploying to target {target:?}");
|
info!("Deploying to target {target:?}");
|
||||||
let score = ArgoHelmScore {
|
let score = ArgoHelmScore {
|
||||||
namespace: "harmonydemo-staging".to_string(),
|
namespace: "harmonydemo-staging".to_string(),
|
||||||
openshift: true,
|
openshift: false,
|
||||||
domain: "argo.harmonydemo.apps.st.mcd".to_string(),
|
domain: "argo.harmonydemo.apps.st.mcd".to_string(),
|
||||||
argo_apps: vec![ArgoApplication::from(CDApplicationConfig {
|
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
|
// helm pull oci://hub.nationtech.io/harmony/harmony-example-rust-webapp-chart --version 0.1.0
|
||||||
version: Version::from("0.1.0").unwrap(),
|
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_repo_url: "hub.nationtech.io/harmony".to_string(),
|
||||||
helm_chart_name: "harmony-example-rust-webapp-chart".to_string(),
|
helm_chart_name: "harmony-example-rust-webapp-chart".to_string(),
|
||||||
values_overrides: None,
|
values_overrides: None,
|
||||||
name: "harmony-demo-rust-webapp".to_string(),
|
name: "harmony-demo-rust-webapp".to_string(),
|
||||||
@ -207,14 +204,7 @@ impl<
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
Ok(())
|
||||||
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
|
|
||||||
- [X] Push only if staging or prod
|
|
||||||
- [X] Deploy to local k3d when target is local
|
|
||||||
- [ ] Poke Argo
|
|
||||||
- [ ] Ensure app is up")
|
|
||||||
}
|
}
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
"ContinuousDelivery".to_string()
|
"ContinuousDelivery".to_string()
|
||||||
@ -225,7 +215,7 @@ impl<
|
|||||||
/// more CD systems
|
/// more CD systems
|
||||||
pub struct CDApplicationConfig {
|
pub struct CDApplicationConfig {
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub helm_chart_repo_url: Url,
|
pub helm_chart_repo_url: String,
|
||||||
pub helm_chart_name: String,
|
pub helm_chart_name: String,
|
||||||
pub values_overrides: Option<Value>,
|
pub values_overrides: Option<Value>,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use k8s_openapi::Resource;
|
|
||||||
use log::error;
|
use log::error;
|
||||||
use non_blank_string_rs::NonBlankString;
|
use non_blank_string_rs::NonBlankString;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@ -647,7 +646,7 @@ server:
|
|||||||
# Argo CD server ingress configuration
|
# Argo CD server ingress configuration
|
||||||
ingress:
|
ingress:
|
||||||
# -- Enable an ingress resource for the Argo CD server
|
# -- Enable an ingress resource for the Argo CD server
|
||||||
enabled: false
|
enabled: true
|
||||||
# -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke`
|
# -- Specific implementation for ingress controller. One of `generic`, `aws` or `gke`
|
||||||
## Additional configuration might be required in related configuration sections
|
## Additional configuration might be required in related configuration sections
|
||||||
controller: generic
|
controller: generic
|
||||||
|
@ -1,36 +1,99 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::info;
|
use base64::{Engine as _, engine::general_purpose};
|
||||||
|
use log::{debug, info};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
inventory::Inventory,
|
inventory::Inventory,
|
||||||
modules::{
|
modules::{
|
||||||
application::{Application, ApplicationFeature},
|
application::{ApplicationFeature, OCICompliant},
|
||||||
monitoring::{
|
monitoring::{
|
||||||
application_monitoring::k8s_application_monitoring_score::ApplicationPrometheusMonitoringScore,
|
alert_channel::webhook_receiver::WebhookReceiver,
|
||||||
kube_prometheus::{
|
kube_prometheus::{
|
||||||
helm_prometheus_alert_score::HelmPrometheusAlertingScore,
|
helm_prometheus_alert_score::HelmPrometheusAlertingScore,
|
||||||
types::{NamespaceSelector, ServiceMonitor},
|
types::{NamespaceSelector, ServiceMonitor},
|
||||||
},
|
},
|
||||||
|
ntfy::ntfy::NtfyScore,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
score::Score,
|
score::Score,
|
||||||
topology::{HelmCommand, Topology, tenant::TenantManager},
|
topology::{HelmCommand, K8sclient, Topology, Url, tenant::TenantManager},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
taha marked this conversation as resolved
Outdated
wjro
commented
if we add pub application: Arc to monitoring we can use the application name as namespace so that it is consistent with the way continuous _delivery feature gets the namespace if we add pub application: Arc<dyn Application> to monitoring we can use the application name as namespace so that it is consistent with the way continuous _delivery feature gets the namespace
|
|||||||
pub struct Monitoring {}
|
pub struct Monitoring {
|
||||||
|
pub application: Arc<dyn OCICompliant>,
|
||||||
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl<T: Topology + HelmCommand + 'static + TenantManager> ApplicationFeature<T> for Monitoring {
|
impl<T: Topology + HelmCommand + K8sclient + 'static + TenantManager> ApplicationFeature<T>
|
||||||
|
for Monitoring
|
||||||
|
{
|
||||||
async fn ensure_installed(&self, topology: &T) -> Result<(), String> {
|
async fn ensure_installed(&self, topology: &T) -> Result<(), String> {
|
||||||
info!("Ensuring monitoring is available for application");
|
info!("Ensuring monitoring is available for application");
|
||||||
|
|
||||||
|
let ntfy = NtfyScore {
|
||||||
|
// namespace: topology
|
||||||
|
// .get_tenant_config()
|
||||||
|
// .await
|
||||||
taha marked this conversation as resolved
wjro
commented
rather than hardcoding namespace here rather than hardcoding namespace here
|
|||||||
|
// .expect("couldn't get tenant config")
|
||||||
|
// .name,
|
||||||
|
namespace: self.application.name(),
|
||||||
|
host: "localhost".to_string(),
|
||||||
|
};
|
||||||
|
ntfy.create_interpret()
|
||||||
|
.execute(&Inventory::empty(), topology)
|
||||||
|
.await
|
||||||
|
.expect("couldn't create interpret for ntfy");
|
||||||
|
|
||||||
|
let ntfy_default_auth_username = "harmony";
|
||||||
|
let ntfy_default_auth_password = "harmony";
|
||||||
|
let ntfy_default_auth_header = format!(
|
||||||
|
"Basic {}",
|
||||||
|
general_purpose::STANDARD.encode(format!(
|
||||||
|
"{ntfy_default_auth_username}:{ntfy_default_auth_password}"
|
||||||
|
))
|
||||||
|
);
|
||||||
|
|
||||||
|
debug!("ntfy_default_auth_header: {ntfy_default_auth_header}");
|
||||||
|
|
||||||
|
let ntfy_default_auth_param = general_purpose::STANDARD
|
||||||
|
.encode(ntfy_default_auth_header)
|
||||||
|
.replace("=", "");
|
||||||
|
|
||||||
|
debug!("ntfy_default_auth_param: {ntfy_default_auth_param}");
|
||||||
|
|
||||||
|
let ntfy_receiver = WebhookReceiver {
|
||||||
|
name: "ntfy-webhook".to_string(),
|
||||||
|
url: Url::Url(
|
||||||
|
url::Url::parse(
|
||||||
|
format!(
|
||||||
|
"http://ntfy.{}.svc.cluster.local/rust-web-app?auth={ntfy_default_auth_param}",
|
||||||
|
self.application.name()
|
||||||
|
)
|
||||||
|
.as_str(),
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
let mut service_monitor = ServiceMonitor::default();
|
let mut service_monitor = ServiceMonitor::default();
|
||||||
service_monitor.namespace_selector = Some(NamespaceSelector {
|
service_monitor.namespace_selector = Some(NamespaceSelector {
|
||||||
any: true,
|
any: true,
|
||||||
match_names: vec![],
|
match_names: vec![],
|
||||||
});
|
});
|
||||||
let alerting_score = ApplicationPrometheusMonitoringScore {
|
|
||||||
receivers: vec![],
|
service_monitor.name = "rust-webapp".to_string();
|
||||||
|
|
||||||
|
// let alerting_score = ApplicationPrometheusMonitoringScore {
|
||||||
|
// receivers: vec![Box::new(ntfy_receiver)],
|
||||||
|
// rules: vec![],
|
||||||
|
// service_monitors: vec![service_monitor],
|
||||||
|
// };
|
||||||
|
|
||||||
|
let alerting_score = HelmPrometheusAlertingScore {
|
||||||
|
receivers: vec![Box::new(ntfy_receiver)],
|
||||||
rules: vec![],
|
rules: vec![],
|
||||||
service_monitors: vec![service_monitor],
|
service_monitors: vec![service_monitor],
|
||||||
};
|
};
|
||||||
|
@ -59,9 +59,7 @@ impl<A: Application, T: Topology + std::fmt::Debug> Interpret<T> for Application
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
todo!(
|
Ok(Outcome::success("successfully created app".to_string()))
|
||||||
"Do I need to do anything more than this here?? I feel like the Application trait itself should expose something like ensure_ready but its becoming redundant. We'll see as this evolves."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> InterpretName {
|
fn get_name(&self) -> InterpretName {
|
||||||
|
@ -4,11 +4,15 @@ use std::process;
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use bollard::query_parameters::PushImageOptionsBuilder;
|
||||||
|
use bollard::{Docker, body_full};
|
||||||
wjro marked this conversation as resolved
wjro
commented
add a comment to justify using bollard vs docker via the cli add a comment to justify using bollard vs docker via the cli
taha
commented
The reason I used The reason I used `bollard` was because I find it preferable to access the docker daemon programmatically over an API over using a (albeit relatively stable) CLI interface
|
|||||||
use dockerfile_builder::Dockerfile;
|
use dockerfile_builder::Dockerfile;
|
||||||
use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR};
|
use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, USER, WORKDIR};
|
||||||
use dockerfile_builder::instruction_builder::CopyBuilder;
|
use dockerfile_builder::instruction_builder::CopyBuilder;
|
||||||
|
use futures_util::StreamExt;
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
use tar::Archive;
|
||||||
|
|
||||||
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
|
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -108,6 +112,7 @@ impl OCICompliant for RustWebapp {
|
|||||||
// 1. Build the local image by calling the synchronous helper function.
|
// 1. Build the local image by calling the synchronous helper function.
|
||||||
let local_image_name = self.local_image_name();
|
let local_image_name = self.local_image_name();
|
||||||
self.build_docker_image(&local_image_name)
|
self.build_docker_image(&local_image_name)
|
||||||
|
.await
|
||||||
.map_err(|e| format!("Failed to build Docker image: {}", e))?;
|
.map_err(|e| format!("Failed to build Docker image: {}", e))?;
|
||||||
info!(
|
info!(
|
||||||
"Successfully built local Docker image: {}",
|
"Successfully built local Docker image: {}",
|
||||||
@ -117,6 +122,7 @@ impl OCICompliant for RustWebapp {
|
|||||||
let remote_image_name = self.image_name();
|
let remote_image_name = self.image_name();
|
||||||
// 2. Push the image to the registry.
|
// 2. Push the image to the registry.
|
||||||
self.push_docker_image(&local_image_name, &remote_image_name)
|
self.push_docker_image(&local_image_name, &remote_image_name)
|
||||||
|
.await
|
||||||
.map_err(|e| format!("Failed to push Docker image: {}", e))?;
|
.map_err(|e| format!("Failed to push Docker image: {}", e))?;
|
||||||
info!("Successfully pushed Docker image to: {}", remote_image_name);
|
info!("Successfully pushed Docker image to: {}", remote_image_name);
|
||||||
|
|
||||||
@ -153,66 +159,68 @@ impl RustWebapp {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Builds the Docker image using the generated Dockerfile.
|
/// Builds the Docker image using the generated Dockerfile.
|
||||||
pub fn build_docker_image(
|
pub async fn build_docker_image(
|
||||||
&self,
|
&self,
|
||||||
image_name: &str,
|
image_name: &str,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
info!("Generating Dockerfile for '{}'", self.name);
|
info!("Generating Dockerfile for '{}'", self.name);
|
||||||
let dockerfile_path = self.build_dockerfile()?;
|
let _dockerfile_path = self.build_dockerfile()?;
|
||||||
|
|
||||||
info!(
|
let docker = Docker::connect_with_socket_defaults().unwrap();
|
||||||
"Building Docker image with file {} from root {}",
|
|
||||||
dockerfile_path.to_string_lossy(),
|
let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default()
|
||||||
self.project_root.to_string_lossy()
|
.dockerfile("Dockerfile.harmony")
|
||||||
|
.t(image_name)
|
||||||
|
.q(false)
|
||||||
|
.version(bollard::query_parameters::BuilderVersion::BuilderV1)
|
||||||
|
.platform("linux/x86_64");
|
||||||
|
|
||||||
|
let mut temp_tar_builder = tar::Builder::new(Vec::new());
|
||||||
|
let _ = temp_tar_builder
|
||||||
|
.append_dir_all("", self.project_root.clone())
|
||||||
|
.unwrap();
|
||||||
|
let archive = temp_tar_builder
|
||||||
|
.into_inner()
|
||||||
|
.expect("couldn't finish creating tar");
|
||||||
|
let archived_files = Archive::new(archive.as_slice())
|
||||||
|
.entries()
|
||||||
|
.unwrap()
|
||||||
|
.map(|entry| entry.unwrap().path().unwrap().into_owned())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
debug!("files in docker tar: {:#?}", archived_files);
|
||||||
|
|
||||||
|
let mut image_build_stream = docker.build_image(
|
||||||
|
build_image_options.build(),
|
||||||
|
None,
|
||||||
|
Some(body_full(archive.into())),
|
||||||
);
|
);
|
||||||
let output = process::Command::new("docker")
|
|
||||||
.args([
|
|
||||||
"build",
|
|
||||||
"--file",
|
|
||||||
dockerfile_path.to_str().unwrap(),
|
|
||||||
"-t",
|
|
||||||
&image_name,
|
|
||||||
self.project_root.to_str().unwrap(),
|
|
||||||
])
|
|
||||||
.spawn()?
|
|
||||||
.wait_with_output()?;
|
|
||||||
|
|
||||||
self.check_output(&output, "Failed to build Docker image")?;
|
while let Some(msg) = image_build_stream.next().await {
|
||||||
|
println!("Message: {msg:?}");
|
||||||
|
}
|
||||||
|
|
||||||
Ok(image_name.to_string())
|
Ok(image_name.to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tags and pushes a Docker image to the configured remote registry.
|
/// Tags and pushes a Docker image to the configured remote registry.
|
||||||
fn push_docker_image(
|
async fn push_docker_image(
|
||||||
&self,
|
&self,
|
||||||
image_name: &str,
|
image_name: &str,
|
||||||
full_tag: &str,
|
full_tag: &str,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
info!("Pushing docker image {full_tag}");
|
info!("Pushing docker image {full_tag}");
|
||||||
|
|
||||||
// Tag the image for the remote registry.
|
let docker = Docker::connect_with_socket_defaults().unwrap();
|
||||||
let output = process::Command::new("docker")
|
|
||||||
.args(["tag", image_name, &full_tag])
|
|
||||||
.spawn()?
|
|
||||||
.wait_with_output()?;
|
|
||||||
self.check_output(&output, "Tagging docker image failed")?;
|
|
||||||
debug!(
|
|
||||||
"docker tag output: stdout: {}, stderr: {}",
|
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Push the image.
|
// let push_options = PushImageOptionsBuilder::new().tag(tag);
|
||||||
let output = process::Command::new("docker")
|
|
||||||
.args(["push", &full_tag])
|
let mut push_image_stream =
|
||||||
.spawn()?
|
docker.push_image(full_tag, Some(PushImageOptionsBuilder::new().build()), None);
|
||||||
.wait_with_output()?;
|
|
||||||
self.check_output(&output, "Pushing docker image failed")?;
|
while let Some(msg) = push_image_stream.next().await {
|
||||||
debug!(
|
println!("Message: {msg:?}");
|
||||||
"docker push output: stdout: {}, stderr: {}",
|
}
|
||||||
String::from_utf8_lossy(&output.stdout),
|
|
||||||
String::from_utf8_lossy(&output.stderr)
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(full_tag.to_string())
|
Ok(full_tag.to_string())
|
||||||
}
|
}
|
||||||
@ -349,7 +357,11 @@ impl RustWebapp {
|
|||||||
image_url: &str,
|
image_url: &str,
|
||||||
) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
let chart_name = format!("{}-chart", self.name);
|
let chart_name = format!("{}-chart", self.name);
|
||||||
let chart_dir = self.project_root.join("helm").join(&chart_name);
|
let chart_dir = self
|
||||||
|
.project_root
|
||||||
|
.join(".harmony_generated")
|
||||||
|
.join("helm")
|
||||||
|
.join(&chart_name);
|
||||||
let templates_dir = chart_dir.join("templates");
|
let templates_dir = chart_dir.join("templates");
|
||||||
fs::create_dir_all(&templates_dir)?;
|
fs::create_dir_all(&templates_dir)?;
|
||||||
|
|
||||||
@ -416,7 +428,7 @@ ingress:
|
|||||||
Expand the name of the chart.
|
Expand the name of the chart.
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "chart.name" -}}
|
{{- define "chart.name" -}}
|
||||||
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
{{- default .Chart.Name $.Values.nameOverride | trunc 63 | trimSuffix "-" }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
|
|
||||||
{{/*
|
{{/*
|
||||||
@ -424,7 +436,7 @@ Create a default fully qualified app name.
|
|||||||
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
|
||||||
*/}}
|
*/}}
|
||||||
{{- define "chart.fullname" -}}
|
{{- define "chart.fullname" -}}
|
||||||
{{- $name := default .Chart.Name .Values.nameOverride }}
|
{{- $name := default .Chart.Name $.Values.nameOverride }}
|
||||||
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
"#;
|
"#;
|
||||||
@ -437,12 +449,12 @@ kind: Service
|
|||||||
metadata:
|
metadata:
|
||||||
name: {{ include "chart.fullname" . }}
|
name: {{ include "chart.fullname" . }}
|
||||||
spec:
|
spec:
|
||||||
johnride
commented
What does the What does the `$` do ?
taha
commented
`$` is the global scope, for some reason `.Values.service.type` on its own wasn't working (different scope?), did some digging, found `$` to specifically reference the global/top level scope and it then worked. I'm not an expert at helm templating though
|
|||||||
type: {{ .Values.service.type }}
|
type: {{ $.Values.service.type }}
|
||||||
ports:
|
ports:
|
||||||
- port: {{ .Values.service.port }}
|
- name: main
|
||||||
targetPort: 3000
|
port: {{ $.Values.service.port | default 3000 }}
|
||||||
johnride
commented
Thanks for fixing my demo hardcoding crap 😅 Thanks for fixing my demo hardcoding crap 😅
|
|||||||
|
targetPort: {{ $.Values.service.port | default 3000 }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
name: http
|
|
||||||
selector:
|
selector:
|
||||||
app: {{ include "chart.name" . }}
|
app: {{ include "chart.name" . }}
|
||||||
"#;
|
"#;
|
||||||
@ -455,7 +467,7 @@ kind: Deployment
|
|||||||
metadata:
|
metadata:
|
||||||
name: {{ include "chart.fullname" . }}
|
name: {{ include "chart.fullname" . }}
|
||||||
spec:
|
spec:
|
||||||
replicas: {{ .Values.replicaCount }}
|
replicas: {{ $.Values.replicaCount }}
|
||||||
selector:
|
selector:
|
||||||
matchLabels:
|
matchLabels:
|
||||||
app: {{ include "chart.name" . }}
|
app: {{ include "chart.name" . }}
|
||||||
@ -466,28 +478,28 @@ spec:
|
|||||||
spec:
|
spec:
|
||||||
containers:
|
containers:
|
||||||
- name: {{ .Chart.Name }}
|
- name: {{ .Chart.Name }}
|
||||||
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
image: "{{ $.Values.image.repository }}:{{ $.Values.image.tag | default .Chart.AppVersion }}"
|
||||||
imagePullPolicy: {{ .Values.image.pullPolicy }}
|
imagePullPolicy: {{ $.Values.image.pullPolicy }}
|
||||||
ports:
|
ports:
|
||||||
- name: http
|
- name: main
|
||||||
containerPort: 3000
|
containerPort: {{ $.Values.service.port | default 3000 }}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
"#;
|
"#;
|
||||||
fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?;
|
fs::write(templates_dir.join("deployment.yaml"), deployment_yaml)?;
|
||||||
|
|
||||||
// Create templates/ingress.yaml
|
// Create templates/ingress.yaml
|
||||||
let ingress_yaml = r#"
|
let ingress_yaml = r#"
|
||||||
{{- if .Values.ingress.enabled -}}
|
{{- if $.Values.ingress.enabled -}}
|
||||||
apiVersion: networking.k8s.io/v1
|
apiVersion: networking.k8s.io/v1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ include "chart.fullname" . }}
|
name: {{ include "chart.fullname" . }}
|
||||||
annotations:
|
annotations:
|
||||||
{{- toYaml .Values.ingress.annotations | nindent 4 }}
|
{{- toYaml $.Values.ingress.annotations | nindent 4 }}
|
||||||
spec:
|
spec:
|
||||||
{{- if .Values.ingress.tls }}
|
{{- if $.Values.ingress.tls }}
|
||||||
tls:
|
tls:
|
||||||
{{- range .Values.ingress.tls }}
|
{{- range $.Values.ingress.tls }}
|
||||||
- hosts:
|
- hosts:
|
||||||
{{- range .hosts }}
|
{{- range .hosts }}
|
||||||
- {{ . | quote }}
|
- {{ . | quote }}
|
||||||
@ -496,7 +508,7 @@ spec:
|
|||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
rules:
|
rules:
|
||||||
{{- range .Values.ingress.hosts }}
|
{{- range $.Values.ingress.hosts }}
|
||||||
- host: {{ .host | quote }}
|
- host: {{ .host | quote }}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
@ -507,7 +519,7 @@ spec:
|
|||||||
service:
|
service:
|
||||||
name: {{ include "chart.fullname" $ }}
|
name: {{ include "chart.fullname" $ }}
|
||||||
port:
|
port:
|
||||||
number: 3000
|
number: {{ $.Values.service.port | default 3000 }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
{{- end }}
|
{{- end }}
|
||||||
@ -526,11 +538,15 @@ spec:
|
|||||||
info!(
|
info!(
|
||||||
"Launching `helm package {}` cli with CWD {}",
|
"Launching `helm package {}` cli with CWD {}",
|
||||||
chart_dirname.to_string_lossy(),
|
chart_dirname.to_string_lossy(),
|
||||||
&self.project_root.join("helm").to_string_lossy()
|
&self
|
||||||
|
.project_root
|
||||||
|
.join(".harmony_generated")
|
||||||
|
.join("helm")
|
||||||
|
.to_string_lossy()
|
||||||
);
|
);
|
||||||
let output = process::Command::new("helm")
|
let output = process::Command::new("helm")
|
||||||
.args(["package", chart_dirname.to_str().unwrap()])
|
.args(["package", chart_dirname.to_str().unwrap()])
|
||||||
.current_dir(&self.project_root.join("helm")) // Run package from the parent dir
|
.current_dir(&self.project_root.join(".harmony_generated").join("helm")) // Run package from the parent dir
|
||||||
.output()?;
|
.output()?;
|
||||||
|
|
||||||
self.check_output(&output, "Failed to package Helm chart")?;
|
self.check_output(&output, "Failed to package Helm chart")?;
|
||||||
@ -547,7 +563,11 @@ spec:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The output from helm is relative, so we join it with the execution directory.
|
// The output from helm is relative, so we join it with the execution directory.
|
||||||
Ok(self.project_root.join("helm").join(tgz_name))
|
Ok(self
|
||||||
|
.project_root
|
||||||
|
.join(".harmony_generated")
|
||||||
|
.join("helm")
|
||||||
|
.join(tgz_name))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a packaged Helm chart to an OCI registry.
|
/// Pushes a packaged Helm chart to an OCI registry.
|
||||||
|
@ -24,7 +24,10 @@ pub struct ApplicationPrometheusMonitoringScore {
|
|||||||
|
|
||||||
impl<T: Topology + HelmCommand + TenantManager> Score<T> for ApplicationPrometheusMonitoringScore {
|
impl<T: Topology + HelmCommand + TenantManager> Score<T> for ApplicationPrometheusMonitoringScore {
|
||||||
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn crate::interpret::Interpret<T>> {
|
||||||
let config = Arc::new(Mutex::new(PrometheusConfig::new()));
|
let mut prom_config = PrometheusConfig::new();
|
||||||
|
prom_config.alert_manager = true;
|
||||||
|
|
||||||
|
let config = Arc::new(Mutex::new(prom_config));
|
||||||
config
|
config
|
||||||
.try_lock()
|
.try_lock()
|
||||||
.expect("couldn't lock config")
|
.expect("couldn't lock config")
|
||||||
|
@ -38,15 +38,15 @@ impl KubePrometheusConfig {
|
|||||||
node_exporter: false,
|
node_exporter: false,
|
||||||
prometheus: true,
|
prometheus: true,
|
||||||
kubernetes_service_monitors: true,
|
kubernetes_service_monitors: true,
|
||||||
kubernetes_api_server: false,
|
kubernetes_api_server: true,
|
||||||
kubelet: true,
|
kubelet: true,
|
||||||
kube_controller_manager: false,
|
kube_controller_manager: true,
|
||||||
kube_etcd: false,
|
kube_etcd: true,
|
||||||
kube_proxy: false,
|
kube_proxy: true,
|
||||||
kube_state_metrics: true,
|
kube_state_metrics: true,
|
||||||
prometheus_operator: true,
|
prometheus_operator: true,
|
||||||
core_dns: false,
|
core_dns: true,
|
||||||
kube_scheduler: false,
|
kube_scheduler: true,
|
||||||
alert_receiver_configs: vec![],
|
alert_receiver_configs: vec![],
|
||||||
alert_rules: vec![],
|
alert_rules: vec![],
|
||||||
additional_service_monitors: vec![],
|
additional_service_monitors: vec![],
|
||||||
|
@ -70,12 +70,12 @@ pub fn kube_prometheus_helm_chart_score(
|
|||||||
r#"
|
r#"
|
||||||
global:
|
global:
|
||||||
rbac:
|
rbac:
|
||||||
create: false
|
create: true
|
||||||
prometheus:
|
prometheus:
|
||||||
enabled: {prometheus}
|
enabled: {prometheus}
|
||||||
prometheusSpec:
|
prometheusSpec:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 500Mi
|
memory: 500Mi
|
||||||
limits:
|
limits:
|
||||||
@ -121,7 +121,7 @@ defaultRules:
|
|||||||
windowsMonitoring:
|
windowsMonitoring:
|
||||||
enabled: {windows_monitoring}
|
enabled: {windows_monitoring}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -130,13 +130,13 @@ windowsMonitoring:
|
|||||||
grafana:
|
grafana:
|
||||||
enabled: {grafana}
|
enabled: {grafana}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
cpu: 200m
|
cpu: 200m
|
||||||
memory: 250Mi
|
memory: 250Mi
|
||||||
initChownData:
|
initChownData:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 10m
|
cpu: 10m
|
||||||
@ -157,7 +157,7 @@ kubernetesServiceMonitors:
|
|||||||
kubeApiServer:
|
kubeApiServer:
|
||||||
enabled: {kubernetes_api_server}
|
enabled: {kubernetes_api_server}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -166,7 +166,7 @@ kubeApiServer:
|
|||||||
kubelet:
|
kubelet:
|
||||||
enabled: {kubelet}
|
enabled: {kubelet}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -175,7 +175,7 @@ kubelet:
|
|||||||
kubeControllerManager:
|
kubeControllerManager:
|
||||||
enabled: {kube_controller_manager}
|
enabled: {kube_controller_manager}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -184,7 +184,7 @@ kubeControllerManager:
|
|||||||
coreDns:
|
coreDns:
|
||||||
enabled: {core_dns}
|
enabled: {core_dns}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -193,7 +193,7 @@ coreDns:
|
|||||||
kubeEtcd:
|
kubeEtcd:
|
||||||
enabled: {kube_etcd}
|
enabled: {kube_etcd}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -202,7 +202,7 @@ kubeEtcd:
|
|||||||
kubeScheduler:
|
kubeScheduler:
|
||||||
enabled: {kube_scheduler}
|
enabled: {kube_scheduler}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -211,7 +211,7 @@ kubeScheduler:
|
|||||||
kubeProxy:
|
kubeProxy:
|
||||||
enabled: {kube_proxy}
|
enabled: {kube_proxy}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -221,7 +221,7 @@ kubeStateMetrics:
|
|||||||
enabled: {kube_state_metrics}
|
enabled: {kube_state_metrics}
|
||||||
kube-state-metrics:
|
kube-state-metrics:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -230,7 +230,7 @@ kube-state-metrics:
|
|||||||
nodeExporter:
|
nodeExporter:
|
||||||
enabled: {node_exporter}
|
enabled: {node_exporter}
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -238,16 +238,16 @@ nodeExporter:
|
|||||||
memory: 250Mi
|
memory: 250Mi
|
||||||
prometheus-node-exporter:
|
prometheus-node-exporter:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
cpu: 200m
|
cpu: 200m
|
||||||
memory: 250Mi
|
memory: 250Mi
|
||||||
prometheusOperator:
|
prometheusOperator:
|
||||||
enabled: false
|
enabled: true
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -255,7 +255,7 @@ prometheusOperator:
|
|||||||
memory: 200Mi
|
memory: 200Mi
|
||||||
prometheusConfigReloader:
|
prometheusConfigReloader:
|
||||||
resources:
|
resources:
|
||||||
requests:
|
requests:
|
||||||
cpu: 100m
|
cpu: 100m
|
||||||
memory: 150Mi
|
memory: 150Mi
|
||||||
limits:
|
limits:
|
||||||
@ -267,7 +267,7 @@ prometheusOperator:
|
|||||||
limits:
|
limits:
|
||||||
cpu: 10m
|
cpu: 10m
|
||||||
memory: 100Mi
|
memory: 100Mi
|
||||||
requests:
|
requests:
|
||||||
cpu: 10m
|
cpu: 10m
|
||||||
memory: 100Mi
|
memory: 100Mi
|
||||||
patch:
|
patch:
|
||||||
@ -275,7 +275,7 @@ prometheusOperator:
|
|||||||
limits:
|
limits:
|
||||||
cpu: 10m
|
cpu: 10m
|
||||||
memory: 100Mi
|
memory: 100Mi
|
||||||
requests:
|
requests:
|
||||||
cpu: 10m
|
cpu: 10m
|
||||||
memory: 100Mi
|
memory: 100Mi
|
||||||
"#,
|
"#,
|
||||||
|
@ -28,7 +28,7 @@ impl<T: Topology + HelmCommand + TenantManager> Score<T> for HelmPrometheusAlert
|
|||||||
.expect("couldn't lock config")
|
.expect("couldn't lock config")
|
||||||
.additional_service_monitors = self.service_monitors.clone();
|
.additional_service_monitors = self.service_monitors.clone();
|
||||||
Box::new(AlertingInterpret {
|
Box::new(AlertingInterpret {
|
||||||
sender: KubePrometheus::new(),
|
sender: KubePrometheus { config },
|
||||||
receivers: self.receivers.clone(),
|
receivers: self.receivers.clone(),
|
||||||
rules: self.rules.clone(),
|
rules: self.rules.clone(),
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,7 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use crate::modules::helm::chart::{HelmChartScore, HelmRepository};
|
use crate::modules::helm::chart::{HelmChartScore, HelmRepository};
|
||||||
|
|
||||||
pub fn ntfy_helm_chart_score(namespace: String) -> HelmChartScore {
|
pub fn ntfy_helm_chart_score(namespace: String, host: String) -> HelmChartScore {
|
||||||
let values = format!(
|
let values = format!(
|
||||||
r#"
|
r#"
|
||||||
replicaCount: 1
|
replicaCount: 1
|
||||||
@ -28,12 +28,12 @@ service:
|
|||||||
port: 80
|
port: 80
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: false
|
enabled: true
|
||||||
# annotations:
|
# annotations:
|
||||||
# kubernetes.io/ingress.class: nginx
|
# kubernetes.io/ingress.class: nginx
|
||||||
# kubernetes.io/tls-acme: "true"
|
# kubernetes.io/tls-acme: "true"
|
||||||
hosts:
|
hosts:
|
||||||
- host: ntfy.host.com
|
- host: {host}
|
||||||
paths:
|
paths:
|
||||||
- path: /
|
- path: /
|
||||||
pathType: ImplementationSpecific
|
pathType: ImplementationSpecific
|
||||||
|
@ -17,6 +17,7 @@ use crate::{
|
|||||||
#[derive(Debug, Clone, Serialize)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct NtfyScore {
|
pub struct NtfyScore {
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
|
pub host: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + HelmCommand + K8sclient> Score<T> for NtfyScore {
|
impl<T: Topology + HelmCommand + K8sclient> Score<T> for NtfyScore {
|
||||||
@ -126,7 +127,7 @@ impl<T: Topology + HelmCommand + K8sclient> Interpret<T> for NtfyInterpret {
|
|||||||
inventory: &Inventory,
|
inventory: &Inventory,
|
||||||
topology: &T,
|
topology: &T,
|
||||||
) -> Result<Outcome, InterpretError> {
|
) -> Result<Outcome, InterpretError> {
|
||||||
ntfy_helm_chart_score(self.score.namespace.clone())
|
ntfy_helm_chart_score(self.score.namespace.clone(), self.score.host.clone())
|
||||||
.create_interpret()
|
.create_interpret()
|
||||||
.execute(inventory, topology)
|
.execute(inventory, topology)
|
||||||
.await?;
|
.await?;
|
||||||
|
These would be better in the examples's own gitignore file
examples/rust/.gitignore
. Eventually we may have hundreds of examples, we don't want thousands of lines in this file.But thinking a bit further, this points towards a problem on the way we manage these generated files. They should probably end up in a harmony specific directory that we can then ignore in a single line for all examples. Maybe
harmony_generated
orharmony_build
?