Skip to content
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: 4 additions & 4 deletions linkup-cli/src/commands/preview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use crate::state::{config_path, get_config};
use crate::worker_client::WorkerClient;
use anyhow::Context;
use clap::builder::ValueParser;
use linkup::CreatePreviewRequest;
use linkup::UpsertSessionRequest;
use url::Url;

#[derive(clap::Args)]
Expand All @@ -24,12 +24,12 @@ pub struct Args {
pub async fn preview(args: &Args, config: &Option<String>) -> Result<()> {
let config_path = config_path(config)?;
let input_config = get_config(&config_path)?;
let create_preview_request: CreatePreviewRequest =
let upsert_session_request: UpsertSessionRequest =
linkup::create_preview_req_from_config(&input_config, &args.services);
let url = input_config.linkup.worker_url.clone();

if args.print_request {
let create_req_json = serde_json::to_string(&create_preview_request)
let create_req_json = serde_json::to_string(&upsert_session_request)
.context("Failed to encode request to JSON string")?;

println!("{}", create_req_json);
Expand All @@ -38,7 +38,7 @@ pub async fn preview(args: &Args, config: &Option<String>) -> Result<()> {
}

let preview_name = WorkerClient::from(&input_config)
.preview(&create_preview_request)
.preview(&upsert_session_request)
.await
.with_context(|| format!("Failed to send preview request to {}", url))?;

Expand Down
4 changes: 2 additions & 2 deletions linkup-cli/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use regex::Regex;
use serde::{Deserialize, Serialize};
use url::Url;

use linkup::{Domain, Session, SessionService, UpdateSessionRequest};
use linkup::{Domain, Session, SessionService, UpsertSessionRequest};

use crate::{
LINKUP_CONFIG_ENV, LINKUP_STATE_FILE, Result, linkup_file_path, services,
Expand Down Expand Up @@ -233,7 +233,7 @@ async fn upload_session_to_server(
desired_name: &str,
session: Session,
) -> Result<String, worker_client::Error> {
let session_update_req = UpdateSessionRequest {
let session_update_req = UpsertSessionRequest::Named {
session_token: session.session_token,
desired_name: desired_name.to_string(),
services: session.services,
Expand Down
6 changes: 3 additions & 3 deletions linkup-cli/src/worker_client.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use linkup::{CreatePreviewRequest, UpdateSessionRequest};
use linkup::UpsertSessionRequest;
use reqwest::{StatusCode, header};
use serde::{Deserialize, Serialize};
use url::Url;
Expand Down Expand Up @@ -62,11 +62,11 @@ impl WorkerClient {
}
}

pub async fn preview(&self, params: &CreatePreviewRequest) -> Result<String, Error> {
pub async fn preview(&self, params: &UpsertSessionRequest) -> Result<String, Error> {
self.post("/linkup/preview-session", params).await
}

pub async fn linkup(&self, params: &UpdateSessionRequest) -> Result<String, Error> {
pub async fn linkup(&self, params: &UpsertSessionRequest) -> Result<String, Error> {
self.post("/linkup/local-session", params).await
}

Expand Down
97 changes: 50 additions & 47 deletions linkup/src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,29 +28,30 @@ pub struct Route {
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct UpdateSessionRequest {
pub desired_name: String,
pub session_token: String,
pub services: Vec<SessionService>,
pub domains: Vec<Domain>,
#[serde(
default,
serialize_with = "crate::serde_ext::serialize_opt_vec_regex",
deserialize_with = "crate::serde_ext::deserialize_opt_vec_regex"
)]
pub cache_routes: Option<Vec<Regex>>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct CreatePreviewRequest {
pub services: Vec<SessionService>,
pub domains: Vec<Domain>,
#[serde(
default,
serialize_with = "crate::serde_ext::serialize_opt_vec_regex",
deserialize_with = "crate::serde_ext::deserialize_opt_vec_regex"
)]
pub cache_routes: Option<Vec<Regex>>,
#[serde(untagged)]
pub enum UpsertSessionRequest {
Named {
desired_name: String,
session_token: String,
services: Vec<SessionService>,
domains: Vec<Domain>,
#[serde(
default,
serialize_with = "crate::serde_ext::serialize_opt_vec_regex",
deserialize_with = "crate::serde_ext::deserialize_opt_vec_regex"
)]
cache_routes: Option<Vec<Regex>>,
},
Unnamed {
services: Vec<SessionService>,
domains: Vec<Domain>,
#[serde(
default,
serialize_with = "crate::serde_ext::serialize_opt_vec_regex",
deserialize_with = "crate::serde_ext::deserialize_opt_vec_regex"
)]
cache_routes: Option<Vec<Regex>>,
},
}

#[derive(Clone, Debug, Deserialize, Serialize)]
Expand Down Expand Up @@ -133,33 +134,35 @@ impl Session {
}
}

impl TryFrom<UpdateSessionRequest> for Session {
impl TryFrom<UpsertSessionRequest> for Session {
type Error = ConfigError;

fn try_from(req: UpdateSessionRequest) -> Result<Self, Self::Error> {
let session = Self {
session_token: req.session_token,
services: req.services,
domains: req.domains,
cache_routes: req.cache_routes,
fn try_from(req: UpsertSessionRequest) -> Result<Self, Self::Error> {
let (session_token, services, domains, cache_routes) = match req {
UpsertSessionRequest::Named {
services,
domains,
cache_routes,
session_token,
..
} => (session_token, services, domains, cache_routes),
UpsertSessionRequest::Unnamed {
services,
domains,
cache_routes,
} => (
PREVIEW_SESSION_TOKEN.to_string(),
services,
domains,
cache_routes,
),
};

validate_not_empty(&session)?;
validate_services(&session)?;

Ok(session)
}
}

impl TryFrom<CreatePreviewRequest> for Session {
type Error = ConfigError;

fn try_from(req: CreatePreviewRequest) -> Result<Self, Self::Error> {
let session = Self {
session_token: PREVIEW_SESSION_TOKEN.to_string(),
services: req.services,
domains: req.domains,
cache_routes: req.cache_routes,
session_token,
services,
domains,
cache_routes,
};

validate_not_empty(&session)?;
Expand All @@ -185,7 +188,7 @@ impl TryFrom<serde_json::Value> for Session {
pub fn create_preview_req_from_config(
config: &Config,
services_overwrite: &[(String, Url)],
) -> CreatePreviewRequest {
) -> UpsertSessionRequest {
let mut session_services: Vec<SessionService> = Vec::with_capacity(config.services.len());

for service in &config.services {
Expand All @@ -205,7 +208,7 @@ pub fn create_preview_req_from_config(
});
}

CreatePreviewRequest {
UpsertSessionRequest::Unnamed {
services: session_services,
domains: config.domains.clone(),
cache_routes: config.linkup.cache_routes.clone(),
Expand Down
4 changes: 2 additions & 2 deletions linkup/src/session_allocator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ impl<'a, S: StringStore> SessionAllocator<'a, S> {
#[cfg(test)]
mod tests {
use super::*;
use crate::{CreatePreviewRequest, MemoryStringStore};
use crate::{MemoryStringStore, UpsertSessionRequest};

#[tokio::test]
async fn identical_preview_requests_reuse_same_name() {
Expand Down Expand Up @@ -195,7 +195,7 @@ mod tests {
.to_string();

let first_session =
Session::try_from(serde_json::from_str::<CreatePreviewRequest>(&request_json).unwrap())
Session::try_from(serde_json::from_str::<UpsertSessionRequest>(&request_json).unwrap())
.unwrap();

let mut second_session = first_session.clone();
Expand Down
19 changes: 13 additions & 6 deletions local-server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ use hyper_util::{
rt::TokioExecutor,
};
use linkup::{
MemoryStringStore, NameKind, Session, SessionAllocator, TargetService, UpdateSessionRequest,
MemoryStringStore, NameKind, Session, SessionAllocator, TargetService, UpsertSessionRequest,
allow_all_cors, get_additional_headers, get_target_service,
};
use rustls::ServerConfig;
Expand Down Expand Up @@ -417,16 +417,23 @@ async fn handle_http_req(
async fn linkup_config_handler(
Extension(store): Extension<MemoryStringStore>,
Extension(dns_catalog): Extension<DnsCatalog>,
Json(update_req): Json<UpdateSessionRequest>,
Json(upsert_req): Json<UpsertSessionRequest>,
) -> impl IntoResponse {
let desired_name = update_req.desired_name.clone();
let domains = update_req
.domains
let (desired_name, req_domains) = match &upsert_req {
UpsertSessionRequest::Named {
desired_name,
domains,
..
} => (desired_name.clone(), domains),
UpsertSessionRequest::Unnamed { domains, .. } => (String::new(), domains),
};

let domains = req_domains
.iter()
.map(|domain| domain.domain.clone())
.collect::<Vec<String>>();

let server_conf: Session = match update_req.try_into() {
let server_conf: Session = match upsert_req.try_into() {
Ok(conf) => conf,
Err(e) => {
return ApiError::new(
Expand Down
4 changes: 2 additions & 2 deletions server-tests/tests/helpers.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::process::Command;

use linkup::{Domain, MemoryStringStore, SessionService, UpdateSessionRequest};
use linkup::{Domain, MemoryStringStore, SessionService, UpsertSessionRequest};
use linkup_local_server::{DnsCatalog, linkup_router};
use reqwest::Url;
use tokio::net::TcpListener;
Expand Down Expand Up @@ -54,7 +54,7 @@ pub fn create_session_request(name: String, fe_location: Option<String>) -> Stri
Some(location) => location,
None => "http://example.com".to_string(),
};
let req = UpdateSessionRequest {
let req = UpsertSessionRequest::Named {
desired_name: name,
session_token: "token".to_string(),
domains: vec![Domain {
Expand Down
4 changes: 2 additions & 2 deletions server-tests/tests/server_test.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use helpers::ServerKind;
use linkup::{CreatePreviewRequest, Domain, SessionService};
use linkup::{Domain, SessionService, UpsertSessionRequest};
use reqwest::Url;
use rstest::rstest;

Expand Down Expand Up @@ -84,7 +84,7 @@ pub fn create_preview_request(fe_location: Option<String>) -> String {
Some(location) => location,
None => "http://example.com".to_string(),
};
let req = CreatePreviewRequest {
let req = UpsertSessionRequest::Unnamed {
domains: vec![Domain {
domain: "example.com".to_string(),
default_service: "frontend".to_string(),
Expand Down
62 changes: 25 additions & 37 deletions worker/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ use http::{HeaderMap, Uri};
use http_error::HttpError;
use kv_store::CfWorkerStringStore;
use linkup::{
CreatePreviewRequest, NameKind, Session, SessionAllocator, UpdateSessionRequest, Version,
VersionChannel, allow_all_cors, get_additional_headers, get_target_service,
NameKind, Session, SessionAllocator, UpsertSessionRequest, Version, VersionChannel,
allow_all_cors, get_additional_headers, get_target_service,
};
use serde::{Deserialize, Serialize};
use tower_service::Service;
Expand Down Expand Up @@ -197,50 +197,38 @@ async fn get_tunnel_handler(
#[worker::send]
async fn linkup_session_handler(
State(state): State<LinkupState>,
Json(update_req): Json<UpdateSessionRequest>,
Json(upsert_req): Json<UpsertSessionRequest>,
) -> impl IntoResponse {
let store = CfWorkerStringStore::new(state.sessions_kv.clone());
let sessions = SessionAllocator::new(&store);

let desired_name = update_req.desired_name.clone();
let server_conf: Session = match update_req.try_into() {
Ok(conf) => conf,
Err(e) => {
return HttpError::new(
format!("Failed to parse server config: {} - Worker", e),
StatusCode::BAD_REQUEST,
)
.into_response();
}
};

let session_name = sessions
.store_session(server_conf, NameKind::Animal, &desired_name)
.await;

let name = match session_name {
Ok(session_name) => session_name,
Err(e) => {
return HttpError::new(
format!("Failed to store server config: {}", e),
StatusCode::INTERNAL_SERVER_ERROR,
)
.into_response();
}
};

(StatusCode::OK, name).into_response()
handle_session_upsert(state, upsert_req, NameKind::Animal).await
}

#[worker::send]
async fn linkup_preview_handler(
State(state): State<LinkupState>,
Json(update_req): Json<CreatePreviewRequest>,
Json(upsert_req): Json<UpsertSessionRequest>,
) -> impl IntoResponse {
handle_session_upsert(state, upsert_req, NameKind::SixChar).await
}

// TODO(augustoccesar)[2026-04-13]: This methods now exists because both the endpoints to
// create a preview session and a local session are exactly the same with the only
// difference being on the name generator kind.
// We should probably deprecate them as separate endpoints and create a new one that
// can take the name generator as part of the request.
async fn handle_session_upsert(
state: LinkupState,
req: UpsertSessionRequest,
name_kind: NameKind,
) -> impl IntoResponse {
let store = CfWorkerStringStore::new(state.sessions_kv.clone());
let sessions = SessionAllocator::new(&store);

let server_conf: Session = match update_req.try_into() {
let desired_name = match &req {
UpsertSessionRequest::Named { desired_name, .. } => desired_name.clone(),
UpsertSessionRequest::Unnamed { .. } => String::new(),
};

let session: Session = match req.try_into() {
Ok(conf) => conf,
Err(e) => {
return HttpError::new(
Expand All @@ -252,7 +240,7 @@ async fn linkup_preview_handler(
};

let session_name = sessions
.store_session(server_conf, NameKind::SixChar, "")
.store_session(session, name_kind, &desired_name)
.await;

let name = match session_name {
Expand Down
Loading