Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(cargo-shuttle): beta certificate command #1860

Merged
merged 11 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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