Compare commits
	
		
			3 Commits
		
	
	
		
			28978299c9
			...
			90b80b24bc
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 90b80b24bc | |||
| c879ca143f | |||
| bc2bd2f2f4 | 
							
								
								
									
										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 | ||||
| @ -38,18 +41,21 @@ impl<T: Topology + K8sclient> Score<T> for K8sDeploymentScore { | ||||
|                         "spec": { | ||||
|                             "containers": [ | ||||
|                                 { | ||||
|                                      "image": self.image, | ||||
|                                       "name": self.image | ||||
|                                     "image": 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