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

bug: delete a project even if the current state is destroyed #1413

Merged
merged 11 commits into from
Nov 23, 2023
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions gateway/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,4 @@ portpicker = { workspace = true }
ring = { workspace = true }
snailquote = "0.3.1"
tempfile = { workspace = true }
test-context = "0.1.4"
46 changes: 42 additions & 4 deletions gateway/src/api/latest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ use tokio::sync::{Mutex, MutexGuard};
use tower::ServiceBuilder;
use tracing::{field, instrument, trace};
use ttl_cache::TtlCache;
use ulid::Ulid;
use utoipa::openapi::security::{ApiKey, ApiKeyValue, SecurityScheme};
use utoipa::IntoParams;
use utoipa::{Modify, OpenApi};
Expand Down Expand Up @@ -319,6 +320,24 @@ async fn delete_project(
req: Request<Body>,
) -> Result<AxumJson<String>, Error> {
let project_name = scoped_user.scope.clone();
let project = state.service.find_project(&project_name).await?;
let project_id =
Ulid::from_string(&project.project_id).expect("stored project id to be a valid ULID");

// Try to startup a destroyed project first
if project.state.is_destroyed() {
let handle = state
.service
.new_task()
.project(project_name.clone())
.and_then(task::restart(project_id))
.send(&state.sender)
.await?;

// Wait for the project to be ready
handle.await;
}

let service = state.service.clone();
let sender = state.sender.clone();

Expand Down Expand Up @@ -1098,17 +1117,19 @@ pub mod tests {
use axum::headers::Authorization;
use axum::http::Request;
use futures::TryFutureExt;
use http::Method;
use hyper::body::to_bytes;
use hyper::StatusCode;
use serde_json::Value;
use shuttle_common::constants::limits::{MAX_PROJECTS_DEFAULT, MAX_PROJECTS_EXTRA};
use test_context::test_context;
use tokio::sync::mpsc::channel;
use tokio::sync::oneshot;
use tower::Service;

use super::*;
use crate::service::GatewayService;
use crate::tests::{RequestBuilderExt, World};
use crate::tests::{RequestBuilderExt, TestProject, World};

#[tokio::test]
async fn api_create_get_delete_projects() -> anyhow::Result<()> {
Expand Down Expand Up @@ -1140,7 +1161,7 @@ pub mod tests {
.unwrap()
};

let delete_project = |project: &str| {
let stop_project = |project: &str| {
Request::builder()
.method("DELETE")
.uri(format!("/projects/{project}"))
Expand Down Expand Up @@ -1197,7 +1218,7 @@ pub mod tests {
.unwrap();

router
.call(delete_project("matrix").with_header(&authorization))
.call(stop_project("matrix").with_header(&authorization))
.map_ok(|resp| {
assert_eq!(resp.status(), StatusCode::OK);
})
Expand All @@ -1223,7 +1244,7 @@ pub mod tests {
.unwrap();

router
.call(delete_project("reloaded").with_header(&authorization))
.call(stop_project("reloaded").with_header(&authorization))
.map_ok(|resp| {
assert_eq!(resp.status(), StatusCode::NOT_FOUND);
})
Expand Down Expand Up @@ -1364,6 +1385,23 @@ pub mod tests {
Ok(())
}

#[test_context(TestProject)]
#[tokio::test]
async fn api_delete_project_that_is_ready(project: &mut TestProject) -> anyhow::Result<()> {
project.router_call(Method::DELETE, "/delete").await;

Ok(())
}
Comment on lines +1388 to +1394
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean! 😍 Can we apply these utilities for the existing tests as well, and break them up into smaller pieces (looking at you api_create_get_delete_projects)? We can do it in a later PR to not block this for tomorrows release.

Copy link
Contributor Author

@chesedo chesedo Nov 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yip, getting to them in another PR is the plan.... and maybe even adding them to the end of CI after the image builds 😄


#[test_context(TestProject)]
#[tokio::test]
async fn api_delete_project_that_is_destroyed(project: &mut TestProject) -> anyhow::Result<()> {
project.destroy_project().await;
project.router_call(Method::DELETE, "/delete").await;

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn status() {
let world = World::new().await;
Expand Down
3 changes: 3 additions & 0 deletions gateway/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,7 @@ pub struct ContextArgs {
/// Api key for the user that has rights to start deploys
#[arg(long, default_value = "gateway4deployes")]
pub deploys_api_key: String,

/// Allow tests to set some extra /etc/hosts
pub extra_hosts: Vec<String>,
}
Loading