Merge pull request 'feat: push docker image to registry and deploy with full tag' (#27) from feat/lampDatabase into master
Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/27 Reviewed-by: wjro <wrolleman@nationtech.io>
This commit is contained in:
commit
90b80b24bc
10
Cargo.lock
generated
10
Cargo.lock
generated
@ -524,6 +524,15 @@ dependencies = [
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@ -1383,6 +1392,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"cidr",
|
||||
"convert_case",
|
||||
"derive-new",
|
||||
"directories",
|
||||
"dockerfile_builder",
|
||||
|
@ -35,6 +35,7 @@ serde_yaml = "0.9.34"
|
||||
serde-value = "0.7.0"
|
||||
http = "1.2.0"
|
||||
inquire = "0.7.5"
|
||||
convert_case = "0.8.0"
|
||||
|
||||
[workspace.dependencies.uuid]
|
||||
version = "1.11.0"
|
||||
|
@ -8,17 +8,30 @@ use harmony::{
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// let _ = env_logger::Builder::from_default_env().filter_level(log::LevelFilter::Info).try_init();
|
||||
// This here is the whole configuration to
|
||||
// - setup a local K3D cluster
|
||||
// - Build a docker image with the PHP project builtin and production grade settings
|
||||
// - Deploy a mariadb database using a production grade helm chart
|
||||
// - Deploy the new container using a kubernetes deployment
|
||||
// - Configure networking between the PHP container and the database
|
||||
// - Provision a public route and an SSL certificate automatically on production environments
|
||||
//
|
||||
// Enjoy :)
|
||||
let lamp_stack = LAMPScore {
|
||||
name: "harmony-lamp-demo".to_string(),
|
||||
domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()),
|
||||
php_version: Version::from("8.4.4").unwrap(),
|
||||
// This config can be extended as needed for more complicated configurations
|
||||
config: LAMPConfig {
|
||||
project_root: "./php".into(),
|
||||
..Default::default()
|
||||
},
|
||||
};
|
||||
|
||||
// You can choose the type of Topology you want, we suggest starting with the
|
||||
// K8sAnywhereTopology as it is the most automatic one that enables you to easily deploy
|
||||
// locally, to development environment from a CI, to staging, and to production with settings
|
||||
// that automatically adapt to each environment grade.
|
||||
let mut maestro = Maestro::<K8sAnywhereTopology>::initialize(
|
||||
Inventory::autoload(),
|
||||
K8sAnywhereTopology::new(),
|
||||
@ -26,5 +39,7 @@ async fn main() {
|
||||
.await
|
||||
.unwrap();
|
||||
maestro.register_all(vec![Box::new(lamp_stack)]);
|
||||
// Here we bootstrap the CLI, this gives some nice features if you need them
|
||||
harmony_cli::init(maestro, None).await.unwrap();
|
||||
}
|
||||
// That's it, end of the infra as code.
|
||||
|
@ -13,23 +13,23 @@ rust-ipmi = "0.1.1"
|
||||
semver = "1.0.23"
|
||||
serde = { version = "1.0.209", features = ["derive"] }
|
||||
serde_json = "1.0.127"
|
||||
tokio = { workspace = true }
|
||||
derive-new = { workspace = true }
|
||||
log = { workspace = true }
|
||||
env_logger = { workspace = true }
|
||||
async-trait = { workspace = true }
|
||||
cidr = { workspace = true }
|
||||
tokio.workspace = true
|
||||
derive-new.workspace = true
|
||||
log.workspace = true
|
||||
env_logger.workspace = true
|
||||
async-trait.workspace = true
|
||||
cidr.workspace = true
|
||||
opnsense-config = { path = "../opnsense-config" }
|
||||
opnsense-config-xml = { path = "../opnsense-config-xml" }
|
||||
harmony_macros = { path = "../harmony_macros" }
|
||||
harmony_types = { path = "../harmony_types" }
|
||||
uuid = { workspace = true }
|
||||
url = { workspace = true }
|
||||
kube = { workspace = true }
|
||||
k8s-openapi = { workspace = true }
|
||||
serde_yaml = { workspace = true }
|
||||
http = { workspace = true }
|
||||
serde-value = { workspace = true }
|
||||
uuid.workspace = true
|
||||
url.workspace = true
|
||||
kube.workspace = true
|
||||
k8s-openapi.workspace = true
|
||||
serde_yaml.workspace = true
|
||||
http.workspace = true
|
||||
serde-value.workspace = true
|
||||
inquire.workspace = true
|
||||
helm-wrapper-rs = "0.4.0"
|
||||
non-blank-string-rs = "1.0.4"
|
||||
@ -38,3 +38,4 @@ directories = "6.0.0"
|
||||
lazy_static = "1.5.0"
|
||||
dockerfile_builder = "0.1.5"
|
||||
temp-file = "0.1.9"
|
||||
convert_case.workspace = true
|
||||
|
@ -6,4 +6,8 @@ lazy_static! {
|
||||
.unwrap()
|
||||
.data_dir()
|
||||
.join("harmony");
|
||||
pub static ref REGISTRY_URL: String = std::env::var("HARMONY_REGISTRY_URL")
|
||||
.unwrap_or_else(|_| "hub.nationtech.io".to_string());
|
||||
pub static ref REGISTRY_PROJECT: String =
|
||||
std::env::var("HARMONY_REGISTRY_PROJECT").unwrap_or_else(|_| "harmony".to_string());
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ impl K8sClient {
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn apply_namespaced<K>(&self, resource: &Vec<K>) -> Result<K, Error>
|
||||
pub async fn apply_namespaced<K>(&self, resource: &Vec<K>, ns: Option<&str>) -> Result<K, Error>
|
||||
where
|
||||
K: Resource<Scope = NamespaceResourceScope>
|
||||
+ Clone
|
||||
@ -49,7 +49,10 @@ impl K8sClient {
|
||||
<K as kube::Resource>::DynamicType: Default,
|
||||
{
|
||||
for r in resource.iter() {
|
||||
let api: Api<K> = Api::default_namespaced(self.client.clone());
|
||||
let api: Api<K> = match ns {
|
||||
Some(ns) => Api::namespaced(self.client.clone(), ns),
|
||||
None => Api::default_namespaced(self.client.clone()),
|
||||
};
|
||||
api.create(&PostParams::default(), &r).await?;
|
||||
}
|
||||
todo!("")
|
||||
|
@ -1,4 +1,5 @@
|
||||
use k8s_openapi::api::apps::v1::Deployment;
|
||||
use k8s_openapi::{DeepMerge, api::apps::v1::Deployment};
|
||||
use log::debug;
|
||||
use serde::Serialize;
|
||||
use serde_json::json;
|
||||
|
||||
@ -14,11 +15,13 @@ use super::resource::{K8sResourceInterpret, K8sResourceScore};
|
||||
pub struct K8sDeploymentScore {
|
||||
pub name: String,
|
||||
pub image: String,
|
||||
pub namespace: Option<String>,
|
||||
pub env_vars: serde_json::Value,
|
||||
}
|
||||
|
||||
impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||
let deployment: Deployment = serde_json::from_value(json!(
|
||||
let deployment = json!(
|
||||
{
|
||||
"metadata": {
|
||||
"name": self.name
|
||||
@ -39,17 +42,20 @@ impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
||||
"containers": [
|
||||
{
|
||||
"image": self.image,
|
||||
"name": self.image
|
||||
"name": self.name,
|
||||
"imagePullPolicy": "IfNotPresent",
|
||||
"env": self.env_vars,
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
))
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let deployment: Deployment = serde_json::from_value(deployment).unwrap();
|
||||
Box::new(K8sResourceInterpret {
|
||||
score: K8sResourceScore::single(deployment.clone()),
|
||||
score: K8sResourceScore::single(deployment.clone(), self.namespace.clone()),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -14,12 +14,14 @@ use crate::{
|
||||
#[derive(Debug, Clone, Serialize)]
|
||||
pub struct K8sResourceScore<K: Resource + std::fmt::Debug> {
|
||||
pub resource: Vec<K>,
|
||||
pub namespace: Option<String>,
|
||||
}
|
||||
|
||||
impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> {
|
||||
pub fn single(resource: K) -> Self {
|
||||
pub fn single(resource: K, namespace: Option<String>) -> Self {
|
||||
Self {
|
||||
resource: vec![resource],
|
||||
namespace,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -77,7 +79,7 @@ where
|
||||
.k8s_client()
|
||||
.await
|
||||
.expect("Environment should provide enough information to instanciate a client")
|
||||
.apply_namespaced(&self.score.resource)
|
||||
.apply_namespaced(&self.score.resource, self.score.namespace.as_deref())
|
||||
.await?;
|
||||
|
||||
Ok(Outcome::success(
|
||||
|
@ -1,14 +1,17 @@
|
||||
use convert_case::{Case, Casing};
|
||||
use dockerfile_builder::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR};
|
||||
use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder};
|
||||
use non_blank_string_rs::NonBlankString;
|
||||
use serde_json::json;
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::str::FromStr;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::info;
|
||||
use log::{debug, info};
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
|
||||
use crate::topology::HelmCommand;
|
||||
use crate::{
|
||||
data::{Id, Version},
|
||||
@ -80,22 +83,49 @@ impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for LAMPInterpret {
|
||||
};
|
||||
info!("LAMP docker image built {image_name}");
|
||||
|
||||
let remote_name = match self.push_docker_image(&image_name) {
|
||||
Ok(remote_name) => remote_name,
|
||||
Err(e) => {
|
||||
return Err(InterpretError::new(format!(
|
||||
"Could not push docker image {e}"
|
||||
)));
|
||||
}
|
||||
};
|
||||
info!("LAMP docker image pushed to {remote_name}");
|
||||
|
||||
info!("Deploying database");
|
||||
self.deploy_database(inventory, topology).await?;
|
||||
|
||||
let base_name = self.score.name.to_case(Case::Kebab);
|
||||
let secret_name = format!("{}-database-mariadb", base_name);
|
||||
|
||||
let deployment_score = K8sDeploymentScore {
|
||||
name: <LAMPScore as Score<T>>::name(&self.score),
|
||||
image: image_name,
|
||||
name: <LAMPScore as Score<T>>::name(&self.score).to_case(Case::Kebab),
|
||||
image: remote_name,
|
||||
namespace: self.get_namespace().map(|nbs| nbs.to_string()),
|
||||
env_vars: json!([
|
||||
{
|
||||
"name": "MYSQL_PASSWORD",
|
||||
"valueFrom": {
|
||||
"secretKeyRef": {
|
||||
"name": secret_name,
|
||||
"key": "mariadb-root-password"
|
||||
}
|
||||
}
|
||||
},
|
||||
]),
|
||||
};
|
||||
|
||||
info!("Deploying score {deployment_score:#?}");
|
||||
|
||||
deployment_score
|
||||
.create_interpret()
|
||||
.execute(inventory, topology)
|
||||
.await?;
|
||||
|
||||
info!("LAMP deployment_score {deployment_score:?}");
|
||||
todo!("1. Use HelmChartScore to deploy mariadb
|
||||
2. Use deploymentScore to deploy lamp docker container
|
||||
todo!("1. [x] Use HelmChartScore to deploy mariadb
|
||||
2. [x] Use deploymentScore to deploy lamp docker container
|
||||
3. for remote clusters, push the image to some registry (use nationtech's for demos? push to the cluster's registry?)");
|
||||
}
|
||||
|
||||
@ -258,6 +288,43 @@ opcache.fast_shutdown=1
|
||||
Ok(dockerfile_path)
|
||||
}
|
||||
|
||||
fn check_output(
|
||||
&self,
|
||||
output: &std::process::Output,
|
||||
msg: &str,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
if !output.status.success() {
|
||||
return Err(format!("{msg}: {}", String::from_utf8_lossy(&output.stderr)).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_docker_image(&self, image_name: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let full_tag = format!("{}/{}/{}", *REGISTRY_URL, *REGISTRY_PROJECT, &image_name);
|
||||
let output = std::process::Command::new("docker")
|
||||
.args(["tag", image_name, &full_tag])
|
||||
.output()?;
|
||||
self.check_output(&output, "Tagging docker image failed")?;
|
||||
|
||||
debug!(
|
||||
"docker tag output {} {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
let output = std::process::Command::new("docker")
|
||||
.args(["push", &full_tag])
|
||||
.output()?;
|
||||
self.check_output(&output, "Pushing docker image failed")?;
|
||||
debug!(
|
||||
"docker push output {} {}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
|
||||
Ok(full_tag)
|
||||
}
|
||||
|
||||
pub fn build_docker_image(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||
info!("Generating Dockerfile");
|
||||
let dockerfile = self.build_dockerfile(&self.score)?;
|
||||
|
Loading…
Reference in New Issue
Block a user