Skip to content

Commit

Permalink
open workspace from a hash url
Browse files Browse the repository at this point in the history
  • Loading branch information
lyang2821 committed Oct 18, 2024
1 parent e7d6ea9 commit fd7c918
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 42 deletions.
8 changes: 8 additions & 0 deletions lapdev-api/src/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ fn v1_api_routes(additional_router: Option<Router<CoreState>>) -> Router<CoreSta
"/organizations/:org_id/workspaces/:workspace_name/stop",
post(workspace::stop_workspace),
)
.route(
"/organizations/:org_id/workspaces/:workspace_name/ports",
get(workspace::workspace_ports),
)
.route(
"/organizations/:org_id/workspaces/:workspace_name/ports/:port",
put(workspace::update_workspace_port),
)
.route("/account/ssh_keys", post(account::create_ssh_key))
.route("/account/ssh_keys", get(account::all_ssh_keys))
.route("/account/ssh_keys/:key_id", delete(account::delete_ssh_key))
Expand Down
99 changes: 97 additions & 2 deletions lapdev-api/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,17 @@ use axum::{
Json,
};
use axum_extra::{headers::Cookie, TypedHeader};
use chrono::Utc;
use hyper::StatusCode;
use lapdev_common::{NewWorkspace, WorkspaceInfo, WorkspaceService, WorkspaceStatus};
use lapdev_common::{
AuditAction, AuditResourceKind, NewWorkspace, UpdateWorkspacePort, WorkspaceInfo,
WorkspacePort, WorkspaceService, WorkspaceStatus,
};
use lapdev_db::entities;
use lapdev_rpc::error::ApiError;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use sea_orm::{
ActiveModelTrait, ActiveValue, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait,
};
use tracing::error;
use uuid::Uuid;

Expand Down Expand Up @@ -257,3 +263,92 @@ pub async fn stop_workspace(
.await?;
Ok(StatusCode::NO_CONTENT.into_response())
}

pub async fn workspace_ports(
TypedHeader(cookie): TypedHeader<Cookie>,
Path((org_id, workspace_name)): Path<(Uuid, String)>,
State(state): State<CoreState>,
) -> Result<Response, ApiError> {
let user = state.authenticate(&cookie).await?;
state
.db
.get_organization_member(user.id, org_id)
.await
.map_err(|_| ApiError::Unauthorized)?;
let ws = state
.db
.get_workspace_by_name(&workspace_name)
.await
.map_err(|_| ApiError::InvalidRequest("workspace name doesn't exist".to_string()))?;
if ws.user_id != user.id {
return Err(ApiError::Unauthorized);
}
let ports = state.db.get_workspace_ports(ws.id).await?;
Ok(Json(
ports
.into_iter()
.map(|p| WorkspacePort {
port: p.port as u16,
shared: p.shared,
})
.collect::<Vec<_>>(),
)
.into_response())
}

pub async fn update_workspace_port(
TypedHeader(cookie): TypedHeader<Cookie>,
Path((org_id, workspace_name, port)): Path<(Uuid, String, u16)>,
State(state): State<CoreState>,
info: RequestInfo,
Json(update_workspace_port): Json<UpdateWorkspacePort>,
) -> Result<Response, ApiError> {
let user = state.authenticate(&cookie).await?;
state
.db
.get_organization_member(user.id, org_id)
.await
.map_err(|_| ApiError::Unauthorized)?;
let ws = state
.db
.get_workspace_by_name(&workspace_name)
.await
.map_err(|_| ApiError::InvalidRequest("workspace name doesn't exist".to_string()))?;
if ws.user_id != user.id {
return Err(ApiError::Unauthorized);
}
let port = state
.db
.get_workspace_port(ws.id, port)
.await?
.ok_or_else(|| ApiError::InvalidRequest(format!("port {port} not found")))?;

let now = Utc::now();
let txn = state.db.conn.begin().await?;
state
.conductor
.enterprise
.insert_audit_log(
&txn,
now.into(),
ws.user_id,
ws.organization_id,
AuditResourceKind::Workspace.to_string(),
ws.id,
format!("{} port {}", ws.name, port.port),
AuditAction::WorkspaceUpdate.to_string(),
info.ip.clone(),
info.user_agent.clone(),
)
.await?;
entities::workspace_port::ActiveModel {
id: ActiveValue::Set(port.id),
shared: ActiveValue::Set(update_workspace_port.shared),
..Default::default()
}
.update(&txn)
.await?;
txn.commit().await?;

Ok(StatusCode::NO_CONTENT.into_response())
}
13 changes: 13 additions & 0 deletions lapdev-common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ pub struct NewWorkspace {
pub source: RepoSource,
pub branch: Option<String>,
pub machine_type_id: Uuid,
pub from_hash: bool,
}

#[derive(Serialize, Deserialize, Debug)]
Expand Down Expand Up @@ -315,6 +316,17 @@ pub struct WorkspaceInfo {
pub hostname: String,
}

#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct WorkspacePort {
pub port: u16,
pub shared: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct UpdateWorkspacePort {
pub shared: bool,
}

#[derive(Serialize, Deserialize, Debug, Clone, Hash, Eq, PartialEq)]
pub struct WorkspaceService {
pub name: String,
Expand Down Expand Up @@ -419,6 +431,7 @@ pub enum AuditAction {
WorkspaceDelete,
WorkspaceStart,
WorkspaceStop,
WorkspaceUpdate,
ProjectCreate,
ProjectDelete,
ProjectUpdateEnv,
Expand Down
15 changes: 15 additions & 0 deletions lapdev-common/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,18 @@ pub fn sha256(input: &str) -> String {
let hash = Sha256::digest(input.as_bytes());
base16ct::lower::encode_string(&hash)
}

pub fn format_repo_url(repo: &str) -> String {
let repo = repo.trim().to_lowercase();
let repo = if !repo.starts_with("http://")
&& !repo.starts_with("https://")
&& !repo.starts_with("ssh://")
{
format!("https://{repo}")
} else {
repo.to_string()
};
repo.strip_suffix('/')
.map(|r| r.to_string())
.unwrap_or(repo)
}
36 changes: 17 additions & 19 deletions lapdev-conductor/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ use chrono::Utc;
use data_encoding::BASE64_MIME;
use futures::{channel::mpsc::UnboundedReceiver, stream::AbortHandle, SinkExt, StreamExt};
use git2::{Cred, RemoteCallbacks};
use lapdev_common::{utils, PrebuildReplicaStatus, WorkspaceHostStatus};
use lapdev_common::{
utils::rand_string, AuditAction, AuditResourceKind, AuthProvider, BuildTarget, CpuCore,
CreateWorkspaceRequest, DeleteWorkspaceRequest, GitBranch, NewProject, NewProjectResponse,
NewWorkspace, NewWorkspaceResponse, PrebuildInfo, PrebuildStatus, PrebuildUpdateEvent,
ProjectRequest, RepoBuildInfo, RepoBuildOutput, RepoSource, StartWorkspaceRequest,
StopWorkspaceRequest, UsageResourceKind, WorkspaceStatus, WorkspaceUpdateEvent,
};
use lapdev_common::{PrebuildReplicaStatus, WorkspaceHostStatus};
use lapdev_db::{api::DbApi, entities};
use lapdev_enterprise::enterprise::Enterprise;
use lapdev_rpc::{
Expand Down Expand Up @@ -520,21 +520,6 @@ impl Conductor {
}
}

fn format_repo_url(&self, repo: &str) -> String {
let repo = repo.trim().to_lowercase();
let repo = if !repo.starts_with("http://")
&& !repo.starts_with("https://")
&& !repo.starts_with("ssh://")
{
format!("https://{repo}")
} else {
repo.to_string()
};
repo.strip_suffix('/')
.map(|r| r.to_string())
.unwrap_or(repo)
}

async fn get_raw_repo_details(
&self,
repo_url: &str,
Expand Down Expand Up @@ -582,7 +567,7 @@ impl Conductor {
};
tracing::warn!("can't open repo {local_repo_url}: {err}");
ApiError::RepositoryInvalid(
"Repository URL invalid or we don't have access to it. If it's a private repo, you can try to update the permission in User Settings -> Git Providers.".to_string(),
format!("Repository {local_repo_url} is invalid or we don't have access to it. If it's a private repo, you can try to update the permission in User Settings -> Git Providers."),
)
})?
};
Expand Down Expand Up @@ -662,7 +647,7 @@ impl Conductor {
ip: Option<String>,
user_agent: Option<String>,
) -> Result<NewProjectResponse, ApiError> {
let repo = self.format_repo_url(&project.repo);
let repo = utils::format_repo_url(&project.repo);

let oauth = self.find_match_oauth_for_repo(&user, &repo).await?;

Expand Down Expand Up @@ -1483,7 +1468,7 @@ impl Conductor {
) -> Result<RepoDetails, ApiError> {
let project = match source {
RepoSource::Url(repo) => {
let repo = self.format_repo_url(repo);
let repo = utils::format_repo_url(repo);
let project = self.db.get_project_by_repo(org_id, &repo).await?;
if let Some(project) = project {
project
Expand Down Expand Up @@ -1948,6 +1933,19 @@ impl Conductor {
workspace.branch.as_deref(),
)
.await?;
if workspace.from_hash {
// if the workspace was created from hash
// we check if there's existing workspace
if let Ok(Some(workspace)) = self
.db
.get_workspace_by_url(org.id, user.id, &repo.url)
.await
{
return Ok(NewWorkspaceResponse {
name: workspace.name,
});
}
}

let machine_type = self
.db
Expand Down
14 changes: 3 additions & 11 deletions lapdev-dashboard/src/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use leptos::{
component, create_action, create_local_resource, expect_context, use_context, view, window,
IntoView, Resource, RwSignal, Signal, SignalGet, SignalGetUntracked, SignalSet, SignalWith,
};
use leptos_router::{use_location, use_params_map};
use leptos_router::use_params_map;

use crate::{cluster::OauthSettings, modal::ErrorResponse};

Expand All @@ -18,17 +18,9 @@ pub async fn get_login() -> Result<MeUser> {
}

async fn now_login(provider: AuthProvider) -> Result<()> {
let location = use_location();
let next = format!("{}{}", location.pathname.get_untracked(), {
let search = location.search.get_untracked();
if search.is_empty() {
"".to_string()
} else {
format!("?{search}")
}
});
let next = urlencoding::encode(&next).to_string();
let location = window().window().location();
let next = location.href().unwrap_or_default();
let next = urlencoding::encode(&next).to_string();
let resp = Request::get("/api/private/session")
.query([
("provider", &provider.to_string()),
Expand Down
8 changes: 2 additions & 6 deletions lapdev-dashboard/src/git_provider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,9 @@ async fn connect_oauth(
provider: AuthProvider,
update_read_repo: Option<bool>,
) -> Result<(), ErrorResponse> {
let location = use_location();
let next = format!(
"{}{}",
location.pathname.get_untracked(),
location.search.get_untracked()
);
let location = window().window().location();
let next = location.href().unwrap_or_default();
let next = urlencoding::encode(&next).to_string();
let url = if update_read_repo.is_some() {
"/api/v1/account/git_providers/update_scope"
} else {
Expand Down
Loading

0 comments on commit fd7c918

Please sign in to comment.