Skip to content

Commit

Permalink
feat: make deployer only answer its own project (#466)
Browse files Browse the repository at this point in the history
* feat: make deployer only answer its own project

* bug: use correct project name

* refactor: make backwards compatible
  • Loading branch information
chesedo authored Nov 11, 2022
1 parent 3a98a47 commit 001dbcf
Show file tree
Hide file tree
Showing 6 changed files with 78 additions and 10 deletions.
4 changes: 2 additions & 2 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,10 @@ This prevents `gateway` from starting up. Now you can start deployer only using:

```bash
provisioner_address=$(docker inspect --format '{{(index .NetworkSettings.Networks "shuttle_default").IPAddress}}' shuttle_prod_hello-world-rocket-app_run)
cargo run -p shuttle-deployer -- --provisioner-address $provisioner_address --provisioner-port 8000 --proxy-fqdn local.rs --admin-secret test-key
cargo run -p shuttle-deployer -- --provisioner-address $provisioner_address --provisioner-port 8000 --proxy-fqdn local.rs --admin-secret test-key --project <project_name>
```

The `--admin-secret` can safely be changed to your api-key to make testing easier.
The `--admin-secret` can safely be changed to your api-key to make testing easier. While `<project_name>` needs to match the name of the project that will be deployed to this deployer. This is the `Cargo.toml` or `Shuttle.toml` name for the project.

### Using Podman instead of Docker
If you are using Podman over Docker, then expose a rootless socket of Podman using the following command:
Expand Down
6 changes: 5 additions & 1 deletion deployer/src/args.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{net::SocketAddr, path::PathBuf};

use clap::Parser;
use fqdn::FQDN;
use shuttle_common::Port;
use shuttle_common::{project::ProjectName, Port};

/// Program to handle the deploys for a single project
/// Handling includes, building, testing, and running each service
Expand Down Expand Up @@ -33,6 +33,10 @@ pub struct Args {
#[clap(long, default_value = "0.0.0.0:8000")]
pub proxy_address: SocketAddr,

/// Project being served by this deployer
#[clap(long)]
pub project: ProjectName,

/// Secret that will be used to perform admin tasks on this deployer
#[clap(long)]
pub admin_secret: String,
Expand Down
7 changes: 7 additions & 0 deletions deployer/src/handlers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use axum::body::{Body, BoxBody};
use axum::extract::ws::{self, WebSocket};
use axum::extract::{Extension, Path, Query};
use axum::http::{Request, Response};
use axum::middleware::from_extractor;
use axum::routing::{get, Router};
use axum::{extract::BodyStream, Json};
use bytes::BufMut;
Expand All @@ -13,6 +14,7 @@ use futures::StreamExt;
use opentelemetry::global;
use opentelemetry_http::HeaderExtractor;
use shuttle_common::models::secret;
use shuttle_common::project::ProjectName;
use shuttle_common::LogItem;
use tower_http::auth::RequireAuthorizationLayer;
use tower_http::trace::TraceLayer;
Expand All @@ -28,11 +30,14 @@ use std::time::Duration;

pub use {self::error::Error, self::error::Result};

mod project;

pub fn make_router(
persistence: Persistence,
deployment_manager: DeploymentManager,
proxy_fqdn: FQDN,
admin_secret: String,
project_name: ProjectName,
) -> Router<Body> {
Router::new()
.route("/projects/:project_name/services", get(list_services))
Expand Down Expand Up @@ -78,6 +83,8 @@ pub fn make_router(
},
),
)
.route_layer(from_extractor::<project::ProjectNameGuard>())
.layer(Extension(project_name))
}

async fn list_services(
Expand Down
52 changes: 52 additions & 0 deletions deployer/src/handlers/project.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use std::collections::HashMap;

use async_trait::async_trait;
use axum::extract::{FromRequest, Path, RequestParts};
use hyper::StatusCode;
use shuttle_common::project::ProjectName;
use tracing::error;

/// Gaurd to ensure request are for the project served by this deployer
/// Note: this guard needs the `ProjectName` extension to be set
pub struct ProjectNameGuard;

#[async_trait]
impl<B> FromRequest<B> for ProjectNameGuard
where
B: Send,
{
type Rejection = StatusCode;

async fn from_request(req: &mut RequestParts<B>) -> Result<Self, Self::Rejection> {
// We expect some path parameters
let Path(path): Path<HashMap<String, String>> = match req.extract().await {
Ok(path) => path,
Err(_) => return Err(StatusCode::NOT_FOUND),
};

// All our routes have the `project_name` parameter
let project_name = match path.get("project_name") {
Some(project_name) => project_name,
None => {
error!("ProjectNameGuard found no project name in path");
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};

// This extractor requires the ProjectName extension to be set
let expected_project_name: &ProjectName = match req.extensions().get() {
Some(expected) => expected,
None => {
error!("ProjectName extension is not set");
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};

if project_name == expected_project_name.as_str() {
Ok(ProjectNameGuard)
} else {
error!(project_name, "project is not served by this deployer");
Err(StatusCode::BAD_REQUEST)
}
}
}
1 change: 1 addition & 0 deletions deployer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ pub async fn start(
deployment_manager,
args.proxy_fqdn,
args.admin_secret,
args.project,
);
let make_service = router.into_make_service();

Expand Down
18 changes: 11 additions & 7 deletions gateway/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -391,11 +391,14 @@ impl ProjectCreating {
"Image": image,
"Hostname": format!("{prefix}{project_name}"),
"Labels": {
"shuttle_prefix": prefix
"shuttle_prefix": prefix,
"project.name": project_name,
},
"Cmd": [
"--admin-secret",
initial_key,
"--project",
project_name,
"--api-address",
format!("0.0.0.0:{RUNTIME_API_PORT}"),
"--provisioner-address",
Expand All @@ -413,10 +416,7 @@ impl ProjectCreating {
],
"Env": [
"RUST_LOG=debug",
],
"Labels": {
"project.name": project_name,
}
]
});

let mut config = Config::<String>::from(container_config);
Expand Down Expand Up @@ -619,9 +619,13 @@ pub struct Service {

impl Service {
pub fn from_container(container: ContainerInspectResponse) -> Result<Self, ProjectError> {
// This version can't be enabled while there are active deployers before v0.8.0 since the don't have this label
// TODO: switch to this version when you notice all deployers are greater than v0.8.0
// let name = safe_unwrap!(container.config.labels.get("project.name")).to_string();
let container_name = safe_unwrap!(container.name.strip_prefix("/")).to_string();

let resource_name = safe_unwrap!(container_name.strip_suffix("_run")).to_string();
let prefix = safe_unwrap!(container.config.labels.get("shuttle_prefix")).to_string();
let resource_name =
safe_unwrap!(container_name.strip_prefix(&prefix).strip_suffix("_run")).to_string();

let network = safe_unwrap!(container.network_settings.networks)
.values()
Expand Down

0 comments on commit 001dbcf

Please sign in to comment.