feat(example): added an example of packaging a rust app from github #124
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "examples/try_rust_webapp/tryrust.org"]
|
||||||
|
path = examples/try_rust_webapp/tryrust.org
|
||||||
|
url = https://github.com/rust-dd/tryrust.org.git
|
44
Cargo.lock
generated
44
Cargo.lock
generated
@ -1838,6 +1838,21 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "example-try-rust-webapp"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64 0.22.1",
|
||||||
|
"env_logger",
|
||||||
|
"harmony",
|
||||||
|
"harmony_cli",
|
||||||
|
"harmony_macros",
|
||||||
|
"harmony_types",
|
||||||
|
"log",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "example-tui"
|
name = "example-tui"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
@ -2318,6 +2333,7 @@ dependencies = [
|
|||||||
"tokio-util",
|
"tokio-util",
|
||||||
"url",
|
"url",
|
||||||
"uuid",
|
"uuid",
|
||||||
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -4955,6 +4971,15 @@ dependencies = [
|
|||||||
"cipher",
|
"cipher",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "same-file"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "schannel"
|
name = "schannel"
|
||||||
version = "0.1.27"
|
version = "0.1.27"
|
||||||
@ -6494,6 +6519,16 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "walkdir"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
|
||||||
|
dependencies = [
|
||||||
|
"same-file",
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "want"
|
name = "want"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
@ -6676,6 +6711,15 @@ version = "0.4.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "winapi-util"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0978bf7171b3d90bac376700cb56d606feb40f251a475a5d6634613564460b22"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.60.2",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "winapi-x86_64-pc-windows-gnu"
|
name = "winapi-x86_64-pc-windows-gnu"
|
||||||
version = "0.4.0"
|
version = "0.4.0"
|
||||||
|
@ -30,6 +30,7 @@ async fn main() {
|
|||||||
domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()),
|
domain: Url::Url(url::Url::parse("https://rustapp.harmony.example.com").unwrap()),
|
||||||
project_root: PathBuf::from("./examples/rust/webapp"),
|
project_root: PathBuf::from("./examples/rust/webapp"),
|
||||||
framework: Some(RustWebFramework::Leptos),
|
framework: Some(RustWebFramework::Leptos),
|
||||||
|
service_port: 3000,
|
||||||
});
|
});
|
||||||
|
|
||||||
let webhook_receiver = WebhookReceiver {
|
let webhook_receiver = WebhookReceiver {
|
||||||
|
@ -20,8 +20,9 @@ async fn main() {
|
|||||||
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()),
|
||||||
project_root: PathBuf::from("./webapp"), // Relative from 'harmony-path' param
|
project_root: PathBuf::from("./webapp"),
|
||||||
framework: Some(RustWebFramework::Leptos),
|
framework: Some(RustWebFramework::Leptos),
|
||||||
|
service_port: 3000,
|
||||||
});
|
});
|
||||||
|
|
||||||
let discord_receiver = DiscordWebhook {
|
let discord_receiver = DiscordWebhook {
|
||||||
|
17
examples/try_rust_webapp/Cargo.toml
Normal file
17
examples/try_rust_webapp/Cargo.toml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
[package]
|
||||||
|
name = "example-try-rust-webapp"
|
||||||
|
edition = "2024"
|
||||||
|
version.workspace = true
|
||||||
|
readme.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
harmony = { path = "../../harmony" }
|
||||||
|
harmony_cli = { path = "../../harmony_cli" }
|
||||||
|
harmony_types = { path = "../../harmony_types" }
|
||||||
|
harmony_macros = { path = "../../harmony_macros" }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
log = { workspace = true }
|
||||||
|
env_logger = { workspace = true }
|
||||||
|
url = { workspace = true }
|
||||||
|
base64.workspace = true
|
52
examples/try_rust_webapp/src/main.rs
Normal file
52
examples/try_rust_webapp/src/main.rs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
use std::{path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use harmony::{
|
||||||
|
inventory::Inventory,
|
||||||
|
modules::{
|
||||||
|
application::{
|
||||||
|
ApplicationScore, RustWebFramework, RustWebapp,
|
||||||
|
features::{ContinuousDelivery, Monitoring},
|
||||||
|
},
|
||||||
|
monitoring::alert_channel::discord_alert_channel::DiscordWebhook,
|
||||||
|
},
|
||||||
|
topology::K8sAnywhereTopology,
|
||||||
|
};
|
||||||
|
use harmony_types::net::Url;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
let application = Arc::new(RustWebapp {
|
||||||
|
name: "harmony-example-tryrust".to_string(),
|
||||||
|
domain: Url::Url(url::Url::parse("https://tryrust.harmony.example.com").unwrap()),
|
||||||
|
project_root: PathBuf::from("./tryrust.org"),
|
||||||
|
framework: Some(RustWebFramework::Leptos),
|
||||||
|
service_port: 8080,
|
||||||
|
});
|
||||||
|
|
||||||
|
let discord_receiver = DiscordWebhook {
|
||||||
|
name: "test-discord".to_string(),
|
||||||
|
url: Url::Url(url::Url::parse("https://discord.doesnt.exist.com").unwrap()),
|
||||||
|
};
|
||||||
|
|
||||||
|
let app = ApplicationScore {
|
||||||
|
features: vec![
|
||||||
|
Box::new(ContinuousDelivery {
|
||||||
|
application: application.clone(),
|
||||||
|
}),
|
||||||
|
Box::new(Monitoring {
|
||||||
|
application: application.clone(),
|
||||||
|
alert_receiver: vec![Box::new(discord_receiver)],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
application,
|
||||||
|
};
|
||||||
|
|
||||||
|
harmony_cli::run(
|
||||||
|
Inventory::autoload(),
|
||||||
|
K8sAnywhereTopology::from_env(),
|
||||||
|
vec![Box::new(app)],
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
}
|
1
examples/try_rust_webapp/tryrust.org
Submodule
1
examples/try_rust_webapp/tryrust.org
Submodule
@ -0,0 +1 @@
|
|||||||
|
Subproject commit 0f9ba145172867f467e5320b37d07a5bbb7dd438
|
@ -66,6 +66,7 @@ tar.workspace = true
|
|||||||
base64.workspace = true
|
base64.workspace = true
|
||||||
thiserror.workspace = true
|
thiserror.workspace = true
|
||||||
once_cell = "1.21.3"
|
once_cell = "1.21.3"
|
||||||
|
walkdir = "2.5.0"
|
||||||
harmony_inventory_agent = { path = "../harmony_inventory_agent" }
|
harmony_inventory_agent = { path = "../harmony_inventory_agent" }
|
||||||
harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" }
|
harmony_secret_derive = { version = "0.1.0", path = "../harmony_secret_derive" }
|
||||||
askama.workspace = true
|
askama.workspace = true
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use std::fs;
|
use std::fs::{self, File};
|
||||||
|
use std::io::Read;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -12,7 +13,8 @@ use dockerfile_builder::instruction_builder::CopyBuilder;
|
|||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use log::{debug, info, log_enabled};
|
use log::{debug, info, log_enabled};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use tar::Archive;
|
use tar::{Archive, Builder, Header};
|
||||||
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
|
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
|
||||||
use crate::{score::Score, topology::Topology};
|
use crate::{score::Score, topology::Topology};
|
||||||
@ -59,6 +61,7 @@ pub struct RustWebapp {
|
|||||||
pub domain: Url,
|
pub domain: Url,
|
||||||
/// The path to the root of the Rust project to be containerized.
|
/// The path to the root of the Rust project to be containerized.
|
||||||
pub project_root: PathBuf,
|
pub project_root: PathBuf,
|
||||||
|
pub service_port: u32,
|
||||||
pub framework: Option<RustWebFramework>,
|
pub framework: Option<RustWebFramework>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,45 +161,99 @@ impl RustWebapp {
|
|||||||
image_name: &str,
|
image_name: &str,
|
||||||
) -> Result<String, Box<dyn std::error::Error>> {
|
) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
debug!("Generating Dockerfile for '{}'", self.name);
|
debug!("Generating Dockerfile for '{}'", self.name);
|
||||||
let _dockerfile_path = self.build_dockerfile()?;
|
let dockerfile = self.get_or_build_dockerfile();
|
||||||
|
|
||||||
let docker = Docker::connect_with_socket_defaults().unwrap();
|
|
||||||
|
|
||||||
let quiet = !log_enabled!(log::Level::Debug);
|
let quiet = !log_enabled!(log::Level::Debug);
|
||||||
|
match dockerfile
|
||||||
let build_image_options = bollard::query_parameters::BuildImageOptionsBuilder::default()
|
|
||||||
.dockerfile("Dockerfile.harmony")
|
|
||||||
.t(image_name)
|
|
||||||
.q(quiet)
|
|
||||||
.version(bollard::query_parameters::BuilderVersion::BuilderV1)
|
|
||||||
.platform("linux/x86_64");
|
|
||||||
|
|
||||||
let mut temp_tar_builder = tar::Builder::new(Vec::new());
|
|
||||||
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()
|
.unwrap()
|
||||||
.map(|entry| entry.unwrap().path().unwrap().into_owned())
|
.file_name()
|
||||||
.collect::<Vec<_>>();
|
.and_then(|os_str| os_str.to_str())
|
||||||
|
{
|
||||||
|
Some(path_str) => {
|
||||||
|
debug!("Building from dockerfile {}", path_str);
|
||||||
|
|
||||||
debug!("files in docker tar: {:#?}", archived_files);
|
let tar_data = self
|
||||||
|
.create_deterministic_tar(&self.project_root.clone())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
let mut image_build_stream = docker.build_image(
|
let docker = Docker::connect_with_socket_defaults().unwrap();
|
||||||
build_image_options.build(),
|
|
||||||
None,
|
|
||||||
Some(body_full(archive.into())),
|
|
||||||
);
|
|
||||||
|
|
||||||
while let Some(msg) = image_build_stream.next().await {
|
let build_image_options =
|
||||||
debug!("Message: {msg:?}");
|
bollard::query_parameters::BuildImageOptionsBuilder::default()
|
||||||
|
.dockerfile(path_str)
|
||||||
|
.t(image_name)
|
||||||
|
.q(quiet)
|
||||||
|
.version(bollard::query_parameters::BuilderVersion::BuilderV1)
|
||||||
|
.platform("linux/x86_64");
|
||||||
|
|
||||||
|
let mut image_build_stream = docker.build_image(
|
||||||
|
build_image_options.build(),
|
||||||
|
None,
|
||||||
|
Some(body_full(tar_data.into())),
|
||||||
|
);
|
||||||
|
|
||||||
|
while let Some(msg) = image_build_stream.next().await {
|
||||||
|
debug!("Message: {msg:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(image_name.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
None => Err(Box::new(std::io::Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
"Path is not valid UTF-8",
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(image_name.to_string())
|
///normalizes timestamp and ignores files that will bust the docker cach
|
||||||
|
async fn create_deterministic_tar(
|
||||||
|
&self,
|
||||||
|
project_root: &std::path::Path,
|
||||||
|
) -> Result<Vec<u8>, Box<dyn std::error::Error>> {
|
||||||
|
debug!("building tar file from project root {:#?}", project_root);
|
||||||
|
let mut tar_data = Vec::new();
|
||||||
|
{
|
||||||
|
let mut builder = Builder::new(&mut tar_data);
|
||||||
|
let ignore_prefixes = [
|
||||||
|
"target",
|
||||||
|
".git",
|
||||||
|
".github",
|
||||||
|
".harmony_generated",
|
||||||
|
"node_modules",
|
||||||
|
];
|
||||||
|
let mut entries: Vec<_> = WalkDir::new(project_root)
|
||||||
|
.into_iter()
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.filter(|e| e.file_type().is_file())
|
||||||
|
.filter(|e| {
|
||||||
|
let rel_path = e.path().strip_prefix(project_root).unwrap();
|
||||||
|
!ignore_prefixes
|
||||||
|
.iter()
|
||||||
|
.any(|prefix| rel_path.starts_with(prefix))
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
entries.sort_by_key(|e| e.path().to_owned());
|
||||||
|
|
||||||
|
for entry in entries {
|
||||||
|
let path = entry.path();
|
||||||
|
let rel_path = path.strip_prefix(project_root).unwrap();
|
||||||
|
|
||||||
|
let mut file = fs::File::open(path)?;
|
||||||
|
let mut header = Header::new_gnu();
|
||||||
|
|
||||||
|
header.set_size(entry.metadata()?.len());
|
||||||
|
header.set_mode(0o644);
|
||||||
|
header.set_mtime(0);
|
||||||
|
header.set_uid(0);
|
||||||
|
header.set_gid(0);
|
||||||
|
|
||||||
|
builder.append_data(&mut header, rel_path, &mut file)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
builder.finish()?;
|
||||||
|
}
|
||||||
|
Ok(tar_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tags and pushes a Docker image to the configured remote registry.
|
/// Tags and pushes a Docker image to the configured remote registry.
|
||||||
@ -272,8 +329,11 @@ impl RustWebapp {
|
|||||||
"groupadd -r appgroup && useradd -r -s /bin/false -g appgroup appuser",
|
"groupadd -r appgroup && useradd -r -s /bin/false -g appgroup appuser",
|
||||||
));
|
));
|
||||||
|
|
||||||
dockerfile.push(ENV::from("LEPTOS_SITE_ADDR=0.0.0.0:3000"));
|
dockerfile.push(ENV::from(format!(
|
||||||
dockerfile.push(EXPOSE::from("3000/tcp"));
|
"LEPTOS_SITE_ADDR=0.0.0.0:{}",
|
||||||
|
self.service_port
|
||||||
|
)));
|
||||||
|
dockerfile.push(EXPOSE::from(format!("{}/tcp", self.service_port)));
|
||||||
dockerfile.push(WORKDIR::from("/home/appuser"));
|
dockerfile.push(WORKDIR::from("/home/appuser"));
|
||||||
|
|
||||||
// Copy static files
|
// Copy static files
|
||||||
@ -394,7 +454,7 @@ image:
|
|||||||
|
|
||||||
service:
|
service:
|
||||||
type: ClusterIP
|
type: ClusterIP
|
||||||
port: 3000
|
port: {}
|
||||||
|
|
||||||
ingress:
|
ingress:
|
||||||
enabled: true
|
enabled: true
|
||||||
@ -414,112 +474,123 @@ ingress:
|
|||||||
- chart-example.local
|
- chart-example.local
|
||||||
|
|
||||||
"#,
|
"#,
|
||||||
chart_name, image_repo, image_tag, self.name
|
chart_name, image_repo, image_tag, self.service_port, self.name
|
||||||
);
|
);
|
||||||
fs::write(chart_dir.join("values.yaml"), values_yaml)?;
|
fs::write(chart_dir.join("values.yaml"), values_yaml)?;
|
||||||
|
|
||||||
// Create templates/_helpers.tpl
|
// Create templates/_helpers.tpl
|
||||||
let helpers_tpl = r#"
|
let helpers_tpl = format!(
|
||||||
{{/*
|
r#"
|
||||||
|
{{{{/*
|
||||||
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 }}}}
|
||||||
|
|
||||||
{{/*
|
{{{{/*
|
||||||
Create a default fully qualified app name.
|
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 }}}}
|
||||||
"#;
|
"#
|
||||||
|
);
|
||||||
fs::write(templates_dir.join("_helpers.tpl"), helpers_tpl)?;
|
fs::write(templates_dir.join("_helpers.tpl"), helpers_tpl)?;
|
||||||
|
|
||||||
// Create templates/service.yaml
|
// Create templates/service.yaml
|
||||||
let service_yaml = r#"
|
let service_yaml = format!(
|
||||||
|
r#"
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: {{ include "chart.fullname" . }}
|
name: {{{{ include "chart.fullname" . }}}}
|
||||||
spec:
|
spec:
|
||||||
type: {{ $.Values.service.type }}
|
type: {{{{ $.Values.service.type }}}}
|
||||||
ports:
|
ports:
|
||||||
- name: main
|
- name: main
|
||||||
port: {{ $.Values.service.port | default 3000 }}
|
port: {{{{ $.Values.service.port | default {} }}}}
|
||||||
targetPort: {{ $.Values.service.port | default 3000 }}
|
targetPort: {{{{ $.Values.service.port | default {} }}}}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
selector:
|
selector:
|
||||||
app: {{ include "chart.name" . }}
|
app: {{{{ include "chart.name" . }}}}
|
||||||
"#;
|
"#,
|
||||||
|
self.service_port, self.service_port
|
||||||
|
);
|
||||||
fs::write(templates_dir.join("service.yaml"), service_yaml)?;
|
fs::write(templates_dir.join("service.yaml"), service_yaml)?;
|
||||||
|
|
||||||
// Create templates/deployment.yaml
|
// Create templates/deployment.yaml
|
||||||
let deployment_yaml = r#"
|
let deployment_yaml = format!(
|
||||||
|
r#"
|
||||||
apiVersion: apps/v1
|
apiVersion: apps/v1
|
||||||
kind: Deployment
|
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" . }}}}
|
||||||
template:
|
template:
|
||||||
metadata:
|
metadata:
|
||||||
labels:
|
labels:
|
||||||
app: {{ include "chart.name" . }}
|
app: {{{{ include "chart.name" . }}}}
|
||||||
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: main
|
- name: main
|
||||||
containerPort: {{ $.Values.service.port | default 3000 }}
|
containerPort: {{{{ $.Values.service.port | default {} }}}}
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
"#;
|
"#,
|
||||||
|
self.service_port
|
||||||
|
);
|
||||||
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 = format!(
|
||||||
{{- if $.Values.ingress.enabled -}}
|
r#"
|
||||||
|
{{{{- 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 }}}}
|
||||||
{{- end }}
|
{{{{- end }}}}
|
||||||
secretName: {{ .secretName }}
|
secretName: {{{{ .secretName }}}}
|
||||||
{{- end }}
|
{{{{- end }}}}
|
||||||
{{- end }}
|
{{{{- end }}}}
|
||||||
rules:
|
rules:
|
||||||
{{- range $.Values.ingress.hosts }}
|
{{{{- range $.Values.ingress.hosts }}}}
|
||||||
- host: {{ .host | quote }}
|
- host: {{{{ .host | quote }}}}
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
{{- range .paths }}
|
{{{{- range .paths }}}}
|
||||||
- path: {{ .path }}
|
- path: {{{{ .path }}}}
|
||||||
pathType: {{ .pathType }}
|
pathType: {{{{ .pathType }}}}
|
||||||
backend:
|
backend:
|
||||||
service:
|
service:
|
||||||
name: {{ include "chart.fullname" $ }}
|
name: {{{{ include "chart.fullname" $ }}}}
|
||||||
port:
|
port:
|
||||||
number: {{ $.Values.service.port | default 3000 }}
|
number: {{{{ $.Values.service.port | default {} }}}}
|
||||||
{{- 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)?;
|
||||||
|
|
||||||
Ok(chart_dir)
|
Ok(chart_dir)
|
||||||
@ -571,7 +642,6 @@ spec:
|
|||||||
let chart_file_name = packaged_chart_path.file_stem().unwrap().to_str().unwrap();
|
let chart_file_name = packaged_chart_path.file_stem().unwrap().to_str().unwrap();
|
||||||
let oci_push_url = format!("oci://{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT);
|
let oci_push_url = format!("oci://{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT);
|
||||||
let oci_pull_url = format!("{oci_push_url}/{}-chart", self.name);
|
let oci_pull_url = format!("{oci_push_url}/{}-chart", self.name);
|
||||||
|
|
||||||
debug!(
|
debug!(
|
||||||
"Pushing Helm chart {} to {}",
|
"Pushing Helm chart {} to {}",
|
||||||
packaged_chart_path.to_string_lossy(),
|
packaged_chart_path.to_string_lossy(),
|
||||||
@ -590,4 +660,20 @@ spec:
|
|||||||
debug!("push url {oci_push_url}");
|
debug!("push url {oci_push_url}");
|
||||||
Ok(format!("{}:{}", oci_pull_url, version))
|
Ok(format!("{}:{}", oci_pull_url, version))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn get_or_build_dockerfile(&self) -> Result<PathBuf, Box<dyn std::error::Error>> {
|
||||||
|
let existing_dockerfile = self.project_root.join("Dockerfile");
|
||||||
|
|
||||||
|
debug!("project_root = {:?}", self.project_root);
|
||||||
|
|
||||||
|
debug!("checking = {:?}", existing_dockerfile);
|
||||||
|
if existing_dockerfile.exists() {
|
||||||
|
debug!(
|
||||||
|
"Checking path {:#?} for existing Dockerfile",
|
||||||
|
self.project_root.clone()
|
||||||
|
);
|
||||||
|
return Ok(existing_dockerfile);
|
||||||
|
}
|
||||||
|
self.build_dockerfile()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user