From 87bfe38a25ab2a896166f22b9bb9c938695d323e Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Tue, 20 Aug 2024 11:42:10 +0200 Subject: [PATCH 01/11] wip: cert models --- common/src/certificate.rs | 9 +++++++++ common/src/lib.rs | 2 ++ 2 files changed, 11 insertions(+) create mode 100644 common/src/certificate.rs diff --git a/common/src/certificate.rs b/common/src/certificate.rs new file mode 100644 index 000000000..62fcc6989 --- /dev/null +++ b/common/src/certificate.rs @@ -0,0 +1,9 @@ +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Serialize, Debug)] +pub struct AddCertificateRequest { + pub domain: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct CertificateResponse {} diff --git a/common/src/lib.rs b/common/src/lib.rs index 9355a43ed..a04cca1ad 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "service")] +pub mod certificate; #[cfg(feature = "claims")] pub mod claims; pub mod constants; From efff88378e59fff2418080be51611a0d215fd36e Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Tue, 20 Aug 2024 13:20:29 +0200 Subject: [PATCH 02/11] wip: cert command --- api-client/src/lib.rs | 11 +++++++++++ cargo-shuttle/src/args.rs | 31 ++++++++++++++++++++++++------- cargo-shuttle/src/lib.rs | 21 ++++++++++++++++++--- 3 files changed, 53 insertions(+), 10 deletions(-) diff --git a/api-client/src/lib.rs b/api-client/src/lib.rs index f923525b9..087c26fc4 100644 --- a/api-client/src/lib.rs +++ b/api-client/src/lib.rs @@ -8,6 +8,7 @@ use reqwest::Response; use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; use serde::{Deserialize, Serialize}; use serde_json::Value; +use shuttle_common::certificate::AddCertificateRequest; use shuttle_common::log::{LogsRange, LogsResponseBeta}; use shuttle_common::models::deployment::{ DeploymentRequest, DeploymentRequestBeta, UploadArchiveResponseBeta, @@ -231,6 +232,16 @@ impl ShuttleApiClient { .await } + pub async fn add_certificate_beta(&self, project: &str, domain: String) -> Result<()> { + self.post( + format!("/projects/{project}/certificates"), + Some(AddCertificateRequest { domain }), + ) + .await?; + + Ok(()) + } + pub async fn create_project( &self, project: &str, diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index 427e83105..a33421a72 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -99,7 +99,7 @@ impl ProjectArgs { /// for more information. #[derive(Parser)] pub enum Command { - /// Create a new Shuttle project + /// Generate a Shuttle project from a template Init(InitArgs), /// Run a Shuttle service locally Run(RunArgs), @@ -110,16 +110,19 @@ pub enum Command { Deployment(DeploymentCommand), /// View the status of a Shuttle service Status, - /// Stop this Shuttle service + /// Stop a Shuttle service Stop, - /// View the logs of a deployment in this Shuttle service + /// View logs of a Shuttle service Logs(LogsArgs), - /// List or manage projects on Shuttle + /// Manage projects on Shuttle #[command(subcommand)] Project(ProjectCommand), - /// Manage resources of a Shuttle project + /// Manage resources #[command(subcommand)] Resource(ResourceCommand), + /// BETA: Manage SSL certificates for custom domains + #[command(subcommand)] + Certificate(CertificateCommand), /// Remove cargo build artifacts in the Shuttle environment Clean, /// BETA: Show info about your Shuttle account @@ -158,7 +161,7 @@ pub struct TableArgs { #[derive(Parser)] pub enum DeploymentCommand { - /// List all the deployments for a service + /// List the deployments for a service List { #[arg(long, default_value = "1")] /// Which page to display @@ -182,7 +185,7 @@ pub enum DeploymentCommand { #[derive(Parser)] pub enum ResourceCommand { - /// List all the resources for a project + /// List the resources for a project List { #[command(flatten)] table: TableArgs, @@ -202,6 +205,20 @@ pub enum ResourceCommand { }, } +#[derive(Parser)] +pub enum CertificateCommand { + /// Add an SSL certificate for a custom domain + Add { + /// Domain name + domain: String, + }, + /// List the certificates for a project + List { + #[command(flatten)] + table: TableArgs, + }, +} + #[derive(Parser)] pub enum ProjectCommand { /// Create an environment for this project on Shuttle diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 18a21f8a6..031264cf5 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -76,12 +76,12 @@ use tracing::{debug, error, info, trace, warn}; use uuid::Uuid; use zip::write::FileOptions; -pub use crate::args::{Command, ProjectArgs, RunArgs, ShuttleArgs}; use crate::args::{ - ConfirmationArgs, DeployArgs, DeploymentCommand, GenerateCommand, InitArgs, LoginArgs, - LogoutArgs, LogsArgs, ProjectCommand, ProjectStartArgs, ResourceCommand, TableArgs, + CertificateCommand, ConfirmationArgs, DeployArgs, DeploymentCommand, GenerateCommand, InitArgs, + LoginArgs, LogoutArgs, LogsArgs, ProjectCommand, ProjectStartArgs, ResourceCommand, TableArgs, TemplateLocation, }; +pub use crate::args::{Command, ProjectArgs, RunArgs, ShuttleArgs}; use crate::config::RequestContext; use crate::provisioner_server::beta::{ProvApiState, ProvisionerServerBeta}; use crate::provisioner_server::LocalProvisioner; @@ -276,6 +276,10 @@ impl Shuttle { confirmation: ConfirmationArgs { yes }, } => self.resource_delete(&resource_type, yes).await, }, + Command::Certificate(cmd) => match cmd { + CertificateCommand::Add { domain } => self.add_certificate(domain).await, + CertificateCommand::List { .. } => todo!(), + }, Command::Project(cmd) => match cmd { ProjectCommand::Start(ProjectStartArgs { idle_minutes }) => { if self.beta { @@ -1299,6 +1303,17 @@ impl Shuttle { Ok(CommandOutcome::Ok) } + async fn add_certificate(&self, domain: String) -> Result { + let client = self.client.as_ref().unwrap(); + client + .add_certificate_beta(self.ctx.project_name(), domain.clone()) + .await?; + + println!("Added certificate for {domain}"); + + Ok(CommandOutcome::Ok) + } + fn get_secrets(run_args: &RunArgs, service: &BuiltService) -> Result> { let secrets_file = run_args.secret_args.secrets.clone().or_else(|| { let crate_dir = service.crate_directory(); From 31567f79af002b270f5f279f2122d172e3aa0168 Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Tue, 20 Aug 2024 15:15:10 +0200 Subject: [PATCH 03/11] fix cert cmd --- api-client/src/lib.rs | 20 +++++++++----------- cargo-shuttle/src/lib.rs | 2 ++ 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api-client/src/lib.rs b/api-client/src/lib.rs index 087c26fc4..9e271e04a 100644 --- a/api-client/src/lib.rs +++ b/api-client/src/lib.rs @@ -8,7 +8,7 @@ use reqwest::Response; use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use shuttle_common::certificate::AddCertificateRequest; +use shuttle_common::certificate::{AddCertificateRequest, CertificateResponse}; use shuttle_common::log::{LogsRange, LogsResponseBeta}; use shuttle_common::models::deployment::{ DeploymentRequest, DeploymentRequestBeta, UploadArchiveResponseBeta, @@ -137,11 +137,7 @@ impl ShuttleApiClient { deployment_req: DeploymentRequestBeta, ) -> Result { let path = format!("/projects/{project}/deployments"); - self.post(path, Some(deployment_req)) - .await - .context("failed to start deployment")? - .to_json() - .await + self.post_json(path, Some(deployment_req)).await } pub async fn upload_archive_beta( @@ -232,14 +228,16 @@ impl ShuttleApiClient { .await } - pub async fn add_certificate_beta(&self, project: &str, domain: String) -> Result<()> { - self.post( + pub async fn add_certificate_beta( + &self, + project: &str, + domain: String, + ) -> Result { + self.post_json( format!("/projects/{project}/certificates"), Some(AddCertificateRequest { domain }), ) - .await?; - - Ok(()) + .await } pub async fn create_project( diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 031264cf5..a1325e17c 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -173,6 +173,7 @@ impl Shuttle { Command::Deploy(..) | Command::Deployment(..) | Command::Resource(..) + | Command::Certificate(..) | Command::Project( // ProjectCommand::List does not need to know which project we are in ProjectCommand::Start { .. } @@ -202,6 +203,7 @@ impl Shuttle { | Command::Logout(..) | Command::Deployment(..) | Command::Resource(..) + | Command::Certificate(..) | Command::Stop | Command::Clean | Command::Project(..) From c3a9b7a75c0a6e69bc4fd8cebf89238732c61aed Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:36:40 +0200 Subject: [PATCH 04/11] nit: suggestions --- cargo-shuttle/src/lib.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index a1325e17c..81861fe4a 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -130,19 +130,19 @@ impl Shuttle { ) -> Result { self.beta = args.beta; if self.beta { - if matches!( - args.cmd, - Command::Project(ProjectCommand::Stop { .. } | ProjectCommand::Restart { .. }) - ) { - eprintln!("This command is discontinued on the beta platform."); + if matches!(args.cmd, Command::Project(ProjectCommand::Restart { .. })) { + eprintln!("This command is discontinued on the beta platform. Deploy to start a new deployment."); return Ok(CommandOutcome::Ok); } if matches!(args.cmd, Command::Status) { - eprintln!("This command is deprecated on the beta platform. Use `deployment status` instead."); + eprintln!("This command is discontinued on the beta platform. Use `deployment status` instead."); return Ok(CommandOutcome::Ok); } - if matches!(args.cmd, Command::Stop) { - eprintln!("This command is deprecated on the beta platform. Use `deployment stop` instead."); + if matches!( + args.cmd, + Command::Stop | Command::Project(ProjectCommand::Stop { .. }) + ) { + eprintln!("This command is discontinued on the beta platform. Use `deployment stop` instead."); return Ok(CommandOutcome::Ok); } if matches!(args.cmd, Command::Clean) { @@ -154,7 +154,7 @@ impl Shuttle { args.cmd, Command::Deployment(DeploymentCommand::Stop) | Command::Account ) { - eprintln!("This command is not supported on the legacy platform."); + eprintln!("This command is not supported on the legacy platform. Set --beta or SHUTTLE_BETA=true."); return Ok(CommandOutcome::Ok); } if let Some(ref url) = args.api_url { From 38a58c9bea439ba5ac613c3edea88459b2ead675 Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Wed, 21 Aug 2024 01:35:31 +0200 Subject: [PATCH 05/11] feat(backends): allow disabling 2xx check --- backends/src/client/mod.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/backends/src/client/mod.rs b/backends/src/client/mod.rs index 2c2f0a424..7ba2559a7 100644 --- a/backends/src/client/mod.rs +++ b/backends/src/client/mod.rs @@ -36,6 +36,8 @@ pub enum Error { pub struct ServicesApiClient { client: Client, base: Uri, + /// Default true. Mutate to false to disable check. + pub error_on_non_2xx: bool, } impl ServicesApiClient { @@ -47,6 +49,7 @@ impl ServicesApiClient { Self { client: Self::builder().build().unwrap(), base, + error_on_non_2xx: true, } } @@ -57,6 +60,7 @@ impl ServicesApiClient { .build() .unwrap(), base, + error_on_non_2xx: true, } } @@ -64,6 +68,7 @@ impl ServicesApiClient { Self { client: Self::builder().default_headers(headers).build().unwrap(), base, + error_on_non_2xx: true, } } @@ -148,7 +153,7 @@ impl ServicesApiClient { let resp = req.send().await?; trace!(response = ?resp, "service response"); - if !resp.status().is_success() { + if self.error_on_non_2xx && !resp.status().is_success() { return Err(Error::RequestError(resp.status())); } From 84042aa56f62e4035f8447dfe5a6ff7e6781e9e6 Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:52:01 +0200 Subject: [PATCH 06/11] feat: command aliases --- cargo-shuttle/src/args.rs | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index a33421a72..b569a1ba1 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -106,7 +106,7 @@ pub enum Command { /// Deploy a Shuttle service Deploy(DeployArgs), /// Manage deployments of a Shuttle service - #[command(subcommand)] + #[command(subcommand, visible_alias = "depl")] Deployment(DeploymentCommand), /// View the status of a Shuttle service Status, @@ -115,17 +115,18 @@ pub enum Command { /// View logs of a Shuttle service Logs(LogsArgs), /// Manage projects on Shuttle - #[command(subcommand)] + #[command(subcommand, visible_alias = "proj")] Project(ProjectCommand), /// Manage resources - #[command(subcommand)] + #[command(subcommand, visible_alias = "res")] Resource(ResourceCommand), /// BETA: Manage SSL certificates for custom domains - #[command(subcommand)] + #[command(subcommand, visible_alias = "cert", hide = true)] Certificate(CertificateCommand), /// Remove cargo build artifacts in the Shuttle environment Clean, /// BETA: Show info about your Shuttle account + #[command(visible_alias = "acc", hide = true)] Account, /// Login to the Shuttle platform Login(LoginArgs), @@ -162,6 +163,7 @@ pub struct TableArgs { #[derive(Parser)] pub enum DeploymentCommand { /// List the deployments for a service + #[command(visible_alias = "ls")] List { #[arg(long, default_value = "1")] /// Which page to display @@ -180,12 +182,14 @@ pub enum DeploymentCommand { id: Option, }, /// BETA: Stop running deployment(s) + #[command(hide = true)] Stop, } #[derive(Parser)] pub enum ResourceCommand { /// List the resources for a project + #[command(visible_alias = "ls")] List { #[command(flatten)] table: TableArgs, @@ -195,6 +199,7 @@ pub enum ResourceCommand { show_secrets: bool, }, /// Delete a resource + #[command(visible_alias = "rm")] Delete { /// Type of the resource to delete. /// Use the string in the 'Type' column as displayed in the `resource list` command. @@ -213,6 +218,7 @@ pub enum CertificateCommand { domain: String, }, /// List the certificates for a project + #[command(visible_alias = "ls")] List { #[command(flatten)] table: TableArgs, @@ -235,6 +241,7 @@ pub enum ProjectCommand { /// Destroy and create an environment for this project on Shuttle Restart(ProjectStartArgs), /// List all projects you have access to + #[command(visible_alias = "ls")] List { // deprecated args, kept around to not break #[arg(long, hide = true)] @@ -246,6 +253,7 @@ pub enum ProjectCommand { table: TableArgs, }, /// Delete a project and all linked data + #[command(visible_alias = "rm")] Delete(ConfirmationArgs), } @@ -277,6 +285,7 @@ pub struct LogoutArgs { #[arg(long)] pub reset_api_key: bool, } + #[derive(Parser, Default)] pub struct DeployArgs { /// BETA: Deploy this Docker image instead of building one From 0d314662d45c884eff5452a0e550fe804c4b03dd Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Wed, 21 Aug 2024 15:52:10 +0200 Subject: [PATCH 07/11] feat: cert models --- common/src/certificate.rs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/common/src/certificate.rs b/common/src/certificate.rs index 62fcc6989..f49e95389 100644 --- a/common/src/certificate.rs +++ b/common/src/certificate.rs @@ -6,4 +6,13 @@ pub struct AddCertificateRequest { } #[derive(Deserialize, Serialize, Debug)] -pub struct CertificateResponse {} +pub struct DeleteCertificateRequest { + pub domain: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct CertificateResponse { + pub subject: String, + pub serial_hex: String, + pub not_after: String, +} From 555218722c16c3dc84e4510b38b674dd42b75b61 Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Fri, 23 Aug 2024 13:06:16 +0200 Subject: [PATCH 08/11] feat: correct cert calls --- admin/src/client.rs | 11 ++++--- api-client/src/lib.rs | 65 ++++++++++++++++++++++++++++++++++++--- cargo-shuttle/src/args.rs | 8 +++++ cargo-shuttle/src/lib.rs | 54 ++++++++++++++++++++++++++++++-- 4 files changed, 128 insertions(+), 10 deletions(-) diff --git a/admin/src/client.rs b/admin/src/client.rs index 17e261edd..eeb59c943 100644 --- a/admin/src/client.rs +++ b/admin/src/client.rs @@ -76,9 +76,10 @@ impl Client { pub async fn change_project_owner(&self, project_name: &str, new_user_id: &str) -> Result<()> { self.inner - .get(format!( - "/admin/projects/change-owner/{project_name}/{new_user_id}" - )) + .get( + format!("/admin/projects/change-owner/{project_name}/{new_user_id}"), + Option::<()>::None, + ) .await?; Ok(()) @@ -98,7 +99,9 @@ impl Client { .put(format!("/users/{user_id}/beta"), Option::<()>::None) .await?; } else { - self.inner.delete(format!("/users/{user_id}/beta")).await?; + self.inner + .delete(format!("/users/{user_id}/beta"), Option::<()>::None) + .await?; } Ok(()) diff --git a/api-client/src/lib.rs b/api-client/src/lib.rs index 9e271e04a..e5f30ee32 100644 --- a/api-client/src/lib.rs +++ b/api-client/src/lib.rs @@ -228,6 +228,10 @@ impl ShuttleApiClient { .await } + pub async fn list_certificates_beta(&self, project: &str) -> Result> { + self.get_json(format!("/projects/{project}/certificates")) + .await + } pub async fn add_certificate_beta( &self, project: &str, @@ -239,6 +243,13 @@ impl ShuttleApiClient { ) .await } + pub async fn delete_certificate_beta(&self, project: &str, domain: String) -> Result<()> { + self.delete_json_with_body( + format!("/projects/{project}/certificates"), + AddCertificateRequest { domain }, + ) + .await + } pub async fn create_project( &self, @@ -450,12 +461,24 @@ impl ShuttleApiClient { Ok(stream) } - pub async fn get(&self, path: impl AsRef) -> Result { + pub async fn get( + &self, + path: impl AsRef, + body: Option, + ) -> Result { let url = format!("{}{}", self.api_url, path.as_ref()); let mut builder = self.client.get(url); builder = self.set_auth_bearer(builder); + if let Some(body) = body { + let body = serde_json::to_string(&body)?; + #[cfg(feature = "tracing")] + debug!("Outgoing body: {}", body); + builder = builder.body(body); + builder = builder.header("Content-Type", "application/json"); + } + builder.send().await.context("failed to make get request") } @@ -463,7 +486,18 @@ impl ShuttleApiClient { where R: for<'de> Deserialize<'de>, { - self.get(path).await?.to_json().await + self.get(path, Option::<()>::None).await?.to_json().await + } + + pub async fn get_json_with_body( + &self, + path: impl AsRef, + body: T, + ) -> Result + where + R: for<'de> Deserialize<'de>, + { + self.get(path, Some(body)).await?.to_json().await } pub async fn post( @@ -530,12 +564,24 @@ impl ShuttleApiClient { self.put(path, body).await?.to_json().await } - pub async fn delete(&self, path: impl AsRef) -> Result { + pub async fn delete( + &self, + path: impl AsRef, + body: Option, + ) -> Result { let url = format!("{}{}", self.api_url, path.as_ref()); let mut builder = self.client.delete(url); builder = self.set_auth_bearer(builder); + if let Some(body) = body { + let body = serde_json::to_string(&body)?; + #[cfg(feature = "tracing")] + debug!("Outgoing body: {}", body); + builder = builder.body(body); + builder = builder.header("Content-Type", "application/json"); + } + builder .send() .await @@ -546,6 +592,17 @@ impl ShuttleApiClient { where R: for<'de> Deserialize<'de>, { - self.delete(path).await?.to_json().await + self.delete(path, Option::<()>::None).await?.to_json().await + } + + pub async fn delete_json_with_body( + &self, + path: impl AsRef, + body: T, + ) -> Result + where + R: for<'de> Deserialize<'de>, + { + self.delete(path, Some(body)).await?.to_json().await } } diff --git a/cargo-shuttle/src/args.rs b/cargo-shuttle/src/args.rs index b569a1ba1..a1e5fa234 100644 --- a/cargo-shuttle/src/args.rs +++ b/cargo-shuttle/src/args.rs @@ -223,6 +223,14 @@ pub enum CertificateCommand { #[command(flatten)] table: TableArgs, }, + /// Delete an SSL certificate + #[command(visible_alias = "rm")] + Delete { + /// Domain name + domain: String, + #[command(flatten)] + confirmation: ConfirmationArgs, + }, } #[derive(Parser)] diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 81861fe4a..19bb58b3d 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -280,7 +280,11 @@ impl Shuttle { }, Command::Certificate(cmd) => match cmd { CertificateCommand::Add { domain } => self.add_certificate(domain).await, - CertificateCommand::List { .. } => todo!(), + CertificateCommand::List { table } => self.list_certificates(table).await, + CertificateCommand::Delete { + domain, + confirmation: ConfirmationArgs { yes }, + } => self.delete_certificate(domain, yes).await, }, Command::Project(cmd) => match cmd { ProjectCommand::Start(ProjectStartArgs { idle_minutes }) => { @@ -1305,13 +1309,59 @@ impl Shuttle { Ok(CommandOutcome::Ok) } + async fn list_certificates(&self, _table_args: TableArgs) -> Result { + let client = self.client.as_ref().unwrap(); + let certs = client + .list_certificates_beta(self.ctx.project_name()) + .await?; + + // TODO: make table + println!("{:?}", certs); + + Ok(CommandOutcome::Ok) + } async fn add_certificate(&self, domain: String) -> Result { let client = self.client.as_ref().unwrap(); - client + let cert = client .add_certificate_beta(self.ctx.project_name(), domain.clone()) .await?; println!("Added certificate for {domain}"); + // TODO: Make nicer + println!("{:?}", cert); + + Ok(CommandOutcome::Ok) + } + async fn delete_certificate(&self, domain: String, no_confirm: bool) -> Result { + let client = self.client.as_ref().unwrap(); + + if !no_confirm { + println!( + "{}", + formatdoc!( + " + WARNING: + Delete the certificate for {}?", + domain + ) + .bold() + .red() + ); + if !Confirm::with_theme(&ColorfulTheme::default()) + .with_prompt("Are you sure?") + .default(false) + .interact() + .unwrap() + { + return Ok(CommandOutcome::Ok); + } + } + + client + .delete_certificate_beta(self.ctx.project_name(), domain.clone()) + .await?; + + println!("Deleted certificate for {domain}"); Ok(CommandOutcome::Ok) } From bf1fa2b560c98818f9e7507becf7c8da54499f87 Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Mon, 26 Aug 2024 12:23:25 +0200 Subject: [PATCH 09/11] nit: utility fixes --- Makefile | 8 ++++++-- admin/src/client.rs | 11 ++++++++--- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/Makefile b/Makefile index c00edb25d..7c92a7a5d 100644 --- a/Makefile +++ b/Makefile @@ -135,9 +135,13 @@ DOCKER_COMPOSE_ENV=\ DOCKER_SOCK=$(DOCKER_SOCK)\ SHUTTLE_ENV=$(SHUTTLE_ENV)\ SHUTTLE_SERVICE_VERSION=$(SHUTTLE_SERVICE_VERSION)\ - PERMIT_API_KEY=$(PERMIT_API_KEY) + PERMIT_API_KEY=$(PERMIT_API_KEY)\ + PERMIT_DEV_API_KEY=$(PERMIT_DEV_API_KEY) -.PHONY: clean deep-clean images the-shuttle-images shuttle-% postgres otel deploy test docker-compose.rendered.yml up down +.PHONY: envfile clean deep-clean images the-shuttle-images shuttle-% postgres otel deploy test docker-compose.rendered.yml up down + +envfile: + echo $(DOCKER_COMPOSE_ENV) > dockerenv clean: rm .shuttle-* diff --git a/admin/src/client.rs b/admin/src/client.rs index eeb59c943..dd1de65a9 100644 --- a/admin/src/client.rs +++ b/admin/src/client.rs @@ -94,14 +94,19 @@ impl Client { } pub async fn set_beta_access(&self, user_id: &str, access: bool) -> Result<()> { - if access { + let resp = if access { self.inner .put(format!("/users/{user_id}/beta"), Option::<()>::None) - .await?; + .await? } else { self.inner .delete(format!("/users/{user_id}/beta"), Option::<()>::None) - .await?; + .await? + }; + + if !resp.status().is_success() { + dbg!(resp); + panic!("request failed"); } Ok(()) From 498fbcd0145bf49a9d792c29a889bea29a4ba1f5 Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Mon, 26 Aug 2024 15:49:22 +0200 Subject: [PATCH 10/11] fix & clippy --- api-client/src/lib.rs | 6 ++++-- cargo-shuttle/src/init.rs | 2 +- cargo-shuttle/src/lib.rs | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/api-client/src/lib.rs b/api-client/src/lib.rs index e5f30ee32..98fbc7390 100644 --- a/api-client/src/lib.rs +++ b/api-client/src/lib.rs @@ -8,7 +8,9 @@ use reqwest::Response; use reqwest_middleware::{ClientWithMiddleware, RequestBuilder}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use shuttle_common::certificate::{AddCertificateRequest, CertificateResponse}; +use shuttle_common::certificate::{ + AddCertificateRequest, CertificateResponse, DeleteCertificateRequest, +}; use shuttle_common::log::{LogsRange, LogsResponseBeta}; use shuttle_common::models::deployment::{ DeploymentRequest, DeploymentRequestBeta, UploadArchiveResponseBeta, @@ -246,7 +248,7 @@ impl ShuttleApiClient { pub async fn delete_certificate_beta(&self, project: &str, domain: String) -> Result<()> { self.delete_json_with_body( format!("/projects/{project}/certificates"), - AddCertificateRequest { domain }, + DeleteCertificateRequest { domain }, ) .await } diff --git a/cargo-shuttle/src/init.rs b/cargo-shuttle/src/init.rs index b59717d6b..c4b9b32d2 100644 --- a/cargo-shuttle/src/init.rs +++ b/cargo-shuttle/src/init.rs @@ -201,7 +201,7 @@ fn copy_dirs(src: &Path, dest: &Path, git_policy: GitDir) -> Result<()> { ); } else { // Copy this file. - fs::copy(&entry.path(), &entry_dest)?; + fs::copy(entry.path(), &entry_dest)?; } } else if entry_type.is_symlink() { println!("Warning: symlink '{entry_name}' is ignored"); diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 19bb58b3d..6ed928b8b 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -1752,7 +1752,7 @@ impl Shuttle { fn find_available_port(run_args: &mut RunArgs, services_len: usize) { let default_port = run_args.port; - 'outer: for port in (run_args.port..=std::u16::MAX).step_by(services_len.max(10)) { + 'outer: for port in (run_args.port..=u16::MAX).step_by(services_len.max(10)) { for inner_port in port..(port + services_len as u16) { if !portpicker::is_free_tcp(inner_port) { continue 'outer; @@ -1777,7 +1777,7 @@ impl Shuttle { } fn find_available_port_beta(run_args: &mut RunArgs) { let original_port = run_args.port; - for port in (run_args.port..=std::u16::MAX).step_by(10) { + for port in (run_args.port..=u16::MAX).step_by(10) { if !portpicker::is_free_tcp(port) { continue; } From 66973040291183f3e5bd1656eb12d5d67496294b Mon Sep 17 00:00:00 2001 From: jonaro00 <54029719+jonaro00@users.noreply.github.com> Date: Mon, 26 Aug 2024 17:51:18 +0200 Subject: [PATCH 11/11] feat: cert cmd outputs --- cargo-shuttle/src/lib.rs | 11 +++++------ common/src/models/resource.rs | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/cargo-shuttle/src/lib.rs b/cargo-shuttle/src/lib.rs index 6ed928b8b..ab137925b 100644 --- a/cargo-shuttle/src/lib.rs +++ b/cargo-shuttle/src/lib.rs @@ -33,6 +33,7 @@ use indicatif::ProgressBar; use indoc::{formatdoc, printdoc}; use reqwest::header::HeaderMap; use shuttle_api_client::ShuttleApiClient; +use shuttle_common::models::resource::get_certificates_table_beta; use shuttle_common::{ constants::{ headers::X_CARGO_SHUTTLE_VERSION, API_URL_DEFAULT, DEFAULT_IDLE_MINUTES, EXAMPLES_REPO, @@ -1309,14 +1310,14 @@ impl Shuttle { Ok(CommandOutcome::Ok) } - async fn list_certificates(&self, _table_args: TableArgs) -> Result { + async fn list_certificates(&self, table_args: TableArgs) -> Result { let client = self.client.as_ref().unwrap(); let certs = client .list_certificates_beta(self.ctx.project_name()) .await?; - // TODO: make table - println!("{:?}", certs); + let table = get_certificates_table_beta(certs.as_ref(), table_args.raw); + println!("{}", table); Ok(CommandOutcome::Ok) } @@ -1326,9 +1327,7 @@ impl Shuttle { .add_certificate_beta(self.ctx.project_name(), domain.clone()) .await?; - println!("Added certificate for {domain}"); - // TODO: Make nicer - println!("{:?}", cert); + println!("Added certificate for {}", cert.subject); Ok(CommandOutcome::Ok) } diff --git a/common/src/models/resource.rs b/common/src/models/resource.rs index 9cd79b27f..33b1f98ac 100644 --- a/common/src/models/resource.rs +++ b/common/src/models/resource.rs @@ -8,6 +8,7 @@ use comfy_table::{ use crossterm::style::Stylize; use crate::{ + certificate::CertificateResponse, resource::{Response, Type}, secrets::SecretStore, DatabaseInfoBeta, DatabaseResource, @@ -187,6 +188,40 @@ fn get_databases_table_beta( format!("These databases are linked to {service_name}\n{table}\n{show_secret_hint}") } +pub fn get_certificates_table_beta(certs: &[CertificateResponse], raw: bool) -> String { + let mut table = Table::new(); + + if raw { + table + .load_preset(NOTHING) + .set_content_arrangement(ContentArrangement::Disabled) + .set_header(vec![ + Cell::new("Serial").set_alignment(CellAlignment::Left), + Cell::new("Subject").set_alignment(CellAlignment::Left), + Cell::new("Expires").set_alignment(CellAlignment::Left), + ]); + } else { + table + .load_preset(UTF8_BORDERS_ONLY) + .set_content_arrangement(ContentArrangement::Disabled) + .set_header(vec![ + Cell::new("Serial"), + Cell::new("Subject"), + Cell::new("Expires"), + ]); + } + + for cert in certs { + table.add_row(vec![ + cert.serial_hex.clone(), + cert.subject.clone(), + cert.not_after.clone(), + ]); + } + + table.to_string() +} + fn get_secrets_table(secrets: &[&Response], service_name: &str, raw: bool) -> String { let mut table = Table::new();