feat: push docker image to registry and deploy with full tag #27
							
								
								
									
										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" | ||||||
|  | |||||||
| @ -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