Skip to content

Commit

Permalink
feat(cargo-shuttle): beta certificate command (#1860)
Browse files Browse the repository at this point in the history
* wip: cert models

* wip: cert command

* fix cert cmd

* nit: suggestions

* feat(backends): allow disabling 2xx check

* feat: command aliases

* feat: cert models

* feat: correct cert calls

* nit: utility fixes

* fix & clippy

* feat: cert cmd outputs
  • Loading branch information
jonaro00 authored Aug 26, 2024
1 parent 85f1000 commit 090725d
Show file tree
Hide file tree
Showing 10 changed files with 283 additions and 43 deletions.
8 changes: 6 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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-*
Expand Down
20 changes: 14 additions & 6 deletions admin/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
Expand All @@ -93,12 +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")).await?;
self.inner
.delete(format!("/users/{user_id}/beta"), Option::<()>::None)
.await?
};

if !resp.status().is_success() {
dbg!(resp);
panic!("request failed");
}

Ok(())
Expand Down
86 changes: 77 additions & 9 deletions api-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use reqwest::Response;
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use shuttle_common::certificate::{
AddCertificateRequest, CertificateResponse, DeleteCertificateRequest,
};
use shuttle_common::log::{LogsRange, LogsResponseBeta};
use shuttle_common::models::deployment::{
DeploymentRequest, DeploymentRequestBeta, UploadArchiveResponseBeta,
Expand Down Expand Up @@ -136,11 +139,7 @@ impl ShuttleApiClient {
deployment_req: DeploymentRequestBeta,
) -> Result<deployment::ResponseBeta> {
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(
Expand Down Expand Up @@ -231,6 +230,29 @@ impl ShuttleApiClient {
.await
}

pub async fn list_certificates_beta(&self, project: &str) -> Result<Vec<CertificateResponse>> {
self.get_json(format!("/projects/{project}/certificates"))
.await
}
pub async fn add_certificate_beta(
&self,
project: &str,
domain: String,
) -> Result<CertificateResponse> {
self.post_json(
format!("/projects/{project}/certificates"),
Some(AddCertificateRequest { domain }),
)
.await
}
pub async fn delete_certificate_beta(&self, project: &str, domain: String) -> Result<()> {
self.delete_json_with_body(
format!("/projects/{project}/certificates"),
DeleteCertificateRequest { domain },
)
.await
}

pub async fn create_project(
&self,
project: &str,
Expand Down Expand Up @@ -441,20 +463,43 @@ impl ShuttleApiClient {
Ok(stream)
}

pub async fn get(&self, path: impl AsRef<str>) -> Result<Response> {
pub async fn get<T: Serialize>(
&self,
path: impl AsRef<str>,
body: Option<T>,
) -> Result<Response> {
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")
}

pub async fn get_json<R>(&self, path: impl AsRef<str>) -> Result<R>
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<R, T: Serialize>(
&self,
path: impl AsRef<str>,
body: T,
) -> Result<R>
where
R: for<'de> Deserialize<'de>,
{
self.get(path, Some(body)).await?.to_json().await
}

pub async fn post<T: Serialize>(
Expand Down Expand Up @@ -521,12 +566,24 @@ impl ShuttleApiClient {
self.put(path, body).await?.to_json().await
}

pub async fn delete(&self, path: impl AsRef<str>) -> Result<Response> {
pub async fn delete<T: Serialize>(
&self,
path: impl AsRef<str>,
body: Option<T>,
) -> Result<Response> {
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
Expand All @@ -537,6 +594,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<R, T: Serialize>(
&self,
path: impl AsRef<str>,
body: T,
) -> Result<R>
where
R: for<'de> Deserialize<'de>,
{
self.delete(path, Some(body)).await?.to_json().await
}
}
7 changes: 6 additions & 1 deletion backends/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -47,6 +49,7 @@ impl ServicesApiClient {
Self {
client: Self::builder().build().unwrap(),
base,
error_on_non_2xx: true,
}
}

Expand All @@ -57,13 +60,15 @@ impl ServicesApiClient {
.build()
.unwrap(),
base,
error_on_non_2xx: true,
}
}

pub fn new_with_default_headers(base: Uri, headers: HeaderMap) -> Self {
Self {
client: Self::builder().default_headers(headers).build().unwrap(),
base,
error_on_non_2xx: true,
}
}

Expand Down Expand Up @@ -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()));
}

Expand Down
54 changes: 44 additions & 10 deletions cargo-shuttle/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,30 +99,34 @@ 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),
/// 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,
/// 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
#[command(subcommand)]
/// Manage projects on Shuttle
#[command(subcommand, visible_alias = "proj")]
Project(ProjectCommand),
/// Manage resources of a Shuttle project
#[command(subcommand)]
/// Manage resources
#[command(subcommand, visible_alias = "res")]
Resource(ResourceCommand),
/// BETA: Manage SSL certificates for custom domains
#[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),
Expand Down Expand Up @@ -158,7 +162,8 @@ pub struct TableArgs {

#[derive(Parser)]
pub enum DeploymentCommand {
/// List all the deployments for a service
/// List the deployments for a service
#[command(visible_alias = "ls")]
List {
#[arg(long, default_value = "1")]
/// Which page to display
Expand All @@ -177,12 +182,14 @@ pub enum DeploymentCommand {
id: Option<String>,
},
/// BETA: Stop running deployment(s)
#[command(hide = true)]
Stop,
}

#[derive(Parser)]
pub enum ResourceCommand {
/// List all the resources for a project
/// List the resources for a project
#[command(visible_alias = "ls")]
List {
#[command(flatten)]
table: TableArgs,
Expand All @@ -192,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.
Expand All @@ -202,6 +210,29 @@ 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
#[command(visible_alias = "ls")]
List {
#[command(flatten)]
table: TableArgs,
},
/// Delete an SSL certificate
#[command(visible_alias = "rm")]
Delete {
/// Domain name
domain: String,
#[command(flatten)]
confirmation: ConfirmationArgs,
},
}

#[derive(Parser)]
pub enum ProjectCommand {
/// Create an environment for this project on Shuttle
Expand All @@ -218,6 +249,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)]
Expand All @@ -229,6 +261,7 @@ pub enum ProjectCommand {
table: TableArgs,
},
/// Delete a project and all linked data
#[command(visible_alias = "rm")]
Delete(ConfirmationArgs),
}

Expand Down Expand Up @@ -260,6 +293,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
Expand Down
2 changes: 1 addition & 1 deletion cargo-shuttle/src/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Loading

0 comments on commit 090725d

Please sign in to comment.