feat: push docker image to registry and deploy with full tag
- Added functionality to tag and push the built Docker image to a specified registry. - Modified deployment score to use the full image tag (including registry and project). - Included error handling and logging for the `docker tag` and `docker push` commands. - Updated the `K8sDeploymentScore` struct to include a namespace field and environment variables for database credentials. - Added kebab-case conversion for deployment name and namespace. - Implemented a check_output function for better error reporting.
This commit is contained in:
		
							parent
							
								
									317db2342c
								
							
						
					
					
						commit
						6b47d4140f
					
				
							
								
								
									
										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" | ||||
|  | ||||
| @ -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