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: make deployer only answer its own project #466

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
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 @@ -389,11 +389,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 @@ -411,10 +414,7 @@ impl ProjectCreating {
],
"Env": [
"RUST_LOG=debug",
],
"Labels": {
"project.name": project_name,
}
]
});

let mut config = Config::<String>::from(container_config);
Expand Down Expand Up @@ -617,9 +617,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