From 439fc2b7944c0524e3b5bdc2ea1f502f8f66c7e7 Mon Sep 17 00:00:00 2001 From: Sascha Eglau Date: Sun, 18 Feb 2024 22:20:47 +0100 Subject: [PATCH 1/2] feat: deploy service --- src/api/mod.rs | 29 +++++++++++------ src/api/types.rs | 10 +++--- src/commands/apps.rs | 77 +++++++++++++++++++++++++++++++++++++------- 3 files changed, 90 insertions(+), 26 deletions(-) diff --git a/src/api/mod.rs b/src/api/mod.rs index b4dfaef..ce04f2c 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use reqwest::blocking::Response; -use self::types::{ListOrganizationResponse, Organization, CreateEnvironmentResponse, ListServicesResponse}; +use self::types::*; pub mod types; @@ -29,12 +29,14 @@ impl APIClient { response.json() } - pub fn get_application( + pub fn get_service( &self, token: &str, + org_name: &str, + env_name: &str, name: &str - ) -> Result { - let url = format!("{}/organization", self.base_url); + ) -> Result { + let url = format!("{}/orgs/{}/envs/{}/svcs/{}", self.base_url, org_name, env_name, name); let response = self.get(&url, token)?; response.json() } @@ -87,11 +89,20 @@ impl APIClient { response.json() } - pub fn initialize_application(&self) -> Result<(), reqwest::Error> { - let url = format!("{}/application", self.base_url); - - let response = self.client.post(url).send()?.error_for_status()?; - + pub fn deploy_service( + &self, + token: &str, + org_name: &str, + env_name: &str, + service: Service + ) -> Result { + let url = format!("{}/orgs/{}/envs/{}/svcs", self.base_url, org_name, env_name); + let mut body: HashMap<&str, &str> = HashMap::new(); + let port_str = &format!("{}", service.container_port); + body.insert("container_port", port_str); + body.insert("name", &service.name); + body.insert("image", &service.image); + let response = self.post(&url, token, &body)?; response.json() } diff --git a/src/api/types.rs b/src/api/types.rs index 9ff9f1f..f78140d 100644 --- a/src/api/types.rs +++ b/src/api/types.rs @@ -5,7 +5,7 @@ use tabled::Tabled; pub struct Organization { pub id: String, pub name: String, - billing_email: String, + pub billing_email: String, } #[derive(Serialize, Deserialize, Debug)] @@ -23,9 +23,9 @@ pub struct ListServicesResponse { pub services: Vec } -#[derive(Serialize, Deserialize, Debug, Tabled)] +#[derive(Serialize, Deserialize, Debug, Tabled, Clone)] pub struct Service { - name: String, - image: String, - container_port: u16, + pub name: String, + pub image: String, + pub container_port: u16, } diff --git a/src/commands/apps.rs b/src/commands/apps.rs index 022333f..783c66a 100644 --- a/src/commands/apps.rs +++ b/src/commands/apps.rs @@ -5,10 +5,10 @@ use std::path::Path; use super::CommandBase; use tabled::Table; -use crate::config::{ +use crate::{config::{ application::{Build, HttpService}, scan::{scan_directory_for_type, ApplicationType}, -}; +}, api::types::Service}; #[derive(Parser)] #[derive(Debug)] @@ -53,27 +53,80 @@ pub enum Commands { pub struct Deploy { #[arg(help = "Name of the app to deploy")] name: String, - #[arg(long, help = "The image to deploy, e.g. yourimage:v1")] + #[arg(long, help = "Environment to deploy to")] + env: String, + #[arg(short, long, help = "The image to deploy, e.g. yourimage:v1")] image: Option, - #[arg(long, help = "Skip confirmation")] + #[arg(long, help = "(not implemented) Skip confirmation")] no_confirm: Option, + #[arg(short, long, help = "Port the application listens on")] + port: Option, + #[arg(long, help = "Organization to deploy to")] + org: Option, } impl Deploy { pub fn execute(&self, base: &CommandBase) -> Result<()> { - // flags: image, no-confirm - // 1. check if authenticated + let org_name = if self.org.is_some() { + self.org.clone().unwrap() + } else { + base.user_config().get_default_org().unwrap().to_string() + }; let token = base .user_config() .get_token() .ok_or_else(|| anyhow!("No token found. Please login first."))?; - // 2. get existing application from API - let response = base.api_client().get_application( + + let response = base.api_client().get_service( token, - &self.name, - )?; - // 3. show user what changed - // 4. submit change + &org_name, + &self.env, + &self.name + ); + + let existing_svc: Option; + if let Err(e) = response { + existing_svc = None; + if let Some(reqwest::StatusCode::NOT_FOUND) = e.status() { + // User needs to set every attribute if service does not exist yet + if self.image.is_none() || self.port.is_none() { + return Err(anyhow!("Image and port are mandatory if service does not exist")) + } + } else if let Some(reqwest::StatusCode::UNAUTHORIZED) = e.status() { + return Err(anyhow!("Unauthorized, please login first")) + } else { + return Err(anyhow!("Could not check whether service exists or not")) + } + } else { + existing_svc = Some(response.unwrap()); + } + + let mut new_svc: Service; + if existing_svc.is_some() { + new_svc = existing_svc.clone().unwrap(); + if let Some(image) = &self.image { + new_svc.image = image.to_string() + } + if let Some(port) = self.port { + new_svc.container_port = port + } + } else { + new_svc = Service { + name: self.name.clone(), + image: self.image.clone().unwrap(), + container_port: self.port.clone().unwrap() + } + }; + + if let Some(false) = self.no_confirm { + // TODO: show user what changed + let existing_svc_yaml = serde_yaml::to_string(&existing_svc)?; + println!("{}", existing_svc_yaml); + } + + let result = base.api_client().deploy_service(token, &org_name, &self.env, new_svc)?; + println!("{:?}", result); + Ok(()) } } From c4c56213087f848db3ef7eaf218a444ac0e7271c Mon Sep 17 00:00:00 2001 From: Sascha Eglau Date: Mon, 19 Feb 2024 18:43:47 +0100 Subject: [PATCH 2/2] feat: use serde for serialization, rename module --- src/api/mod.rs | 23 +++++++++++++++-------- src/commands/mod.rs | 2 +- src/commands/{apps.rs => services.rs} | 5 ++--- src/main.rs | 2 +- 4 files changed, 19 insertions(+), 13 deletions(-) rename src/commands/{apps.rs => services.rs} (99%) diff --git a/src/api/mod.rs b/src/api/mod.rs index ce04f2c..81f03e1 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -95,15 +95,11 @@ impl APIClient { org_name: &str, env_name: &str, service: Service - ) -> Result { + ) -> anyhow::Result { let url = format!("{}/orgs/{}/envs/{}/svcs", self.base_url, org_name, env_name); - let mut body: HashMap<&str, &str> = HashMap::new(); - let port_str = &format!("{}", service.container_port); - body.insert("container_port", port_str); - body.insert("name", &service.name); - body.insert("image", &service.image); - let response = self.post(&url, token, &body)?; - response.json() + let body = serde_json::to_string(&service)?; + let response = self.post_str(&url, token, body)?; + Ok(serde_json::from_str(&response.text()?)?) } fn get(&self, url: &str, token: &str) -> Result { @@ -126,4 +122,15 @@ impl APIClient { .send()? .error_for_status(); } + + fn post_str(&self, url: &str, token: &str, body: String) -> Result { + return self.client + .post(url) + .header("User-Agent", self.user_agent.as_str()) + .header("Authorization", format!("Bearer {}", token)) + .header("Content-Type", "application/json") + .body(body) + .send()? + .error_for_status(); + } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index fa0b8d8..3992f14 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -11,10 +11,10 @@ use crate::{ }, }; -pub mod apps; pub mod auth; pub mod environments; pub mod orgs; +pub mod services; pub struct CommandBase<'a> { user_config: &'a mut UserConfig, diff --git a/src/commands/apps.rs b/src/commands/services.rs similarity index 99% rename from src/commands/apps.rs rename to src/commands/services.rs index 783c66a..537cd90 100644 --- a/src/commands/apps.rs +++ b/src/commands/services.rs @@ -125,8 +125,7 @@ impl Deploy { } let result = base.api_client().deploy_service(token, &org_name, &self.env, new_svc)?; - println!("{:?}", result); - + println!("Service {} deployed", result.name); Ok(()) } } @@ -186,7 +185,7 @@ impl Initialize { } } - println!("Creating app..."); + println!("Creating service..."); println!("{:#?}", app_config); Ok(()) diff --git a/src/main.rs b/src/main.rs index f03d188..f770ef6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,7 +37,7 @@ enum Commands { /// Login to Molnett Auth(commands::auth::Auth), /// Deploy and manage services - Services(commands::apps::Services), + Services(commands::services::Services), /// Create and manage environments Environments(commands::environments::Environments), }