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",
|
"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]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -1383,6 +1392,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"cidr",
|
"cidr",
|
||||||
|
"convert_case",
|
||||||
"derive-new",
|
"derive-new",
|
||||||
"directories",
|
"directories",
|
||||||
"dockerfile_builder",
|
"dockerfile_builder",
|
||||||
|
@ -35,6 +35,7 @@ serde_yaml = "0.9.34"
|
|||||||
serde-value = "0.7.0"
|
serde-value = "0.7.0"
|
||||||
http = "1.2.0"
|
http = "1.2.0"
|
||||||
inquire = "0.7.5"
|
inquire = "0.7.5"
|
||||||
|
convert_case = "0.8.0"
|
||||||
|
|
||||||
[workspace.dependencies.uuid]
|
[workspace.dependencies.uuid]
|
||||||
version = "1.11.0"
|
version = "1.11.0"
|
||||||
|
@ -8,17 +8,30 @@ use harmony::{
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn 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 {
|
let lamp_stack = LAMPScore {
|
||||||
name: "harmony-lamp-demo".to_string(),
|
name: "harmony-lamp-demo".to_string(),
|
||||||
domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()),
|
domain: Url::Url(url::Url::parse("https://lampdemo.harmony.nationtech.io").unwrap()),
|
||||||
php_version: Version::from("8.4.4").unwrap(),
|
php_version: Version::from("8.4.4").unwrap(),
|
||||||
|
// This config can be extended as needed for more complicated configurations
|
||||||
config: LAMPConfig {
|
config: LAMPConfig {
|
||||||
project_root: "./php".into(),
|
project_root: "./php".into(),
|
||||||
..Default::default()
|
..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(
|
let mut maestro = Maestro::<K8sAnywhereTopology>::initialize(
|
||||||
Inventory::autoload(),
|
Inventory::autoload(),
|
||||||
K8sAnywhereTopology::new(),
|
K8sAnywhereTopology::new(),
|
||||||
@ -26,5 +39,7 @@ async fn main() {
|
|||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
maestro.register_all(vec![Box::new(lamp_stack)]);
|
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();
|
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"
|
semver = "1.0.23"
|
||||||
serde = { version = "1.0.209", features = ["derive"] }
|
serde = { version = "1.0.209", features = ["derive"] }
|
||||||
serde_json = "1.0.127"
|
serde_json = "1.0.127"
|
||||||
tokio = { workspace = true }
|
tokio.workspace = true
|
||||||
derive-new = { workspace = true }
|
derive-new.workspace = true
|
||||||
log = { workspace = true }
|
log.workspace = true
|
||||||
env_logger = { workspace = true }
|
env_logger.workspace = true
|
||||||
async-trait = { workspace = true }
|
async-trait.workspace = true
|
||||||
cidr = { workspace = true }
|
cidr.workspace = true
|
||||||
opnsense-config = { path = "../opnsense-config" }
|
opnsense-config = { path = "../opnsense-config" }
|
||||||
opnsense-config-xml = { path = "../opnsense-config-xml" }
|
opnsense-config-xml = { path = "../opnsense-config-xml" }
|
||||||
harmony_macros = { path = "../harmony_macros" }
|
harmony_macros = { path = "../harmony_macros" }
|
||||||
harmony_types = { path = "../harmony_types" }
|
harmony_types = { path = "../harmony_types" }
|
||||||
uuid = { workspace = true }
|
uuid.workspace = true
|
||||||
url = { workspace = true }
|
url.workspace = true
|
||||||
kube = { workspace = true }
|
kube.workspace = true
|
||||||
k8s-openapi = { workspace = true }
|
k8s-openapi.workspace = true
|
||||||
serde_yaml = { workspace = true }
|
serde_yaml.workspace = true
|
||||||
http = { workspace = true }
|
http.workspace = true
|
||||||
serde-value = { workspace = true }
|
serde-value.workspace = true
|
||||||
inquire.workspace = true
|
inquire.workspace = true
|
||||||
helm-wrapper-rs = "0.4.0"
|
helm-wrapper-rs = "0.4.0"
|
||||||
non-blank-string-rs = "1.0.4"
|
non-blank-string-rs = "1.0.4"
|
||||||
@ -38,3 +38,4 @@ directories = "6.0.0"
|
|||||||
lazy_static = "1.5.0"
|
lazy_static = "1.5.0"
|
||||||
dockerfile_builder = "0.1.5"
|
dockerfile_builder = "0.1.5"
|
||||||
temp-file = "0.1.9"
|
temp-file = "0.1.9"
|
||||||
|
convert_case.workspace = true
|
||||||
|
@ -6,4 +6,8 @@ lazy_static! {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
.data_dir()
|
.data_dir()
|
||||||
.join("harmony");
|
.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)
|
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
|
where
|
||||||
K: Resource<Scope = NamespaceResourceScope>
|
K: Resource<Scope = NamespaceResourceScope>
|
||||||
+ Clone
|
+ Clone
|
||||||
@ -49,7 +49,10 @@ impl K8sClient {
|
|||||||
<K as kube::Resource>::DynamicType: Default,
|
<K as kube::Resource>::DynamicType: Default,
|
||||||
{
|
{
|
||||||
for r in resource.iter() {
|
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?;
|
api.create(&PostParams::default(), &r).await?;
|
||||||
}
|
}
|
||||||
todo!("")
|
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::Serialize;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
|
|
||||||
@ -14,11 +15,13 @@ use super::resource::{K8sResourceInterpret, K8sResourceScore};
|
|||||||
pub struct K8sDeploymentScore {
|
pub struct K8sDeploymentScore {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub image: String,
|
pub image: String,
|
||||||
|
pub namespace: Option<String>,
|
||||||
|
pub env_vars: serde_json::Value,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
||||||
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
fn create_interpret(&self) -> Box<dyn Interpret<T>> {
|
||||||
let deployment: Deployment = serde_json::from_value(json!(
|
let deployment = json!(
|
||||||
{
|
{
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"name": self.name
|
"name": self.name
|
||||||
@ -38,18 +41,21 @@ impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore {
|
|||||||
"spec": {
|
"spec": {
|
||||||
"containers": [
|
"containers": [
|
||||||
{
|
{
|
||||||
"image": self.image,
|
"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 {
|
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)]
|
#[derive(Debug, Clone, Serialize)]
|
||||||
pub struct K8sResourceScore<K: Resource + std::fmt::Debug> {
|
pub struct K8sResourceScore<K: Resource + std::fmt::Debug> {
|
||||||
pub resource: Vec<K>,
|
pub resource: Vec<K>,
|
||||||
|
pub namespace: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> {
|
impl<K: Resource + std::fmt::Debug> K8sResourceScore<K> {
|
||||||
pub fn single(resource: K) -> Self {
|
pub fn single(resource: K, namespace: Option<String>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
resource: vec![resource],
|
resource: vec![resource],
|
||||||
|
namespace,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -77,7 +79,7 @@ where
|
|||||||
.k8s_client()
|
.k8s_client()
|
||||||
.await
|
.await
|
||||||
.expect("Environment should provide enough information to instanciate a client")
|
.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?;
|
.await?;
|
||||||
|
|
||||||
Ok(Outcome::success(
|
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::instruction::{CMD, COPY, ENV, EXPOSE, FROM, RUN, WORKDIR};
|
||||||
use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder};
|
use dockerfile_builder::{Dockerfile, instruction_builder::EnvBuilder};
|
||||||
use non_blank_string_rs::NonBlankString;
|
use non_blank_string_rs::NonBlankString;
|
||||||
|
use serde_json::json;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use log::info;
|
use log::{debug, info};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
|
use crate::config::{REGISTRY_PROJECT, REGISTRY_URL};
|
||||||
use crate::topology::HelmCommand;
|
use crate::topology::HelmCommand;
|
||||||
use crate::{
|
use crate::{
|
||||||
data::{Id, Version},
|
data::{Id, Version},
|
||||||
@ -80,22 +83,49 @@ impl<T: Topology + K8sclient + HelmCommand> Interpret<T> for LAMPInterpret {
|
|||||||
};
|
};
|
||||||
info!("LAMP docker image built {image_name}");
|
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");
|
info!("Deploying database");
|
||||||
self.deploy_database(inventory, topology).await?;
|
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 {
|
let deployment_score = K8sDeploymentScore {
|
||||||
name: <LAMPScore as Score<T>>::name(&self.score),
|
name: <LAMPScore as Score<T>>::name(&self.score).to_case(Case::Kebab),
|
||||||
image: image_name,
|
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
|
deployment_score
|
||||||
.create_interpret()
|
.create_interpret()
|
||||||
.execute(inventory, topology)
|
.execute(inventory, topology)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
info!("LAMP deployment_score {deployment_score:?}");
|
info!("LAMP deployment_score {deployment_score:?}");
|
||||||
todo!("1. Use HelmChartScore to deploy mariadb
|
todo!("1. [x] Use HelmChartScore to deploy mariadb
|
||||||
2. Use deploymentScore to deploy lamp docker container
|
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?)");
|
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)
|
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>> {
|
pub fn build_docker_image(&self) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
info!("Generating Dockerfile");
|
info!("Generating Dockerfile");
|
||||||
let dockerfile = self.build_dockerfile(&self.score)?;
|
let dockerfile = self.build_dockerfile(&self.score)?;
|
||||||
|
Loading…
Reference in New Issue
Block a user