mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-30 07:33:59 +08:00
feat(api)!: unify sandbox references
Signed-off-by: Drew Newberry <anewberry@nvidia.com>
This commit is contained in:
+10
-7
@@ -56,13 +56,16 @@ cross-origin or sibling-subdomain request.
|
||||
|
||||
Public workspace-scoped RPCs carry a typed `WorkspaceSelector`. A request must
|
||||
select one non-empty workspace explicitly; `default` is an ordinary explicit
|
||||
name, not an omitted-value fallback. Sandbox, sandbox template, provider, and
|
||||
service list RPCs also accept an all-workspaces marker after Platform Admin
|
||||
authorization. Single-workspace handlers reject that marker. Platform-global
|
||||
policy operations require the selector to be absent, while workspace policy
|
||||
operations require it. The gateway authorizes the selected scope before
|
||||
performing resource lookup so malformed, unsupported, and unauthorized scopes
|
||||
have consistent behavior across resource types.
|
||||
name, not an omitted-value fallback. Sandbox-scoped RPCs instead carry a
|
||||
`SandboxReference`: name references include a named workspace selector, while
|
||||
canonical ID references omit it. The gateway resolves either form to the same
|
||||
persisted sandbox record and authorizes that record's workspace. Missing and
|
||||
unauthorized references use the same response within each principal class so
|
||||
the resolver does not expose an object-existence oracle. Sandbox, sandbox
|
||||
template, provider, and service list RPCs also accept an all-workspaces marker
|
||||
after Platform Admin authorization. Single-workspace handlers reject that
|
||||
marker. Platform-global policy operations require the sandbox reference to be
|
||||
absent, while sandbox policy operations require it.
|
||||
|
||||
Docker and Podman report the local address through which their sandboxes can
|
||||
reach the gateway. When the primary listener covers that address, the gateway
|
||||
|
||||
@@ -58,8 +58,9 @@ pub async fn sandbox_provider_list(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.list_sandbox_providers(ListSandboxProvidersRequest {
|
||||
sandbox_name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -90,8 +91,9 @@ pub async fn sandbox_provider_attach(
|
||||
// Fetch current sandbox to get resource_version for CAS
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -103,10 +105,11 @@ pub async fn sandbox_provider_attach(
|
||||
|
||||
let response = match client
|
||||
.attach_sandbox_provider(AttachSandboxProviderRequest {
|
||||
sandbox_name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
provider_name: provider.to_string(),
|
||||
expected_resource_version: resource_version,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -146,8 +149,9 @@ pub async fn sandbox_provider_detach(
|
||||
// Fetch current sandbox to get resource_version for CAS
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -159,10 +163,11 @@ pub async fn sandbox_provider_detach(
|
||||
|
||||
let response = match client
|
||||
.detach_sandbox_provider(DetachSandboxProviderRequest {
|
||||
sandbox_name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
provider_name: provider.to_string(),
|
||||
expected_resource_version: resource_version,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
{
|
||||
|
||||
@@ -2137,7 +2137,7 @@ enum ServiceCommands {
|
||||
offset: u32,
|
||||
|
||||
/// List services across all workspaces (overrides --workspace).
|
||||
#[arg(long)]
|
||||
#[arg(long, conflicts_with = "sandbox")]
|
||||
all_workspaces: bool,
|
||||
|
||||
/// Output format.
|
||||
@@ -6059,6 +6059,19 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_list_rejects_sandbox_with_all_workspaces() {
|
||||
let result = Cli::try_parse_from([
|
||||
"openshell",
|
||||
"service",
|
||||
"list",
|
||||
"my-sandbox",
|
||||
"--all-workspaces",
|
||||
]);
|
||||
|
||||
assert!(result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn service_get_accepts_optional_service_name() {
|
||||
let cli = Cli::try_parse_from(["openshell", "service", "get", "my-sandbox", "api"])
|
||||
|
||||
+162
-87
@@ -686,10 +686,12 @@ pub async fn sandbox_create(
|
||||
let setting = parse_cli_setting_value(settings::PROPOSAL_APPROVAL_MODE_KEY, approval_mode)?;
|
||||
match client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: sandbox_name.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
sandbox_name.clone(),
|
||||
)),
|
||||
setting_key: settings::PROPOSAL_APPROVAL_MODE_KEY.to_string(),
|
||||
setting_value: Some(setting),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@@ -752,7 +754,9 @@ pub async fn sandbox_create(
|
||||
};
|
||||
let mut stream = client
|
||||
.watch_sandbox(WatchSandboxRequest {
|
||||
id: sandbox_id.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox_id.clone(),
|
||||
)),
|
||||
follow_status: true,
|
||||
follow_logs: true,
|
||||
follow_events: true,
|
||||
@@ -1494,8 +1498,10 @@ pub async fn sandbox_get(
|
||||
|
||||
let response = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -1511,7 +1517,9 @@ pub async fn sandbox_get(
|
||||
};
|
||||
|
||||
let config = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest { sandbox_id })
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
.into_inner();
|
||||
@@ -1656,8 +1664,10 @@ pub async fn sandbox_exec_grpc(
|
||||
// Resolve sandbox name to id.
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -1725,7 +1735,9 @@ pub async fn sandbox_exec_grpc(
|
||||
// Make the streaming gRPC call.
|
||||
let mut stream = client
|
||||
.exec_sandbox(ExecSandboxRequest {
|
||||
sandbox_id: sandbox.object_id().to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox.object_id(),
|
||||
)),
|
||||
command: command.to_vec(),
|
||||
workdir: workdir.unwrap_or_default().to_string(),
|
||||
environment: environment.clone(),
|
||||
@@ -1876,7 +1888,7 @@ async fn create_forward_session_token(
|
||||
) -> std::result::Result<String, ForwardTcpConnectionError> {
|
||||
let response = client
|
||||
.create_ssh_session(CreateSshSessionRequest {
|
||||
sandbox_id: sandbox_id.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await
|
||||
.map_err(ForwardTcpConnectionError::from_status)?;
|
||||
@@ -1890,8 +1902,10 @@ async fn fetch_ready_sandbox_for_forward(
|
||||
) -> Result<Sandbox> {
|
||||
let response = match client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -1989,7 +2003,7 @@ async fn forward_one_tcp_connection(
|
||||
tx.send(TcpForwardFrame {
|
||||
payload: Some(openshell_core::proto::tcp_forward_frame::Payload::Init(
|
||||
TcpForwardInit {
|
||||
sandbox_id,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
service_id,
|
||||
target: Some(tcp_forward_init::Target::Tcp(TcpRelayTarget {
|
||||
host: target_host,
|
||||
@@ -2109,7 +2123,9 @@ async fn sandbox_exec_interactive_grpc(
|
||||
input_tx
|
||||
.send(ExecSandboxInput {
|
||||
payload: Some(exec_sandbox_input::Payload::Start(ExecSandboxRequest {
|
||||
sandbox_id: sandbox.object_id().to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox.object_id(),
|
||||
)),
|
||||
command: command.to_vec(),
|
||||
workdir: workdir.unwrap_or_default().to_string(),
|
||||
environment: environment.clone(),
|
||||
@@ -3065,8 +3081,10 @@ pub async fn sandbox_delete(
|
||||
|
||||
let response = match client
|
||||
.delete_sandbox(DeleteSandboxRequest {
|
||||
name: name.clone(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.clone(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -3117,8 +3135,10 @@ pub async fn sandbox_stop(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let sandbox = client
|
||||
.stop_sandbox(StopSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -3140,8 +3160,10 @@ pub async fn sandbox_start(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let sandbox = client
|
||||
.start_sandbox(StartSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -3177,7 +3199,7 @@ async fn wait_for_lifecycle_phase(
|
||||
let sandbox_id = sandbox.object_id().to_string();
|
||||
let mut stream = client
|
||||
.watch_sandbox(WatchSandboxRequest {
|
||||
id: sandbox_id,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
follow_status: true,
|
||||
follow_logs: false,
|
||||
follow_events: false,
|
||||
@@ -3238,11 +3260,12 @@ pub async fn service_expose(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.expose_service(ExposeServiceRequest {
|
||||
sandbox: sandbox.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, sandbox,
|
||||
)),
|
||||
service: service.to_string(),
|
||||
target_port: u32::from(target_port),
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.map_err(service_expose_status_error)?
|
||||
@@ -3289,13 +3312,16 @@ pub async fn service_list(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.list_services(ListServicesRequest {
|
||||
sandbox: sandbox.unwrap_or_default().to_string(),
|
||||
sandbox_ref: sandbox
|
||||
.map(|name| openshell_core::proto::sandbox_reference_by_name(workspace, name)),
|
||||
limit,
|
||||
offset,
|
||||
workspace_scope: Some(if all_workspaces {
|
||||
openshell_core::proto::all_workspaces_selector()
|
||||
} else {
|
||||
openshell_core::proto::workspace_selector(workspace)
|
||||
workspace_scope: sandbox.is_none().then(|| {
|
||||
if all_workspaces {
|
||||
openshell_core::proto::all_workspaces_selector()
|
||||
} else {
|
||||
openshell_core::proto::workspace_selector(workspace)
|
||||
}
|
||||
}),
|
||||
})
|
||||
.await
|
||||
@@ -3334,9 +3360,10 @@ pub async fn service_get(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.get_service(GetServiceRequest {
|
||||
sandbox: sandbox.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, sandbox,
|
||||
)),
|
||||
service: service.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.map_err(|status| service_status_error("get service", "sandbox:read", status))?
|
||||
@@ -3356,9 +3383,10 @@ pub async fn service_delete(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.delete_service(DeleteServiceRequest {
|
||||
sandbox: sandbox.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, sandbox,
|
||||
)),
|
||||
service: service.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.map_err(|status| service_status_error("delete service", "sandbox:write", status))?
|
||||
@@ -4152,7 +4180,7 @@ pub async fn sandbox_policy_set_global(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: String::new(),
|
||||
sandbox_ref: None,
|
||||
policy: Some(policy),
|
||||
global: true,
|
||||
..Default::default()
|
||||
@@ -4184,8 +4212,10 @@ pub async fn sandbox_settings_get(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -4195,7 +4225,9 @@ pub async fn sandbox_settings_get(
|
||||
|
||||
let response = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_id: sandbox.object_id().to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox.object_id(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -4352,7 +4384,7 @@ pub async fn gateway_setting_set(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: String::new(),
|
||||
sandbox_ref: None,
|
||||
setting_key: key.to_string(),
|
||||
setting_value: Some(setting_value),
|
||||
global: true,
|
||||
@@ -4385,10 +4417,12 @@ pub async fn sandbox_setting_set(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
setting_key: key.to_string(),
|
||||
setting_value: Some(setting_value),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@@ -4418,7 +4452,7 @@ pub async fn gateway_setting_delete(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: String::new(),
|
||||
sandbox_ref: None,
|
||||
setting_key: key.to_string(),
|
||||
delete_setting: true,
|
||||
global: true,
|
||||
@@ -4451,10 +4485,12 @@ pub async fn sandbox_setting_delete(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let response = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
setting_key: key.to_string(),
|
||||
delete_setting: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@@ -4497,10 +4533,12 @@ pub async fn sandbox_policy_set(
|
||||
// Get current version so we can detect no-ops.
|
||||
let current_version = client
|
||||
.get_sandbox_policy_status(GetSandboxPolicyStatusRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
version: 0,
|
||||
global: false,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.ok()
|
||||
@@ -4509,9 +4547,11 @@ pub async fn sandbox_policy_set(
|
||||
|
||||
let response = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
policy: Some(policy),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@@ -4556,10 +4596,12 @@ pub async fn sandbox_policy_set(
|
||||
|
||||
let status_resp = client
|
||||
.get_sandbox_policy_status(GetSandboxPolicyStatusRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
version: resp.version,
|
||||
global: false,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -4635,8 +4677,10 @@ pub async fn sandbox_policy_update(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -4651,7 +4695,9 @@ pub async fn sandbox_policy_update(
|
||||
};
|
||||
|
||||
let current = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest { sandbox_id })
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
.into_inner();
|
||||
@@ -4683,9 +4729,11 @@ pub async fn sandbox_policy_update(
|
||||
let current_hash = current.policy_hash.clone();
|
||||
let response = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
merge_operations: plan.merge_operations,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@@ -4730,10 +4778,12 @@ pub async fn sandbox_policy_update(
|
||||
|
||||
let status_resp = client
|
||||
.get_sandbox_policy_status(GetSandboxPolicyStatusRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
version: response.version,
|
||||
global: false,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -4838,10 +4888,12 @@ where
|
||||
|
||||
let status_resp = client
|
||||
.get_sandbox_policy_status(GetSandboxPolicyStatusRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
version,
|
||||
global: false,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -4922,8 +4974,10 @@ where
|
||||
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -4937,7 +4991,7 @@ where
|
||||
|
||||
let config = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_id: sandbox_id.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -5032,10 +5086,9 @@ pub async fn sandbox_policy_get_global(
|
||||
|
||||
let status_resp = client
|
||||
.get_sandbox_policy_status(GetSandboxPolicyStatusRequest {
|
||||
name: String::new(),
|
||||
sandbox_ref: None,
|
||||
version,
|
||||
global: true,
|
||||
workspace_scope: None,
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -5172,11 +5225,13 @@ pub async fn sandbox_policy_list(
|
||||
|
||||
let resp = client
|
||||
.list_sandbox_policies(ListSandboxPoliciesRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
limit,
|
||||
offset: 0,
|
||||
global: false,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -5207,11 +5262,10 @@ pub async fn sandbox_policy_list_global(
|
||||
|
||||
let resp = client
|
||||
.list_sandbox_policies(ListSandboxPoliciesRequest {
|
||||
name: String::new(),
|
||||
sandbox_ref: None,
|
||||
limit,
|
||||
offset: 0,
|
||||
global: true,
|
||||
workspace_scope: None,
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -5302,8 +5356,10 @@ pub async fn sandbox_logs(
|
||||
// Resolve sandbox name to id.
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -5336,7 +5392,9 @@ pub async fn sandbox_logs(
|
||||
// Streaming mode: use WatchSandbox.
|
||||
let mut stream = client
|
||||
.watch_sandbox(WatchSandboxRequest {
|
||||
id: sandbox.object_id().to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox.object_id().to_string(),
|
||||
)),
|
||||
follow_status: false,
|
||||
follow_logs: true,
|
||||
follow_events: false,
|
||||
@@ -5363,12 +5421,13 @@ pub async fn sandbox_logs(
|
||||
// One-shot mode: use GetSandboxLogs.
|
||||
let resp = client
|
||||
.get_sandbox_logs(GetSandboxLogsRequest {
|
||||
sandbox_id: sandbox.object_id().to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox.object_id(),
|
||||
)),
|
||||
lines,
|
||||
since_ms,
|
||||
sources: source_filter,
|
||||
min_level: level.to_uppercase(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -5442,9 +5501,11 @@ pub async fn sandbox_draft_get(
|
||||
|
||||
let response = client
|
||||
.get_draft_policy(GetDraftPolicyRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
status_filter: status_filter.unwrap_or("").to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -5550,9 +5611,11 @@ pub async fn sandbox_draft_approve(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let review_token = client
|
||||
.get_draft_policy(GetDraftPolicyRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
status_filter: String::new(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -5565,9 +5628,11 @@ pub async fn sandbox_draft_approve(
|
||||
|
||||
let response = client
|
||||
.approve_draft_chunk(ApproveDraftChunkRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
chunk_id: chunk_id.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
review_token,
|
||||
})
|
||||
.await
|
||||
@@ -5597,10 +5662,12 @@ pub async fn sandbox_draft_reject(
|
||||
|
||||
client
|
||||
.reject_draft_chunk(RejectDraftChunkRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
chunk_id: chunk_id.to_string(),
|
||||
reason: reason.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -5621,9 +5688,11 @@ pub async fn sandbox_draft_approve_all(
|
||||
let mut client = grpc_client(server, tls).await?;
|
||||
let approvals = client
|
||||
.get_draft_policy(GetDraftPolicyRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
status_filter: "pending".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -5638,9 +5707,11 @@ pub async fn sandbox_draft_approve_all(
|
||||
|
||||
let response = client
|
||||
.approve_all_draft_chunks(ApproveAllDraftChunksRequest {
|
||||
name: name.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
include_security_flagged,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
approvals,
|
||||
})
|
||||
.await
|
||||
@@ -5669,8 +5740,10 @@ pub async fn sandbox_draft_clear(
|
||||
|
||||
let response = client
|
||||
.clear_draft_chunks(ClearDraftChunksRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -5696,8 +5769,10 @@ pub async fn sandbox_draft_history(
|
||||
|
||||
let response = client
|
||||
.get_draft_history(GetDraftHistoryRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
|
||||
@@ -91,8 +91,10 @@ async fn ssh_session_config(
|
||||
// Resolve sandbox name to id.
|
||||
let sandbox = client
|
||||
.get_sandbox(GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
name.to_string(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?
|
||||
@@ -105,7 +107,9 @@ async fn ssh_session_config(
|
||||
let response = loop {
|
||||
match client
|
||||
.create_ssh_session(CreateSshSessionRequest {
|
||||
sandbox_id: sandbox.object_id().to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox.object_id(),
|
||||
)),
|
||||
})
|
||||
.await
|
||||
{
|
||||
@@ -1436,7 +1440,7 @@ pub async fn sandbox_ssh_proxy(
|
||||
tx.send(TcpForwardFrame {
|
||||
payload: Some(openshell_core::proto::tcp_forward_frame::Payload::Init(
|
||||
TcpForwardInit {
|
||||
sandbox_id: sandbox_id.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
service_id: format!("ssh-proxy:{sandbox_id}"),
|
||||
target: Some(tcp_forward_init::Target::Ssh(SshRelayTarget {})),
|
||||
authorization_token: token.to_string(),
|
||||
|
||||
@@ -39,6 +39,13 @@ use tokio_stream::wrappers::TcpListenerStream;
|
||||
use tonic::transport::{Certificate as TlsCertificate, Identity, Server, ServerTlsConfig};
|
||||
use tonic::{Response, Status};
|
||||
|
||||
fn sandbox_reference_name(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String {
|
||||
match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) {
|
||||
Some(openshell_core::proto::sandbox_reference::Identifier::Name(name)) => name.clone(),
|
||||
_ => panic!("expected sandbox name reference"),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct ProviderState {
|
||||
providers: Arc<Mutex<HashMap<String, Provider>>>,
|
||||
@@ -174,7 +181,8 @@ impl OpenShell for TestOpenShell {
|
||||
&self,
|
||||
request: tonic::Request<GetSandboxRequest>,
|
||||
) -> Result<Response<SandboxResponse>, Status> {
|
||||
let name = request.into_inner().name;
|
||||
let request = request.into_inner();
|
||||
let name = sandbox_reference_name(request.sandbox_ref.as_ref());
|
||||
// Return a minimal sandbox with metadata for CAS operations
|
||||
Ok(Response::new(SandboxResponse {
|
||||
sandbox: Some(Sandbox {
|
||||
@@ -208,7 +216,8 @@ impl OpenShell for TestOpenShell {
|
||||
&self,
|
||||
request: tonic::Request<ListSandboxProvidersRequest>,
|
||||
) -> Result<Response<ListSandboxProvidersResponse>, Status> {
|
||||
let sandbox_name = request.into_inner().sandbox_name;
|
||||
let request = request.into_inner();
|
||||
let sandbox_name = sandbox_reference_name(request.sandbox_ref.as_ref());
|
||||
self.state
|
||||
.sandbox_provider_requests
|
||||
.lock()
|
||||
@@ -237,12 +246,13 @@ impl OpenShell for TestOpenShell {
|
||||
request: tonic::Request<AttachSandboxProviderRequest>,
|
||||
) -> Result<Response<AttachSandboxProviderResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let sandbox_name = sandbox_reference_name(request.sandbox_ref.as_ref());
|
||||
self.state
|
||||
.sandbox_provider_requests
|
||||
.lock()
|
||||
.await
|
||||
.push(SandboxProviderRequestLog::Attach {
|
||||
sandbox_name: request.sandbox_name.clone(),
|
||||
sandbox_name: sandbox_name.clone(),
|
||||
provider_name: request.provider_name.clone(),
|
||||
});
|
||||
if !self
|
||||
@@ -255,9 +265,7 @@ impl OpenShell for TestOpenShell {
|
||||
return Err(Status::failed_precondition("provider not found"));
|
||||
}
|
||||
let mut sandbox_providers = self.state.sandbox_providers.lock().await;
|
||||
let providers = sandbox_providers
|
||||
.entry(request.sandbox_name.clone())
|
||||
.or_default();
|
||||
let providers = sandbox_providers.entry(sandbox_name.clone()).or_default();
|
||||
let attached = if providers.contains(&request.provider_name) {
|
||||
false
|
||||
} else {
|
||||
@@ -266,7 +274,7 @@ impl OpenShell for TestOpenShell {
|
||||
};
|
||||
let sandbox = Sandbox {
|
||||
metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta {
|
||||
name: request.sandbox_name,
|
||||
name: sandbox_name,
|
||||
..Default::default()
|
||||
}),
|
||||
spec: Some(openshell_core::proto::SandboxSpec {
|
||||
@@ -286,24 +294,23 @@ impl OpenShell for TestOpenShell {
|
||||
request: tonic::Request<DetachSandboxProviderRequest>,
|
||||
) -> Result<Response<DetachSandboxProviderResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let sandbox_name = sandbox_reference_name(request.sandbox_ref.as_ref());
|
||||
self.state
|
||||
.sandbox_provider_requests
|
||||
.lock()
|
||||
.await
|
||||
.push(SandboxProviderRequestLog::Detach {
|
||||
sandbox_name: request.sandbox_name.clone(),
|
||||
sandbox_name: sandbox_name.clone(),
|
||||
provider_name: request.provider_name.clone(),
|
||||
});
|
||||
let mut sandbox_providers = self.state.sandbox_providers.lock().await;
|
||||
let providers = sandbox_providers
|
||||
.entry(request.sandbox_name.clone())
|
||||
.or_default();
|
||||
let providers = sandbox_providers.entry(sandbox_name.clone()).or_default();
|
||||
let before_len = providers.len();
|
||||
providers.retain(|name| name != &request.provider_name);
|
||||
let detached = providers.len() != before_len;
|
||||
let sandbox = Sandbox {
|
||||
metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta {
|
||||
name: request.sandbox_name,
|
||||
name: sandbox_name,
|
||||
..Default::default()
|
||||
}),
|
||||
spec: Some(openshell_core::proto::SandboxSpec {
|
||||
|
||||
@@ -56,6 +56,20 @@ fn selected_workspace(
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_reference_name(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String {
|
||||
match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) {
|
||||
Some(openshell_core::proto::sandbox_reference::Identifier::Name(name)) => name.clone(),
|
||||
_ => panic!("expected sandbox name reference"),
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_reference_id(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String {
|
||||
match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) {
|
||||
Some(openshell_core::proto::sandbox_reference::Identifier::Id(id)) => id.clone(),
|
||||
_ => panic!("expected sandbox ID reference"),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Default)]
|
||||
struct SandboxState {
|
||||
deleted_names: Arc<Mutex<Vec<Vec<String>>>>,
|
||||
@@ -182,7 +196,8 @@ impl OpenShell for TestOpenShell {
|
||||
&self,
|
||||
request: tonic::Request<GetSandboxRequest>,
|
||||
) -> Result<Response<SandboxResponse>, Status> {
|
||||
let name = request.into_inner().name;
|
||||
let request = request.into_inner();
|
||||
let name = sandbox_reference_name(request.sandbox_ref.as_ref());
|
||||
let mut sandbox = Sandbox {
|
||||
metadata: Some(openshell_core::proto::datamodel::v1::ObjectMeta {
|
||||
id: format!("id-{name}"),
|
||||
@@ -332,7 +347,7 @@ impl OpenShell for TestOpenShell {
|
||||
.deleted_names
|
||||
.lock()
|
||||
.await
|
||||
.push(vec![request.name.clone()]);
|
||||
.push(vec![sandbox_reference_name(request.sandbox_ref.as_ref())]);
|
||||
let delete_failure = self.state.fail_delete_sandbox_message.lock().await.take();
|
||||
if let Some(message) = delete_failure {
|
||||
return Err(Status::internal(message));
|
||||
@@ -386,7 +401,8 @@ impl OpenShell for TestOpenShell {
|
||||
{
|
||||
return Err(Status::failed_precondition("sandbox is not ready"));
|
||||
}
|
||||
let sandbox_id = request.into_inner().sandbox_id;
|
||||
let request = request.into_inner();
|
||||
let sandbox_id = sandbox_reference_id(request.sandbox_ref.as_ref());
|
||||
Ok(Response::new(CreateSshSessionResponse {
|
||||
sandbox_id,
|
||||
token: "test-token".to_string(),
|
||||
@@ -574,7 +590,8 @@ impl OpenShell for TestOpenShell {
|
||||
&self,
|
||||
request: tonic::Request<WatchSandboxRequest>,
|
||||
) -> Result<Response<Self::WatchSandboxStream>, Status> {
|
||||
let sandbox_id = request.into_inner().id;
|
||||
let request = request.into_inner();
|
||||
let sandbox_id = sandbox_reference_id(request.sandbox_ref.as_ref());
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
let vm_error_after_started = self.state.vm_error_after_started.load(Ordering::SeqCst);
|
||||
let vm_error_with_observed_exit = self
|
||||
|
||||
@@ -32,6 +32,20 @@ use tokio_stream::wrappers::TcpListenerStream;
|
||||
use tonic::transport::{Certificate as TlsCertificate, Identity, Server, ServerTlsConfig};
|
||||
use tonic::{Response, Status};
|
||||
|
||||
fn sandbox_reference_name(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String {
|
||||
match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) {
|
||||
Some(openshell_core::proto::sandbox_reference::Identifier::Name(name)) => name.clone(),
|
||||
_ => panic!("expected sandbox name reference"),
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_reference_id(sandbox_ref: Option<&openshell_core::proto::SandboxReference>) -> String {
|
||||
match sandbox_ref.and_then(|reference| reference.identifier.as_ref()) {
|
||||
Some(openshell_core::proto::sandbox_reference::Identifier::Id(id)) => id.clone(),
|
||||
_ => panic!("expected sandbox ID reference"),
|
||||
}
|
||||
}
|
||||
|
||||
// ── mock OpenShell server ─────────────────────────────────────────────
|
||||
|
||||
/// Records which sandbox name was requested via `get_sandbox`.
|
||||
@@ -117,7 +131,8 @@ impl OpenShell for TestOpenShell {
|
||||
&self,
|
||||
request: tonic::Request<GetSandboxRequest>,
|
||||
) -> Result<Response<SandboxResponse>, Status> {
|
||||
let name = request.into_inner().name;
|
||||
let request = request.into_inner();
|
||||
let name = sandbox_reference_name(request.sandbox_ref.as_ref());
|
||||
*self.state.last_get_name.lock().await = Some(name.clone());
|
||||
Ok(Response::new(SandboxResponse {
|
||||
sandbox: Some(Sandbox {
|
||||
@@ -179,7 +194,8 @@ impl OpenShell for TestOpenShell {
|
||||
) -> Result<Response<GetSandboxConfigResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
assert_eq!(
|
||||
req.sandbox_id, "test-id",
|
||||
sandbox_reference_id(req.sandbox_ref.as_ref()),
|
||||
"test-id",
|
||||
"GetSandboxConfig should pass the id from GetSandbox"
|
||||
);
|
||||
Ok(Response::new(GetSandboxConfigResponse {
|
||||
@@ -450,7 +466,10 @@ impl OpenShell for TestOpenShell {
|
||||
request: tonic::Request<GetSandboxPolicyStatusRequest>,
|
||||
) -> Result<Response<GetSandboxPolicyStatusResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
assert_eq!(req.name, "my-sandbox");
|
||||
assert_eq!(
|
||||
sandbox_reference_name(req.sandbox_ref.as_ref()),
|
||||
"my-sandbox"
|
||||
);
|
||||
assert_eq!(req.version, 3);
|
||||
assert!(!req.global);
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ use crate::proto::{
|
||||
NetworkActivitySummary, PolicyChunk, PolicySource, PolicyStatus, RefreshSandboxTokenRequest,
|
||||
ReportPolicyStatusRequest, SandboxPolicy as ProtoSandboxPolicy, SubmitPolicyAnalysisRequest,
|
||||
SubmitPolicyAnalysisResponse, UpdateConfigRequest, open_shell_client::OpenShellClient,
|
||||
workspace_selector,
|
||||
};
|
||||
use crate::sandbox_env;
|
||||
use miette::{IntoDiagnostic, Result, WrapErr};
|
||||
@@ -717,7 +716,7 @@ async fn fetch_settings_snapshot_with_client(
|
||||
) -> Result<SettingsPollResult> {
|
||||
let response = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_id: sandbox_id.to_string(),
|
||||
sandbox_ref: Some(crate::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -751,9 +750,8 @@ async fn sync_policy_with_client(
|
||||
) -> Result<()> {
|
||||
client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: sandbox.to_string(),
|
||||
sandbox_ref: Some(crate::proto::sandbox_reference_by_name(workspace, sandbox)),
|
||||
policy: Some(policy.clone()),
|
||||
workspace_scope: Some(workspace_selector(workspace)),
|
||||
..Default::default()
|
||||
})
|
||||
.await
|
||||
@@ -1053,7 +1051,7 @@ impl CachedOpenShellClient {
|
||||
.client
|
||||
.clone()
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_id: sandbox_id.to_string(),
|
||||
sandbox_ref: Some(crate::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
@@ -1179,9 +1177,11 @@ impl CachedOpenShellClient {
|
||||
.client
|
||||
.clone()
|
||||
.get_draft_policy(GetDraftPolicyRequest {
|
||||
name: sandbox_name.to_string(),
|
||||
status_filter: status_filter.to_string(),
|
||||
workspace_scope: Some(workspace_selector(self.workspace())),
|
||||
sandbox_ref: Some(crate::proto::sandbox_reference_by_name(
|
||||
self.workspace(),
|
||||
sandbox_name,
|
||||
)),
|
||||
})
|
||||
.await
|
||||
.into_diagnostic()?;
|
||||
|
||||
@@ -89,3 +89,22 @@ pub fn all_workspaces_selector() -> WorkspaceSelector {
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a canonical reference from a stable sandbox ID.
|
||||
pub fn sandbox_reference_by_id(id: impl Into<String>) -> SandboxReference {
|
||||
SandboxReference {
|
||||
identifier: Some(sandbox_reference::Identifier::Id(id.into())),
|
||||
workspace_scope: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Build a canonical reference from a workspace-scoped sandbox name.
|
||||
pub fn sandbox_reference_by_name(
|
||||
workspace: impl Into<String>,
|
||||
name: impl Into<String>,
|
||||
) -> SandboxReference {
|
||||
SandboxReference {
|
||||
identifier: Some(sandbox_reference::Identifier::Name(name.into())),
|
||||
workspace_scope: Some(workspace_selector(workspace)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -291,7 +291,7 @@ mod tests {
|
||||
|
||||
use openshell_core::proto::{
|
||||
CreateProviderRequest, CreateSandboxRequest, GpuResourceRequirements, Provider,
|
||||
SandboxSpec, UpdateConfigRequest, workspace_selector,
|
||||
SandboxSpec, UpdateConfigRequest, sandbox_reference_by_name, workspace_selector,
|
||||
};
|
||||
use prost::Message as _;
|
||||
use prost_types::{
|
||||
@@ -454,12 +454,11 @@ mod tests {
|
||||
let codec =
|
||||
ProtoJsonCodec::from_descriptor_set(openshell_core::FILE_DESCRIPTOR_SET).unwrap();
|
||||
let request = UpdateConfigRequest {
|
||||
name: "demo".to_string(),
|
||||
sandbox_ref: Some(sandbox_reference_by_name("default", "demo")),
|
||||
annotations: HashMap::from([(
|
||||
"openshell.nvidia.com/policy-signature".to_string(),
|
||||
"signed".to_string(),
|
||||
)]),
|
||||
workspace_scope: Some(workspace_selector("default")),
|
||||
..Default::default()
|
||||
};
|
||||
let bytes = request.encode_to_vec();
|
||||
|
||||
@@ -1009,7 +1009,9 @@ mod tests {
|
||||
codec: codec.clone(),
|
||||
};
|
||||
let request = UpdateConfigRequest {
|
||||
name: "demo".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default", "demo",
|
||||
)),
|
||||
expected_resource_version: u64::MAX - 1,
|
||||
annotations: HashMap::from([
|
||||
("policy-hash".to_string(), "sha256:v2:abc".to_string()),
|
||||
@@ -1017,7 +1019,6 @@ mod tests {
|
||||
("policy-signature-kid".to_string(), "kid".to_string()),
|
||||
("correlation-id".to_string(), "reload-1".to_string()),
|
||||
]),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector("default")),
|
||||
..UpdateConfigRequest::default()
|
||||
};
|
||||
let body = GrpcFrame {
|
||||
@@ -1186,7 +1187,10 @@ mod tests {
|
||||
let operation = ValidatedOperation::new(
|
||||
&codec,
|
||||
"openshell.v1.UpdateConfigRequest",
|
||||
json!({"name": "demo", "expectedResourceVersion": "7"}),
|
||||
json!({
|
||||
"sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}},
|
||||
"expectedResourceVersion": "7"
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let prior = operation.clone();
|
||||
@@ -1224,7 +1228,10 @@ mod tests {
|
||||
let operation = ValidatedOperation::new(
|
||||
&codec,
|
||||
"openshell.v1.UpdateConfigRequest",
|
||||
json!({"name": "demo", "expectedResourceVersion": "7"}),
|
||||
json!({
|
||||
"sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}},
|
||||
"expectedResourceVersion": "7"
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let plan = test_modify_plan(FailurePolicy::FailClosed);
|
||||
@@ -1262,12 +1269,15 @@ mod tests {
|
||||
let operation = ValidatedOperation::new(
|
||||
&codec,
|
||||
"openshell.v1.UpdateConfigRequest",
|
||||
json!({"name": "demo", "expectedResourceVersion": "7"}),
|
||||
json!({
|
||||
"sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}},
|
||||
"expectedResourceVersion": "7"
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let prior = operation.clone();
|
||||
let result = allowed_result(vec![
|
||||
patch("replace", "/name", json!("partially-mutated")),
|
||||
patch("replace", "/sandboxRef/name", json!("partially-mutated")),
|
||||
patch("replace", "/expectedResourceVersion", json!("not-a-number")),
|
||||
]);
|
||||
|
||||
@@ -1281,7 +1291,7 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(outcome, prior);
|
||||
assert_eq!(outcome.json["name"], "demo");
|
||||
assert_eq!(outcome.json["sandboxRef"]["name"], "demo");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1292,17 +1302,20 @@ mod tests {
|
||||
let operation = ValidatedOperation::new(
|
||||
&codec,
|
||||
"openshell.v1.UpdateConfigRequest",
|
||||
json!({"name": "demo", "expectedResourceVersion": "7"}),
|
||||
json!({
|
||||
"sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}},
|
||||
"expectedResourceVersion": "7"
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let plan = test_modify_plan(FailurePolicy::FailOpen);
|
||||
let invalid_first = allowed_result(vec![
|
||||
patch("replace", "/name", json!("rejected-candidate")),
|
||||
patch("replace", "/sandboxRef/name", json!("rejected-candidate")),
|
||||
patch("replace", "/expectedResourceVersion", json!("not-a-number")),
|
||||
]);
|
||||
let second = allowed_result(vec![
|
||||
patch("test", "/name", json!("demo")),
|
||||
patch("replace", "/name", json!("accepted-candidate")),
|
||||
patch("test", "/sandboxRef/name", json!("demo")),
|
||||
patch("replace", "/sandboxRef/name", json!("accepted-candidate")),
|
||||
]);
|
||||
|
||||
let operation = apply_evaluation_result(
|
||||
@@ -1322,9 +1335,14 @@ mod tests {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(operation.json["name"], "accepted-candidate");
|
||||
assert_eq!(operation.json["sandboxRef"]["name"], "accepted-candidate");
|
||||
let decoded = UpdateConfigRequest::decode(operation.encoded.as_slice()).unwrap();
|
||||
assert_eq!(decoded.name, "accepted-candidate");
|
||||
assert_eq!(
|
||||
decoded.sandbox_ref.unwrap().identifier,
|
||||
Some(openshell_core::proto::sandbox_reference::Identifier::Name(
|
||||
"accepted-candidate".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(decoded.expected_resource_version, 7);
|
||||
}
|
||||
|
||||
@@ -1371,10 +1389,17 @@ mod tests {
|
||||
let operation = ValidatedOperation::new(
|
||||
&codec,
|
||||
"openshell.v1.UpdateConfigRequest",
|
||||
json!({"name": "demo", "expectedResourceVersion": "7"}),
|
||||
json!({
|
||||
"sandboxRef": {"name": "demo", "workspaceScope": {"workspace": "default"}},
|
||||
"expectedResourceVersion": "7"
|
||||
}),
|
||||
)
|
||||
.unwrap();
|
||||
let result = allowed_result(vec![patch("replace", "/name", json!("accepted"))]);
|
||||
let result = allowed_result(vec![patch(
|
||||
"replace",
|
||||
"/sandboxRef/name",
|
||||
json!("accepted"),
|
||||
)]);
|
||||
let recorder = TestRecorder::default();
|
||||
|
||||
let operation = metrics::with_local_recorder(&recorder, || {
|
||||
@@ -1389,7 +1414,12 @@ mod tests {
|
||||
.unwrap();
|
||||
|
||||
let decoded = UpdateConfigRequest::decode(operation.encoded.as_slice()).unwrap();
|
||||
assert_eq!(decoded.name, "accepted");
|
||||
assert_eq!(
|
||||
decoded.sandbox_ref.unwrap().identifier,
|
||||
Some(openshell_core::proto::sandbox_reference::Identifier::Name(
|
||||
"accepted".to_string()
|
||||
))
|
||||
);
|
||||
assert_eq!(TestRecorder::count(&recorder.evaluations), 1);
|
||||
assert_eq!(TestRecorder::count(&recorder.patches), 1);
|
||||
assert_eq!(TestRecorder::count(&recorder.fail_open), 0);
|
||||
|
||||
@@ -248,8 +248,7 @@ impl OpenShellClient {
|
||||
let response = self
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector("default")),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)),
|
||||
};
|
||||
async move { grpc.get_sandbox(request).await }
|
||||
})
|
||||
@@ -287,8 +286,7 @@ impl OpenShellClient {
|
||||
let response = self
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::DeleteSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector("default")),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)),
|
||||
};
|
||||
async move { grpc.delete_sandbox(request).await }
|
||||
})
|
||||
@@ -301,8 +299,7 @@ impl OpenShellClient {
|
||||
let response = self
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::StopSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector("default")),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)),
|
||||
};
|
||||
async move { grpc.stop_sandbox(request).await }
|
||||
})
|
||||
@@ -315,8 +312,7 @@ impl OpenShellClient {
|
||||
let response = self
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::StartSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector("default")),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name("default", name)),
|
||||
};
|
||||
async move { grpc.start_sandbox(request).await }
|
||||
})
|
||||
@@ -478,7 +474,7 @@ impl OpenShellClient {
|
||||
pub async fn exec(&self, name: &str, cmd: &[String], opts: ExecOptions) -> Result<ExecResult> {
|
||||
let sandbox = self.get_sandbox(name).await?;
|
||||
let request = proto::ExecSandboxRequest {
|
||||
sandbox_id: sandbox.id,
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_id(sandbox.id)),
|
||||
command: cmd.to_vec(),
|
||||
workdir: opts.workdir.unwrap_or_default(),
|
||||
environment: opts.environment,
|
||||
@@ -736,8 +732,7 @@ impl WorkspaceScopedClient {
|
||||
.client
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::GetSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector(&self.workspace)),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)),
|
||||
};
|
||||
async move { grpc.get_sandbox(request).await }
|
||||
})
|
||||
@@ -772,8 +767,7 @@ impl WorkspaceScopedClient {
|
||||
.client
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::DeleteSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector(&self.workspace)),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)),
|
||||
};
|
||||
async move { grpc.delete_sandbox(request).await }
|
||||
})
|
||||
@@ -787,8 +781,7 @@ impl WorkspaceScopedClient {
|
||||
.client
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::StopSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector(&self.workspace)),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)),
|
||||
};
|
||||
async move { grpc.stop_sandbox(request).await }
|
||||
})
|
||||
@@ -802,8 +795,7 @@ impl WorkspaceScopedClient {
|
||||
.client
|
||||
.unary(|mut grpc| {
|
||||
let request = proto::StartSandboxRequest {
|
||||
name: name.to_string(),
|
||||
workspace_scope: Some(proto::workspace_selector(&self.workspace)),
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_name(&self.workspace, name)),
|
||||
};
|
||||
async move { grpc.start_sandbox(request).await }
|
||||
})
|
||||
@@ -868,7 +860,7 @@ impl WorkspaceScopedClient {
|
||||
pub async fn exec(&self, name: &str, cmd: &[String], opts: ExecOptions) -> Result<ExecResult> {
|
||||
let sandbox = self.get_sandbox(name).await?;
|
||||
let request = proto::ExecSandboxRequest {
|
||||
sandbox_id: sandbox.id,
|
||||
sandbox_ref: Some(proto::sandbox_reference_by_id(sandbox.id)),
|
||||
command: cmd.to_vec(),
|
||||
workdir: opts.workdir.unwrap_or_default(),
|
||||
environment: opts.environment,
|
||||
|
||||
@@ -39,6 +39,24 @@ fn selects_all_workspaces(scope: &Option<proto::datamodel::v1::WorkspaceSelector
|
||||
)
|
||||
}
|
||||
|
||||
fn sandbox_reference_name(reference: &Option<proto::SandboxReference>) -> Option<&str> {
|
||||
match reference.as_ref()?.identifier.as_ref()? {
|
||||
proto::sandbox_reference::Identifier::Name(name) => Some(name),
|
||||
proto::sandbox_reference::Identifier::Id(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_reference_id(reference: &Option<proto::SandboxReference>) -> Option<&str> {
|
||||
match reference.as_ref()?.identifier.as_ref()? {
|
||||
proto::sandbox_reference::Identifier::Id(id) => Some(id),
|
||||
proto::sandbox_reference::Identifier::Name(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sandbox_reference_workspace(reference: &Option<proto::SandboxReference>) -> Option<&str> {
|
||||
selected_workspace(&reference.as_ref()?.workspace_scope)
|
||||
}
|
||||
|
||||
/// Captured fixture state — what the mock observed and the canned replies it
|
||||
/// returned. One per test so assertions are scoped.
|
||||
#[derive(Default)]
|
||||
@@ -297,9 +315,9 @@ impl OpenShell for TestOpenShell {
|
||||
) -> Result<Response<proto::SandboxResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let sandbox = sandbox_with_phase_ws(
|
||||
&request.name,
|
||||
sandbox_reference_name(&request.sandbox_ref).unwrap_or_default(),
|
||||
proto::SandboxPhase::Stopped,
|
||||
selected_workspace(&request.workspace_scope).unwrap_or("default"),
|
||||
sandbox_reference_workspace(&request.sandbox_ref).unwrap_or("default"),
|
||||
);
|
||||
*self.state.last_stop.lock().await = Some(request);
|
||||
Ok(Response::new(proto::SandboxResponse {
|
||||
@@ -313,9 +331,9 @@ impl OpenShell for TestOpenShell {
|
||||
) -> Result<Response<proto::SandboxResponse>, Status> {
|
||||
let request = request.into_inner();
|
||||
let sandbox = sandbox_with_phase_ws(
|
||||
&request.name,
|
||||
sandbox_reference_name(&request.sandbox_ref).unwrap_or_default(),
|
||||
proto::SandboxPhase::Starting,
|
||||
selected_workspace(&request.workspace_scope).unwrap_or("default"),
|
||||
sandbox_reference_workspace(&request.sandbox_ref).unwrap_or("default"),
|
||||
);
|
||||
*self.state.last_start.lock().await = Some(request);
|
||||
Ok(Response::new(proto::SandboxResponse {
|
||||
@@ -328,10 +346,12 @@ impl OpenShell for TestOpenShell {
|
||||
request: tonic::Request<proto::GetSandboxRequest>,
|
||||
) -> Result<Response<proto::SandboxResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
let name = req.name;
|
||||
let name = sandbox_reference_name(&req.sandbox_ref)
|
||||
.unwrap_or_default()
|
||||
.to_string();
|
||||
*self.state.last_get_name.lock().await = Some(name.clone());
|
||||
*self.state.last_get_workspace.lock().await =
|
||||
selected_workspace(&req.workspace_scope).map(str::to_string);
|
||||
sandbox_reference_workspace(&req.sandbox_ref).map(str::to_string);
|
||||
let count = self.state.get_calls.fetch_add(1, Ordering::SeqCst);
|
||||
|
||||
if self.state.get_returns_not_found {
|
||||
@@ -399,9 +419,10 @@ impl OpenShell for TestOpenShell {
|
||||
request: tonic::Request<proto::DeleteSandboxRequest>,
|
||||
) -> Result<Response<proto::DeleteSandboxResponse>, Status> {
|
||||
let req = request.into_inner();
|
||||
*self.state.last_delete_name.lock().await = Some(req.name);
|
||||
*self.state.last_delete_name.lock().await =
|
||||
sandbox_reference_name(&req.sandbox_ref).map(str::to_string);
|
||||
*self.state.last_delete_workspace.lock().await =
|
||||
selected_workspace(&req.workspace_scope).map(str::to_string);
|
||||
sandbox_reference_workspace(&req.sandbox_ref).map(str::to_string);
|
||||
Ok(Response::new(proto::DeleteSandboxResponse {
|
||||
deleted: true,
|
||||
}))
|
||||
@@ -1104,8 +1125,11 @@ async fn stop_and_start_map_requests_and_phases() {
|
||||
let stopped = client.stop_sandbox("sleepy").await.unwrap();
|
||||
assert_eq!(stopped.phase, SandboxPhase::Stopped);
|
||||
let stop = state.last_stop.lock().await.clone().unwrap();
|
||||
assert_eq!(stop.name, "sleepy");
|
||||
assert_eq!(selected_workspace(&stop.workspace_scope), Some("default"));
|
||||
assert_eq!(sandbox_reference_name(&stop.sandbox_ref), Some("sleepy"));
|
||||
assert_eq!(
|
||||
sandbox_reference_workspace(&stop.sandbox_ref),
|
||||
Some("default")
|
||||
);
|
||||
|
||||
let started = client
|
||||
.workspace("team-a")
|
||||
@@ -1114,8 +1138,11 @@ async fn stop_and_start_map_requests_and_phases() {
|
||||
.unwrap();
|
||||
assert_eq!(started.phase, SandboxPhase::Starting);
|
||||
let start = state.last_start.lock().await.clone().unwrap();
|
||||
assert_eq!(start.name, "sleepy");
|
||||
assert_eq!(selected_workspace(&start.workspace_scope), Some("team-a"));
|
||||
assert_eq!(sandbox_reference_name(&start.sandbox_ref), Some("sleepy"));
|
||||
assert_eq!(
|
||||
sandbox_reference_workspace(&start.sandbox_ref),
|
||||
Some("team-a")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
@@ -1247,7 +1274,10 @@ async fn exec_buffers_stdout_stderr_and_exit() {
|
||||
assert_eq!(result.stderr, b"warn\n");
|
||||
|
||||
let observed = state.last_exec_request.lock().await.clone().unwrap();
|
||||
assert_eq!(observed.sandbox_id, "id-my-box");
|
||||
assert_eq!(
|
||||
sandbox_reference_id(&observed.sandbox_ref),
|
||||
Some("id-my-box")
|
||||
);
|
||||
assert_eq!(
|
||||
observed.command,
|
||||
vec!["echo".to_string(), "hello".to_string()]
|
||||
|
||||
@@ -164,7 +164,9 @@ pub async fn handle_refresh_sandbox_token(
|
||||
));
|
||||
} else {
|
||||
let mut config_request = Request::new(GetSandboxConfigRequest {
|
||||
sandbox_id: sandbox.sandbox_id.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
&sandbox.sandbox_id,
|
||||
)),
|
||||
});
|
||||
config_request
|
||||
.extensions_mut()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6566,12 +6566,12 @@ mod tests {
|
||||
let attached = super::super::sandbox::handle_attach_sandbox_provider(
|
||||
&state,
|
||||
authed_request(AttachSandboxProviderRequest {
|
||||
sandbox_name: "sandbox-custom".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"sandbox-custom".to_string(),
|
||||
)),
|
||||
provider_name: "custom-provider".to_string(),
|
||||
expected_resource_version: 0,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,9 +7,9 @@ use std::sync::Arc;
|
||||
use openshell_core::proto::datamodel::v1::ObjectMeta;
|
||||
use openshell_core::proto::{
|
||||
DeleteServiceRequest, DeleteServiceResponse, ExposeServiceRequest, GetServiceRequest,
|
||||
ListServicesRequest, ListServicesResponse, Sandbox, ServiceEndpoint, ServiceEndpointResponse,
|
||||
ListServicesRequest, ListServicesResponse, ServiceEndpoint, ServiceEndpointResponse,
|
||||
};
|
||||
use openshell_core::{ObjectId, ObjectWorkspace};
|
||||
use openshell_core::{ObjectId, ObjectName, ObjectWorkspace};
|
||||
use prost::Message as _;
|
||||
use tonic::{Request, Response, Status};
|
||||
use uuid::Uuid;
|
||||
@@ -17,13 +17,11 @@ use uuid::Uuid;
|
||||
use crate::ServerState;
|
||||
use crate::auth::workspace_authz::{
|
||||
AuthorizedWorkspaceScope, MinWorkspaceRole, authorize_list_workspace_selector,
|
||||
authorize_workspace_selector,
|
||||
};
|
||||
use crate::persistence::{ObjectType, WriteCondition};
|
||||
use crate::service_routing;
|
||||
|
||||
const MAX_SERVICE_NAME_LEN: usize = super::MAX_ROUTABLE_NAME_LEN;
|
||||
const MAX_SANDBOX_NAME_LEN: usize = super::MAX_ROUTABLE_NAME_LEN;
|
||||
|
||||
pub(super) async fn handle_expose_service(
|
||||
state: &Arc<ServerState>,
|
||||
@@ -31,32 +29,25 @@ pub(super) async fn handle_expose_service(
|
||||
) -> Result<Response<ServiceEndpointResponse>, Status> {
|
||||
let principal = super::extract_principal(&request)?;
|
||||
let req = request.into_inner();
|
||||
let authz = authorize_workspace_selector(
|
||||
&state.store,
|
||||
&state.admin_role,
|
||||
let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference(
|
||||
state,
|
||||
&principal,
|
||||
req.workspace_scope.as_ref(),
|
||||
req.sandbox_ref.as_ref(),
|
||||
MinWorkspaceRole::User,
|
||||
)
|
||||
.await?;
|
||||
let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace)
|
||||
.await?
|
||||
.ensure_active()?;
|
||||
validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?;
|
||||
let workspace =
|
||||
super::workspace::resolve_workspace(state.store.as_ref(), sandbox.object_workspace())
|
||||
.await?
|
||||
.ensure_active()?;
|
||||
let sandbox_name = sandbox.object_name();
|
||||
validate_optional_endpoint_name("service", &req.service, MAX_SERVICE_NAME_LEN)?;
|
||||
if req.target_port == 0 || req.target_port > u32::from(u16::MAX) {
|
||||
return Err(Status::invalid_argument("target_port must be in 1..=65535"));
|
||||
}
|
||||
|
||||
let sandbox = state
|
||||
.store
|
||||
.get_message_by_name::<Sandbox>(&workspace, &req.sandbox)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("fetch sandbox failed: {e}")))?
|
||||
.ok_or_else(|| Status::not_found("sandbox not found"))?;
|
||||
|
||||
let now = crate::persistence::current_time_ms();
|
||||
let key = service_routing::endpoint_key(&req.sandbox, &req.service);
|
||||
let key = service_routing::endpoint_key(sandbox_name, &req.service);
|
||||
|
||||
// Fetch existing endpoint to determine create vs. update path
|
||||
let existing = state
|
||||
@@ -92,7 +83,7 @@ pub(super) async fn handle_expose_service(
|
||||
|
||||
let labels_json = serde_json::to_string(&HashMap::from([(
|
||||
"sandbox".to_string(),
|
||||
req.sandbox.clone(),
|
||||
sandbox_name.to_string(),
|
||||
)]))
|
||||
.map_err(|e| Status::internal(format!("serialize labels failed: {e}")))?;
|
||||
|
||||
@@ -101,14 +92,14 @@ pub(super) async fn handle_expose_service(
|
||||
id: id.clone(),
|
||||
name: key.clone(),
|
||||
created_at_ms,
|
||||
labels: HashMap::from([("sandbox".to_string(), req.sandbox.clone())]),
|
||||
labels: HashMap::from([("sandbox".to_string(), sandbox_name.to_string())]),
|
||||
resource_version: 0,
|
||||
annotations: HashMap::new(),
|
||||
workspace: workspace.clone(),
|
||||
deletion_timestamp_ms: 0,
|
||||
}),
|
||||
sandbox_id: sandbox.object_id().to_string(),
|
||||
sandbox_name: req.sandbox.clone(),
|
||||
sandbox_name: sandbox_name.to_string(),
|
||||
service_name: req.service.clone(),
|
||||
target_port: req.target_port,
|
||||
domain: true,
|
||||
@@ -134,7 +125,7 @@ pub(super) async fn handle_expose_service(
|
||||
meta.resource_version = result.resource_version;
|
||||
}
|
||||
|
||||
let url = service_routing::endpoint_url(&state.config, &workspace, &req.sandbox, &req.service)
|
||||
let url = service_routing::endpoint_url(&state.config, &workspace, sandbox_name, &req.service)
|
||||
.unwrap_or_default();
|
||||
service_routing::emit_service_endpoint_config_event(&endpoint, &url, created);
|
||||
|
||||
@@ -150,21 +141,18 @@ pub(super) async fn handle_get_service(
|
||||
) -> Result<Response<ServiceEndpointResponse>, Status> {
|
||||
let principal = super::extract_principal(&request)?;
|
||||
let req = request.into_inner();
|
||||
let authz = authorize_workspace_selector(
|
||||
&state.store,
|
||||
&state.admin_role,
|
||||
let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference(
|
||||
state,
|
||||
&principal,
|
||||
req.workspace_scope.as_ref(),
|
||||
req.sandbox_ref.as_ref(),
|
||||
MinWorkspaceRole::User,
|
||||
)
|
||||
.await?;
|
||||
let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace)
|
||||
.await?
|
||||
.name;
|
||||
validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?;
|
||||
let workspace = sandbox.object_workspace();
|
||||
let sandbox_name = sandbox.object_name();
|
||||
validate_optional_endpoint_name("service", &req.service, MAX_SERVICE_NAME_LEN)?;
|
||||
|
||||
let endpoint = get_service_endpoint(state, &workspace, &req.sandbox, &req.service)
|
||||
let endpoint = get_service_endpoint(state, workspace, sandbox_name, &req.service)
|
||||
.await?
|
||||
.ok_or_else(|| Status::not_found("service endpoint not found"))?;
|
||||
|
||||
@@ -177,11 +165,36 @@ pub(super) async fn handle_list_services(
|
||||
) -> Result<Response<ListServicesResponse>, Status> {
|
||||
let principal = super::extract_principal(&request)?;
|
||||
let req = request.into_inner();
|
||||
if !req.sandbox.is_empty() {
|
||||
validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?;
|
||||
}
|
||||
|
||||
let limit = super::clamp_limit(req.limit, 100, super::MAX_PAGE_SIZE);
|
||||
if req.sandbox_ref.is_some() {
|
||||
if req.workspace_scope.is_some() {
|
||||
return Err(Status::invalid_argument(
|
||||
"workspace_scope must be omitted when sandbox_ref is set",
|
||||
));
|
||||
}
|
||||
let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference(
|
||||
state,
|
||||
&principal,
|
||||
req.sandbox_ref.as_ref(),
|
||||
MinWorkspaceRole::User,
|
||||
)
|
||||
.await?;
|
||||
let endpoints = state
|
||||
.store
|
||||
.list_messages_with_selector::<ServiceEndpoint>(
|
||||
sandbox.object_workspace(),
|
||||
&format!("sandbox={}", sandbox.object_name()),
|
||||
limit,
|
||||
req.offset,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("list endpoints failed: {e}")))?;
|
||||
let services = endpoints
|
||||
.into_iter()
|
||||
.map(|endpoint| service_endpoint_response(state, endpoint))
|
||||
.collect();
|
||||
return Ok(Response::new(ListServicesResponse { services }));
|
||||
}
|
||||
let scope = authorize_list_workspace_selector(
|
||||
&state.store,
|
||||
&state.admin_role,
|
||||
@@ -192,11 +205,6 @@ pub(super) async fn handle_list_services(
|
||||
.await?;
|
||||
let endpoints: Vec<ServiceEndpoint> =
|
||||
if matches!(scope, AuthorizedWorkspaceScope::AllWorkspaces) {
|
||||
if !req.sandbox.is_empty() {
|
||||
return Err(Status::invalid_argument(
|
||||
"sandbox filter is not supported with all_workspaces",
|
||||
));
|
||||
}
|
||||
state.store.list_all_messages(limit, req.offset).await
|
||||
} else {
|
||||
let AuthorizedWorkspaceScope::Workspace(authz) = scope else {
|
||||
@@ -206,22 +214,10 @@ pub(super) async fn handle_list_services(
|
||||
super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace)
|
||||
.await?
|
||||
.name;
|
||||
if req.sandbox.is_empty() {
|
||||
state
|
||||
.store
|
||||
.list_messages(&workspace, limit, req.offset)
|
||||
.await
|
||||
} else {
|
||||
state
|
||||
.store
|
||||
.list_messages_with_selector(
|
||||
&workspace,
|
||||
&format!("sandbox={}", req.sandbox),
|
||||
limit,
|
||||
req.offset,
|
||||
)
|
||||
.await
|
||||
}
|
||||
state
|
||||
.store
|
||||
.list_messages(&workspace, limit, req.offset)
|
||||
.await
|
||||
}
|
||||
.map_err(|e| Status::internal(format!("list endpoints failed: {e}")))?;
|
||||
|
||||
@@ -239,29 +235,26 @@ pub(super) async fn handle_delete_service(
|
||||
) -> Result<Response<DeleteServiceResponse>, Status> {
|
||||
let principal = super::extract_principal(&request)?;
|
||||
let req = request.into_inner();
|
||||
let authz = authorize_workspace_selector(
|
||||
&state.store,
|
||||
&state.admin_role,
|
||||
let sandbox = super::sandbox::resolve_and_authorize_sandbox_reference(
|
||||
state,
|
||||
&principal,
|
||||
req.workspace_scope.as_ref(),
|
||||
req.sandbox_ref.as_ref(),
|
||||
MinWorkspaceRole::User,
|
||||
)
|
||||
.await?;
|
||||
let workspace = super::workspace::resolve_workspace(state.store.as_ref(), &authz.workspace)
|
||||
.await?
|
||||
.name;
|
||||
validate_endpoint_name("sandbox", &req.sandbox, MAX_SANDBOX_NAME_LEN)?;
|
||||
let workspace = sandbox.object_workspace();
|
||||
let sandbox_name = sandbox.object_name();
|
||||
validate_optional_endpoint_name("service", &req.service, MAX_SERVICE_NAME_LEN)?;
|
||||
|
||||
let endpoint = get_service_endpoint(state, &workspace, &req.sandbox, &req.service).await?;
|
||||
let endpoint = get_service_endpoint(state, workspace, sandbox_name, &req.service).await?;
|
||||
let Some(endpoint) = endpoint else {
|
||||
return Ok(Response::new(DeleteServiceResponse { deleted: false }));
|
||||
};
|
||||
|
||||
let key = service_routing::endpoint_key(&req.sandbox, &req.service);
|
||||
let key = service_routing::endpoint_key(sandbox_name, &req.service);
|
||||
let deleted = state
|
||||
.store
|
||||
.delete_by_name(ServiceEndpoint::object_type(), &workspace, &key)
|
||||
.delete_by_name(ServiceEndpoint::object_type(), workspace, &key)
|
||||
.await
|
||||
.map_err(|e| Status::internal(format!("delete endpoint failed: {e}")))?;
|
||||
|
||||
@@ -305,6 +298,7 @@ fn service_endpoint_response(
|
||||
}
|
||||
|
||||
#[allow(clippy::result_large_err)]
|
||||
#[cfg(test)]
|
||||
fn validate_endpoint_name(field: &str, value: &str, max_len: usize) -> Result<(), Status> {
|
||||
if value.is_empty() {
|
||||
return Err(Status::invalid_argument(format!("{field} is required")));
|
||||
@@ -357,7 +351,7 @@ fn is_dns_label(value: &str) -> bool {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::grpc::test_support::{authed_request, test_server_state};
|
||||
use openshell_core::proto::SandboxPhase;
|
||||
use openshell_core::proto::{Sandbox, SandboxPhase};
|
||||
|
||||
async fn seed_sandbox(state: &Arc<ServerState>, name: &str) {
|
||||
let mut sandbox = Sandbox {
|
||||
@@ -411,13 +405,13 @@ mod tests {
|
||||
let exposed = handle_expose_service(
|
||||
&state,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 8080,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -428,12 +422,13 @@ mod tests {
|
||||
let listed = handle_list_services(
|
||||
&state,
|
||||
authed_request(ListServicesRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
workspace_scope: None,
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -448,11 +443,11 @@ mod tests {
|
||||
let fetched = handle_get_service(
|
||||
&state,
|
||||
authed_request(GetServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -463,11 +458,11 @@ mod tests {
|
||||
let deleted = handle_delete_service(
|
||||
&state,
|
||||
authed_request(DeleteServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -478,11 +473,11 @@ mod tests {
|
||||
let err = handle_get_service(
|
||||
&state,
|
||||
authed_request(GetServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -492,12 +487,13 @@ mod tests {
|
||||
let listed = handle_list_services(
|
||||
&state,
|
||||
authed_request(ListServicesRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
workspace_scope: None,
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -517,13 +513,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state1,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 8080,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -534,13 +530,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state2,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 9090,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -562,12 +558,13 @@ mod tests {
|
||||
let listed = handle_list_services(
|
||||
&state,
|
||||
authed_request(ListServicesRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
workspace_scope: None,
|
||||
limit: 0,
|
||||
offset: 0,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -585,13 +582,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 7070,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -603,13 +600,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state1,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 8080,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -620,13 +617,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state2,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 9090,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -647,11 +644,11 @@ mod tests {
|
||||
let fetched = handle_get_service(
|
||||
&state,
|
||||
authed_request(GetServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -708,13 +705,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 8080,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -723,13 +720,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"beta".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
target_port: 9090,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"beta".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -739,11 +736,11 @@ mod tests {
|
||||
let got = handle_get_service(
|
||||
&state,
|
||||
authed_request(GetServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -755,11 +752,11 @@ mod tests {
|
||||
let got = handle_get_service(
|
||||
&state,
|
||||
authed_request(GetServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"beta".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -771,12 +768,13 @@ mod tests {
|
||||
let listed = handle_list_services(
|
||||
&state,
|
||||
authed_request(ListServicesRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
workspace_scope: None,
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -791,12 +789,13 @@ mod tests {
|
||||
let listed = handle_list_services(
|
||||
&state,
|
||||
authed_request(ListServicesRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"beta".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
workspace_scope: None,
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"beta".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -812,11 +811,11 @@ mod tests {
|
||||
let deleted = handle_delete_service(
|
||||
&state,
|
||||
authed_request(DeleteServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -827,12 +826,13 @@ mod tests {
|
||||
let listed = handle_list_services(
|
||||
&state,
|
||||
authed_request(ListServicesRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
workspace_scope: None,
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -843,11 +843,11 @@ mod tests {
|
||||
let got = handle_get_service(
|
||||
&state,
|
||||
authed_request(GetServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
service: "web".to_string(),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"beta".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "web".to_string(),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -860,13 +860,13 @@ mod tests {
|
||||
handle_expose_service(
|
||||
&state,
|
||||
authed_request(ExposeServiceRequest {
|
||||
sandbox: "my-sandbox".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default".to_string(),
|
||||
"my-sandbox".to_string(),
|
||||
)),
|
||||
service: "api".to_string(),
|
||||
target_port: 3000,
|
||||
domain: true,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector(
|
||||
"default".to_string(),
|
||||
)),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -875,10 +875,10 @@ mod tests {
|
||||
let listed = handle_list_services(
|
||||
&state,
|
||||
authed_request(ListServicesRequest {
|
||||
sandbox: String::new(),
|
||||
sandbox_ref: None,
|
||||
workspace_scope: Some(openshell_core::proto::all_workspaces_selector()),
|
||||
limit: 100,
|
||||
offset: 0,
|
||||
workspace_scope: Some(openshell_core::proto::all_workspaces_selector()),
|
||||
}),
|
||||
)
|
||||
.await
|
||||
@@ -891,7 +891,7 @@ mod tests {
|
||||
/// when targeting a workspace that does not exist. Returning `NOT_FOUND`
|
||||
/// would create a CWE-203 workspace-name oracle.
|
||||
#[tokio::test]
|
||||
async fn non_member_gets_permission_denied_not_workspace_oracle() {
|
||||
async fn non_member_gets_gets_not_found_without_sandbox_oracle() {
|
||||
use crate::auth::identity::{Identity, IdentityProvider};
|
||||
use crate::auth::principal::{Principal, UserPrincipal};
|
||||
|
||||
@@ -915,7 +915,10 @@ mod tests {
|
||||
let err = handle_expose_service(
|
||||
&state,
|
||||
non_member_request(ExposeServiceRequest {
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"no-such-ws",
|
||||
"any",
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
@@ -923,15 +926,18 @@ mod tests {
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.code(),
|
||||
tonic::Code::PermissionDenied,
|
||||
"handle_expose_service should return PermissionDenied, got {:?}",
|
||||
tonic::Code::NotFound,
|
||||
"handle_expose_service should return NotFound, got {:?}",
|
||||
err.code()
|
||||
);
|
||||
|
||||
let err = handle_get_service(
|
||||
&state,
|
||||
non_member_request(GetServiceRequest {
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"no-such-ws",
|
||||
"any",
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
@@ -939,15 +945,19 @@ mod tests {
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.code(),
|
||||
tonic::Code::PermissionDenied,
|
||||
"handle_get_service should return PermissionDenied, got {:?}",
|
||||
tonic::Code::NotFound,
|
||||
"handle_get_service should return NotFound, got {:?}",
|
||||
err.code()
|
||||
);
|
||||
|
||||
let err = handle_list_services(
|
||||
&state,
|
||||
non_member_request(ListServicesRequest {
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"no-such-ws",
|
||||
"any",
|
||||
)),
|
||||
workspace_scope: None,
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
@@ -955,15 +965,18 @@ mod tests {
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.code(),
|
||||
tonic::Code::PermissionDenied,
|
||||
"handle_list_services should return PermissionDenied, got {:?}",
|
||||
tonic::Code::NotFound,
|
||||
"handle_list_services should return NotFound, got {:?}",
|
||||
err.code()
|
||||
);
|
||||
|
||||
let err = handle_delete_service(
|
||||
&state,
|
||||
non_member_request(DeleteServiceRequest {
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector("no-such-ws")),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"no-such-ws",
|
||||
"any",
|
||||
)),
|
||||
..Default::default()
|
||||
}),
|
||||
)
|
||||
@@ -971,8 +984,8 @@ mod tests {
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err.code(),
|
||||
tonic::Code::PermissionDenied,
|
||||
"handle_delete_service should return PermissionDenied, got {:?}",
|
||||
tonic::Code::NotFound,
|
||||
"handle_delete_service should return NotFound, got {:?}",
|
||||
err.code()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,9 @@ pub(super) const MAX_MAIN_PROCESS_ARGV_SIZE: usize = 256 * 1024;
|
||||
/// Command arguments only reject NUL (newlines are valid for inline scripts).
|
||||
/// Environment values and workdir reject both NUL and newlines.
|
||||
pub(super) fn validate_exec_request_fields(req: &ExecSandboxRequest) -> Result<(), Status> {
|
||||
if req.sandbox_ref.is_none() {
|
||||
return Err(Status::invalid_argument("sandbox_ref is required"));
|
||||
}
|
||||
if req.command.len() > MAX_EXEC_COMMAND_ARGS {
|
||||
return Err(Status::invalid_argument(format!(
|
||||
"command array exceeds {MAX_EXEC_COMMAND_ARGS} argument limit"
|
||||
@@ -1351,7 +1354,9 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_exec_request_rejects_reserved_env_key() {
|
||||
let req = ExecSandboxRequest {
|
||||
sandbox_id: "id".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
"id".to_string(),
|
||||
)),
|
||||
command: vec!["echo".to_string()],
|
||||
environment: std::iter::once(("OPENSHELL_SANDBOX_ID".to_string(), "evil".to_string()))
|
||||
.collect(),
|
||||
@@ -1368,7 +1373,9 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_exec_request_allows_pyfunc_helper_key() {
|
||||
let req = ExecSandboxRequest {
|
||||
sandbox_id: "id".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
"id".to_string(),
|
||||
)),
|
||||
command: vec!["python".to_string()],
|
||||
environment: std::iter::once(("OPENSHELL_PYFUNC_B64".to_string(), "data".to_string()))
|
||||
.collect(),
|
||||
@@ -2246,7 +2253,9 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_exec_allows_newlines_in_command_args() {
|
||||
let req = ExecSandboxRequest {
|
||||
sandbox_id: "test".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
"test".to_string(),
|
||||
)),
|
||||
command: vec![
|
||||
"python3".to_string(),
|
||||
"-c".to_string(),
|
||||
@@ -2260,7 +2269,9 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_exec_still_rejects_null_bytes_in_command_args() {
|
||||
let req = ExecSandboxRequest {
|
||||
sandbox_id: "test".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
"test".to_string(),
|
||||
)),
|
||||
command: vec!["echo".to_string(), "hello\x00world".to_string()],
|
||||
..Default::default()
|
||||
};
|
||||
@@ -2271,7 +2282,9 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_exec_still_rejects_newlines_in_workdir() {
|
||||
let req = ExecSandboxRequest {
|
||||
sandbox_id: "test".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
"test".to_string(),
|
||||
)),
|
||||
command: vec!["ls".to_string()],
|
||||
workdir: "/tmp\nmalicious".to_string(),
|
||||
..Default::default()
|
||||
@@ -2283,7 +2296,9 @@ mod tests {
|
||||
#[test]
|
||||
fn validate_exec_still_rejects_newlines_in_env_values() {
|
||||
let req = ExecSandboxRequest {
|
||||
sandbox_id: "test".to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
"test".to_string(),
|
||||
)),
|
||||
command: vec!["ls".to_string()],
|
||||
environment: std::iter::once(("VAR".to_string(), "val\nmalicious".to_string()))
|
||||
.collect(),
|
||||
|
||||
@@ -76,7 +76,7 @@ async fn run_get_sandbox_config(args: &[String]) -> Result<i32> {
|
||||
let mut client = open_client().await?;
|
||||
let resp = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_id: sandbox_id.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await;
|
||||
match resp {
|
||||
|
||||
@@ -653,17 +653,16 @@ fn spawn_log_stream(app: &mut App, tx: mpsc::UnboundedSender<Event>) {
|
||||
};
|
||||
|
||||
let mut client = app.client.clone();
|
||||
let workspace = app.selected_sandbox_workspace();
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
// Phase 1: Fetch initial history via unary RPC.
|
||||
let req = openshell_core::proto::GetSandboxLogsRequest {
|
||||
sandbox_id: sandbox_id.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox_id.clone(),
|
||||
)),
|
||||
lines: 500,
|
||||
since_ms: 0,
|
||||
sources: vec![],
|
||||
min_level: String::new(),
|
||||
workspace_scope: Some(named_workspace_scope(workspace)),
|
||||
};
|
||||
|
||||
match tokio::time::timeout(Duration::from_secs(5), client.get_sandbox_logs(req)).await {
|
||||
@@ -700,7 +699,7 @@ fn spawn_log_stream(app: &mut App, tx: mpsc::UnboundedSender<Event>) {
|
||||
|
||||
// Phase 2: Stream live logs via WatchSandbox.
|
||||
let req = openshell_core::proto::WatchSandboxRequest {
|
||||
id: sandbox_id,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
follow_status: false,
|
||||
follow_logs: true,
|
||||
follow_events: false,
|
||||
@@ -765,8 +764,10 @@ async fn handle_sandbox_delete(app: &mut App) {
|
||||
}
|
||||
|
||||
let req = openshell_core::proto::DeleteSandboxRequest {
|
||||
name: sandbox_name,
|
||||
workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
app.selected_sandbox_workspace(),
|
||||
sandbox_name,
|
||||
)),
|
||||
};
|
||||
match app.client.delete_sandbox(req).await {
|
||||
Ok(_) => {
|
||||
@@ -798,8 +799,10 @@ async fn fetch_sandbox_detail(app: &mut App) {
|
||||
};
|
||||
|
||||
let req = openshell_core::proto::GetSandboxRequest {
|
||||
name: sandbox_name.clone(),
|
||||
workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
app.selected_sandbox_workspace(),
|
||||
sandbox_name.clone(),
|
||||
)),
|
||||
};
|
||||
|
||||
// Step 1: Fetch sandbox metadata (providers, sandbox ID).
|
||||
@@ -828,7 +831,9 @@ async fn fetch_sandbox_detail(app: &mut App) {
|
||||
|
||||
// Step 2: Fetch the current live policy (includes updates since creation).
|
||||
if let Some(id) = sandbox_id {
|
||||
let policy_req = openshell_core::proto::GetSandboxConfigRequest { sandbox_id: id };
|
||||
let policy_req = openshell_core::proto::GetSandboxConfigRequest {
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(id)),
|
||||
};
|
||||
|
||||
match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
@@ -884,8 +889,10 @@ async fn handle_shell_connect(
|
||||
// Step 1: Get sandbox ID.
|
||||
let sandbox_id = {
|
||||
let req = openshell_core::proto::GetSandboxRequest {
|
||||
name: sandbox_name.clone(),
|
||||
workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
app.selected_sandbox_workspace(),
|
||||
sandbox_name.clone(),
|
||||
)),
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(5), app.client.get_sandbox(req)).await {
|
||||
Ok(Ok(resp)) => {
|
||||
@@ -910,7 +917,9 @@ async fn handle_shell_connect(
|
||||
// Step 2: Create SSH session.
|
||||
let session = {
|
||||
let req = openshell_core::proto::CreateSshSessionRequest {
|
||||
sandbox_id: sandbox_id.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox_id.clone(),
|
||||
)),
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(5), app.client.create_ssh_session(req)).await
|
||||
{
|
||||
@@ -1041,8 +1050,10 @@ async fn handle_exec_command(
|
||||
// Step 1: Resolve sandbox → SSH session (same as handle_shell_connect).
|
||||
let sandbox_id = {
|
||||
let req = openshell_core::proto::GetSandboxRequest {
|
||||
name: sandbox_name.to_string(),
|
||||
workspace_scope: Some(named_workspace_scope(workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace,
|
||||
sandbox_name,
|
||||
)),
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(5), app.client.get_sandbox(req)).await {
|
||||
Ok(Ok(resp)) => {
|
||||
@@ -1066,7 +1077,9 @@ async fn handle_exec_command(
|
||||
|
||||
let session = {
|
||||
let req = openshell_core::proto::CreateSshSessionRequest {
|
||||
sandbox_id: sandbox_id.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox_id.clone(),
|
||||
)),
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(5), app.client.create_ssh_session(req)).await
|
||||
{
|
||||
@@ -1460,8 +1473,10 @@ fn spawn_create_sandbox(app: &mut App, tx: mpsc::UnboundedSender<Event>) {
|
||||
tokio::time::sleep(Duration::from_secs(2)).await;
|
||||
|
||||
let req = openshell_core::proto::GetSandboxRequest {
|
||||
name: sandbox_name.clone(),
|
||||
workspace_scope: Some(named_workspace_scope(&workspace)),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
&workspace,
|
||||
sandbox_name.clone(),
|
||||
)),
|
||||
};
|
||||
// Retry on transient errors.
|
||||
if let Ok(resp) = client.get_sandbox(req).await
|
||||
@@ -1519,7 +1534,7 @@ async fn start_port_forwards(
|
||||
// Create SSH session.
|
||||
let session = {
|
||||
let req = openshell_core::proto::CreateSshSessionRequest {
|
||||
sandbox_id: sandbox_id.to_string(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(10), client.create_ssh_session(req)).await {
|
||||
Ok(Ok(resp)) => resp.into_inner(),
|
||||
@@ -1894,9 +1909,10 @@ fn spawn_draft_approve(app: &App, tx: mpsc::UnboundedSender<Event>) {
|
||||
|
||||
tokio::spawn(async move {
|
||||
let req = openshell_core::proto::ApproveDraftChunkRequest {
|
||||
name,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
chunk_id,
|
||||
workspace_scope: Some(named_workspace_scope(workspace)),
|
||||
review_token,
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(5), client.approve_draft_chunk(req)).await {
|
||||
@@ -1939,10 +1955,11 @@ fn spawn_draft_reject(app: &App, tx: mpsc::UnboundedSender<Event>) {
|
||||
|
||||
tokio::spawn(async move {
|
||||
let req = openshell_core::proto::RejectDraftChunkRequest {
|
||||
name,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
chunk_id,
|
||||
reason: String::new(),
|
||||
workspace_scope: Some(named_workspace_scope(workspace)),
|
||||
};
|
||||
match tokio::time::timeout(Duration::from_secs(5), client.reject_draft_chunk(req)).await {
|
||||
Ok(Ok(_)) => {
|
||||
@@ -1989,9 +2006,10 @@ fn spawn_draft_approve_all(
|
||||
})
|
||||
.collect();
|
||||
let req = openshell_core::proto::ApproveAllDraftChunksRequest {
|
||||
name,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
include_security_flagged: false,
|
||||
workspace_scope: Some(named_workspace_scope(workspace)),
|
||||
approvals,
|
||||
};
|
||||
match tokio::time::timeout(
|
||||
@@ -2262,11 +2280,10 @@ async fn refresh_global_settings(app: &mut App) {
|
||||
|
||||
// Check for an active global policy only while the caller can read it.
|
||||
let policy_req = openshell_core::proto::ListSandboxPoliciesRequest {
|
||||
name: String::new(),
|
||||
sandbox_ref: None,
|
||||
limit: 1,
|
||||
offset: 0,
|
||||
global: true,
|
||||
workspace_scope: None,
|
||||
};
|
||||
match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
@@ -2342,7 +2359,6 @@ fn spawn_set_global_setting(app: &App, tx: mpsc::UnboundedSender<Event>) {
|
||||
};
|
||||
|
||||
let req = UpdateConfigRequest {
|
||||
name: String::new(),
|
||||
setting_key: key,
|
||||
setting_value: Some(SettingValue { value: Some(value) }),
|
||||
global: true,
|
||||
@@ -2375,7 +2391,6 @@ fn spawn_delete_global_setting(app: &App, tx: mpsc::UnboundedSender<Event>) {
|
||||
use openshell_core::proto::UpdateConfigRequest;
|
||||
|
||||
let req = UpdateConfigRequest {
|
||||
name: String::new(),
|
||||
setting_key: key,
|
||||
delete_setting: true,
|
||||
global: true,
|
||||
@@ -2443,10 +2458,11 @@ fn spawn_set_sandbox_setting(app: &App, tx: mpsc::UnboundedSender<Event>) {
|
||||
};
|
||||
|
||||
let req = UpdateConfigRequest {
|
||||
name,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
setting_key: key,
|
||||
setting_value: Some(SettingValue { value: Some(value) }),
|
||||
workspace_scope: Some(named_workspace_scope(workspace)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -2481,10 +2497,11 @@ fn spawn_delete_sandbox_setting(app: &App, tx: mpsc::UnboundedSender<Event>) {
|
||||
use openshell_core::proto::UpdateConfigRequest;
|
||||
|
||||
let req = UpdateConfigRequest {
|
||||
name,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
workspace, name,
|
||||
)),
|
||||
setting_key: key,
|
||||
delete_setting: true,
|
||||
workspace_scope: Some(named_workspace_scope(workspace)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -2640,7 +2657,9 @@ async fn refresh_sandbox_policy(app: &mut App) {
|
||||
None => return,
|
||||
};
|
||||
|
||||
let policy_req = openshell_core::proto::GetSandboxConfigRequest { sandbox_id };
|
||||
let policy_req = openshell_core::proto::GetSandboxConfigRequest {
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
};
|
||||
|
||||
match tokio::time::timeout(
|
||||
Duration::from_secs(5),
|
||||
@@ -2678,9 +2697,11 @@ async fn refresh_draft_chunks(app: &mut App) {
|
||||
};
|
||||
|
||||
let req = openshell_core::proto::GetDraftPolicyRequest {
|
||||
name: sandbox_name,
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
app.selected_sandbox_workspace(),
|
||||
sandbox_name,
|
||||
)),
|
||||
status_filter: String::new(),
|
||||
workspace_scope: Some(named_workspace_scope(app.selected_sandbox_workspace())),
|
||||
};
|
||||
|
||||
if let Ok(Ok(resp)) =
|
||||
@@ -2709,9 +2730,8 @@ async fn refresh_sandbox_draft_counts(app: &mut App) {
|
||||
.cloned()
|
||||
.unwrap_or_else(|| app.current_workspace.clone());
|
||||
let req = openshell_core::proto::GetDraftPolicyRequest {
|
||||
name: name.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(ws, name)),
|
||||
status_filter: "pending".to_string(),
|
||||
workspace_scope: Some(named_workspace_scope(ws)),
|
||||
};
|
||||
if let Ok(Ok(resp)) =
|
||||
tokio::time::timeout(Duration::from_secs(2), app.client.get_draft_policy(req)).await
|
||||
|
||||
@@ -166,7 +166,9 @@ The public API represents this choice with a `WorkspaceSelector` oneof. Set
|
||||
`all_workspaces` marker on list requests that support it. Omitting the selector
|
||||
or sending an unset selector is invalid for workspace-scoped operations. The
|
||||
all-workspaces variant is accepted only by sandbox, sandbox template, provider,
|
||||
and service list requests, and it requires Platform Admin access.
|
||||
and service list requests, and it requires Platform Admin access. For services,
|
||||
choose either a sandbox name or `--all-workspaces`; the options are mutually
|
||||
exclusive.
|
||||
|
||||
Clients migrating from the previous request fields should make the scope
|
||||
explicit:
|
||||
@@ -178,6 +180,21 @@ explicit:
|
||||
| `all_workspaces: true` | `workspace_scope.all_workspaces: {}` |
|
||||
| `global: true` | Omit `workspace_scope` |
|
||||
|
||||
Sandbox-scoped RPCs use a `SandboxReference` instead of separate sandbox and
|
||||
workspace fields. Use the name variant for human-readable lookup and include a
|
||||
named workspace selector. Use the ID variant when the canonical sandbox ID is
|
||||
already known; ID references must omit `workspace_scope`.
|
||||
|
||||
| Previous request fields | Sandbox reference |
|
||||
| --- | --- |
|
||||
| `name: "agent"`, `workspace_scope.workspace: "team-ml"` | `sandbox_ref.name: "agent"`, `sandbox_ref.workspace_scope.workspace: "team-ml"` |
|
||||
| `sandbox_name: "agent"`, `workspace_scope.workspace: "team-ml"` | `sandbox_ref.name: "agent"`, `sandbox_ref.workspace_scope.workspace: "team-ml"` |
|
||||
| `sandbox_id: "<canonical-id>"` | `sandbox_ref.id: "<canonical-id>"` |
|
||||
|
||||
Do not set both `sandbox_ref` and a request-level `workspace_scope`. List RPCs
|
||||
that are not filtered to one sandbox continue to use the request-level
|
||||
`workspace_scope` selector.
|
||||
|
||||
The Rust and Python SDKs expose separate all-workspaces list methods. The Go
|
||||
SDK uses `ListAll`, and the TypeScript SDK uses a discriminated option type, so
|
||||
a caller cannot select a named workspace and all workspaces in one typed call.
|
||||
|
||||
@@ -1252,11 +1252,13 @@ async fn propagate_policy_to_running_sandboxes(
|
||||
.map_or(0, |metadata| metadata.resource_version);
|
||||
let result = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: name.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default",
|
||||
name.clone(),
|
||||
)),
|
||||
policy: Some(policy_state.policy_proto.clone()),
|
||||
annotations: policy_update_annotations(policy_state, &correlation_id),
|
||||
expected_resource_version: resource_version,
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector("default")),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
|
||||
@@ -57,7 +57,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let before = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_id: sandbox_id.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(
|
||||
sandbox_id.clone(),
|
||||
)),
|
||||
})
|
||||
.await?
|
||||
.into_inner();
|
||||
@@ -83,9 +85,11 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
||||
let policy_result = client
|
||||
.update_config(UpdateConfigRequest {
|
||||
name: sandbox_name.clone(),
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_name(
|
||||
"default",
|
||||
sandbox_name.clone(),
|
||||
)),
|
||||
policy: Some(widened_policy),
|
||||
workspace_scope: Some(openshell_core::proto::workspace_selector("default")),
|
||||
..Default::default()
|
||||
})
|
||||
.await;
|
||||
@@ -120,7 +124,9 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
.map_err(|status| format!("telemetry-only policy analysis was denied: {status}"))?;
|
||||
|
||||
let after = client
|
||||
.get_sandbox_config(GetSandboxConfigRequest { sandbox_id })
|
||||
.get_sandbox_config(GetSandboxConfigRequest {
|
||||
sandbox_ref: Some(openshell_core::proto::sandbox_reference_by_id(sandbox_id)),
|
||||
})
|
||||
.await?
|
||||
.into_inner();
|
||||
if after.version != before.version || after.policy_hash != before.policy_hash {
|
||||
|
||||
@@ -27,6 +27,21 @@ message WorkspaceSelector {
|
||||
// Marker for the all-workspaces selector variant.
|
||||
message AllWorkspaces {}
|
||||
|
||||
// Identifies one existing sandbox in the public API.
|
||||
//
|
||||
// Exactly one identifier is required. `id` is the immutable, globally unique
|
||||
// gateway-generated ObjectMeta.id and MUST NOT include workspace_scope. `name`
|
||||
// is the human-readable ObjectMeta.name and requires a named workspace_scope;
|
||||
// all_workspaces is never valid for a sandbox reference.
|
||||
message SandboxReference {
|
||||
oneof identifier {
|
||||
string id = 1;
|
||||
string name = 2;
|
||||
}
|
||||
|
||||
WorkspaceSelector workspace_scope = 3;
|
||||
}
|
||||
|
||||
// Kubernetes-style metadata shared by all top-level OpenShell domain objects.
|
||||
//
|
||||
// This structure provides consistent metadata (identity, labels, annotations,
|
||||
|
||||
+90
-148
@@ -1214,12 +1214,9 @@ message BeginRootfsTarStagingResponse {
|
||||
|
||||
// Get sandbox request.
|
||||
message GetSandboxRequest {
|
||||
reserved 2;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key).
|
||||
string name = 1;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
|
||||
reserved 1, 2, 3;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 4;
|
||||
}
|
||||
|
||||
// List sandboxes request.
|
||||
@@ -1236,20 +1233,15 @@ message ListSandboxesRequest {
|
||||
|
||||
// List providers attached to a sandbox request.
|
||||
message ListSandboxProvidersRequest {
|
||||
reserved 2;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key).
|
||||
string sandbox_name = 1;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
|
||||
reserved 1, 2, 3;
|
||||
reserved "sandbox_name", "workspace", "workspace_scope";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 4;
|
||||
}
|
||||
|
||||
// Attach provider to sandbox request.
|
||||
message AttachSandboxProviderRequest {
|
||||
reserved 4;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key).
|
||||
string sandbox_name = 1;
|
||||
reserved 1, 4, 5;
|
||||
reserved "sandbox_name", "workspace", "workspace_scope";
|
||||
// Provider name to attach.
|
||||
string provider_name = 2;
|
||||
// Expected resource version for optimistic concurrency control.
|
||||
@@ -1257,16 +1249,13 @@ message AttachSandboxProviderRequest {
|
||||
// If non-zero, the server validates that the sandbox's current resource_version
|
||||
// matches this value before applying the mutation, returning ABORTED on mismatch.
|
||||
uint64 expected_resource_version = 3;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 6;
|
||||
}
|
||||
|
||||
// Detach provider from sandbox request.
|
||||
message DetachSandboxProviderRequest {
|
||||
reserved 4;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key).
|
||||
string sandbox_name = 1;
|
||||
reserved 1, 4, 5;
|
||||
reserved "sandbox_name", "workspace", "workspace_scope";
|
||||
// Provider name to detach.
|
||||
string provider_name = 2;
|
||||
// Expected resource version for optimistic concurrency control.
|
||||
@@ -1274,38 +1263,28 @@ message DetachSandboxProviderRequest {
|
||||
// If non-zero, the server validates that the sandbox's current resource_version
|
||||
// matches this value before applying the mutation, returning ABORTED on mismatch.
|
||||
uint64 expected_resource_version = 3;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 6;
|
||||
}
|
||||
|
||||
// Delete sandbox request.
|
||||
message DeleteSandboxRequest {
|
||||
reserved 2;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key).
|
||||
string name = 1;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
|
||||
reserved 1, 2, 3;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 4;
|
||||
}
|
||||
|
||||
// Stop sandbox request.
|
||||
message StopSandboxRequest {
|
||||
reserved 2;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key).
|
||||
string name = 1;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
|
||||
reserved 1, 2, 3;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 4;
|
||||
}
|
||||
|
||||
// Start sandbox request.
|
||||
message StartSandboxRequest {
|
||||
reserved 2;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key).
|
||||
string name = 1;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
|
||||
reserved 1, 2, 3;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 4;
|
||||
}
|
||||
|
||||
// Sandbox response.
|
||||
@@ -1344,8 +1323,9 @@ message DeleteSandboxResponse {
|
||||
|
||||
// Create SSH session request.
|
||||
message CreateSshSessionRequest {
|
||||
// Sandbox id.
|
||||
string sandbox_id = 1;
|
||||
reserved 1;
|
||||
reserved "sandbox_id";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 2;
|
||||
}
|
||||
|
||||
// Create SSH session response.
|
||||
@@ -1384,44 +1364,40 @@ message CreateSshSessionResponse {
|
||||
|
||||
// Request to expose an HTTP service running inside a sandbox.
|
||||
message ExposeServiceRequest {
|
||||
reserved 5;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string sandbox = 1;
|
||||
reserved 1, 5, 6;
|
||||
reserved "sandbox", "workspace", "workspace_scope";
|
||||
// Service name within the sandbox.
|
||||
string service = 2;
|
||||
// Loopback TCP port inside the sandbox.
|
||||
uint32 target_port = 3;
|
||||
// Whether to print/use the browser-facing service URL.
|
||||
bool domain = 4;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 7;
|
||||
}
|
||||
|
||||
// Request to fetch an exposed sandbox service endpoint.
|
||||
message GetServiceRequest {
|
||||
reserved 3;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string sandbox = 1;
|
||||
reserved 1, 3, 4;
|
||||
reserved "sandbox", "workspace", "workspace_scope";
|
||||
// Service name within the sandbox. Empty selects the unnamed endpoint.
|
||||
string service = 2;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 5;
|
||||
}
|
||||
|
||||
// Request to list exposed sandbox service endpoints.
|
||||
message ListServicesRequest {
|
||||
reserved 4, 5;
|
||||
reserved "workspace", "all_workspaces";
|
||||
// Optional sandbox name. Empty lists endpoints for all sandboxes.
|
||||
string sandbox = 1;
|
||||
reserved 1;
|
||||
reserved "sandbox";
|
||||
// Page size. Zero uses the server default.
|
||||
uint32 limit = 2;
|
||||
// Page offset.
|
||||
uint32 offset = 3;
|
||||
// Explicit named or all-workspaces scope.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6;
|
||||
// Optional sandbox filter. When present, workspace_scope must be omitted.
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 7;
|
||||
}
|
||||
|
||||
// Response containing exposed sandbox service endpoints.
|
||||
@@ -1431,14 +1407,11 @@ message ListServicesResponse {
|
||||
|
||||
// Request to delete an exposed sandbox service endpoint.
|
||||
message DeleteServiceRequest {
|
||||
reserved 3;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string sandbox = 1;
|
||||
reserved 1, 3, 4;
|
||||
reserved "sandbox", "workspace", "workspace_scope";
|
||||
// Service name within the sandbox. Empty selects the unnamed endpoint.
|
||||
string service = 2;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 5;
|
||||
}
|
||||
|
||||
// Response for deleting an exposed sandbox service endpoint.
|
||||
@@ -1483,8 +1456,8 @@ message RevokeSshSessionResponse {
|
||||
|
||||
// Execute command request.
|
||||
message ExecSandboxRequest {
|
||||
// Sandbox id.
|
||||
string sandbox_id = 1;
|
||||
reserved 1;
|
||||
reserved "sandbox_id";
|
||||
|
||||
// Command and arguments.
|
||||
repeated string command = 2;
|
||||
@@ -1516,6 +1489,7 @@ message ExecSandboxRequest {
|
||||
// sourced by them) are applied. When true, the command runs without those
|
||||
// files (`bash -c`), for automation that needs predictable startup behavior.
|
||||
bool no_login_shell = 10;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 11;
|
||||
}
|
||||
|
||||
// One stdout chunk from a sandbox exec.
|
||||
@@ -1544,8 +1518,9 @@ message ExecSandboxEvent {
|
||||
|
||||
// Initial frame for one TCP forward stream.
|
||||
message TcpForwardInit {
|
||||
// Sandbox id.
|
||||
string sandbox_id = 1;
|
||||
reserved 1;
|
||||
reserved "sandbox_id";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 2;
|
||||
// Optional service identifier for audit/correlation.
|
||||
string service_id = 4;
|
||||
// Target the gateway should request from the supervisor.
|
||||
@@ -1606,8 +1581,8 @@ message SshSession {
|
||||
|
||||
// Watch sandbox request.
|
||||
message WatchSandboxRequest {
|
||||
// Sandbox id.
|
||||
string id = 1;
|
||||
reserved 1;
|
||||
reserved "id";
|
||||
|
||||
// Stream sandbox status snapshots.
|
||||
bool follow_status = 2;
|
||||
@@ -1637,6 +1612,7 @@ message WatchSandboxRequest {
|
||||
|
||||
// Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels.
|
||||
string log_min_level = 10;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 11;
|
||||
}
|
||||
|
||||
// One event in a sandbox watch stream.
|
||||
@@ -2241,11 +2217,8 @@ message ExchangeProviderSubjectTokenResponse {
|
||||
|
||||
// Update sandbox policy request.
|
||||
message UpdateConfigRequest {
|
||||
reserved 10;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key). Required for sandbox-scoped updates.
|
||||
// Not required when `global=true`.
|
||||
string name = 1;
|
||||
reserved 1, 10, 11;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// The new policy to apply.
|
||||
//
|
||||
// Sandbox scope (`global=false`):
|
||||
@@ -2279,9 +2252,8 @@ message UpdateConfigRequest {
|
||||
// sandbox metadata as a convenience projection. For setting-only updates, it
|
||||
// only merges them into sandbox metadata.
|
||||
map<string, string> annotations = 9;
|
||||
// Explicit workspace scope for sandbox-scoped updates. Omit only when
|
||||
// `global` is true; the all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 11;
|
||||
// Required for sandbox-scoped updates and omitted for global updates.
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 12;
|
||||
}
|
||||
|
||||
message PolicyMergeOperation {
|
||||
@@ -2343,17 +2315,14 @@ message UpdateConfigResponse {
|
||||
|
||||
// Get sandbox policy status request.
|
||||
message GetSandboxPolicyStatusRequest {
|
||||
reserved 4;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key). Ignored when global is true.
|
||||
string name = 1;
|
||||
reserved 1, 4, 5;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// The specific policy version to query. 0 means latest.
|
||||
uint32 version = 2;
|
||||
// Query global policy revisions instead of a sandbox-scoped one.
|
||||
bool global = 3;
|
||||
// Explicit workspace scope for sandbox-scoped queries. Omit only when
|
||||
// `global` is true; the all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
|
||||
// Required for sandbox-scoped queries and omitted for global queries.
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 6;
|
||||
}
|
||||
|
||||
// Get sandbox policy status response.
|
||||
@@ -2366,17 +2335,14 @@ message GetSandboxPolicyStatusResponse {
|
||||
|
||||
// List sandbox policies request.
|
||||
message ListSandboxPoliciesRequest {
|
||||
reserved 5;
|
||||
reserved "workspace";
|
||||
// Sandbox name (canonical lookup key). Ignored when global is true.
|
||||
string name = 1;
|
||||
reserved 1, 5, 6;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
uint32 limit = 2;
|
||||
uint32 offset = 3;
|
||||
// List global policy revisions instead of sandbox-scoped ones.
|
||||
bool global = 4;
|
||||
// Explicit workspace scope for sandbox-scoped queries. Omit only when
|
||||
// `global` is true; the all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 6;
|
||||
// Required for sandbox-scoped queries and omitted for global queries.
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 7;
|
||||
}
|
||||
|
||||
// List sandbox policies response.
|
||||
@@ -2447,10 +2413,8 @@ enum PolicyStatus {
|
||||
|
||||
// Get sandbox logs request (one-shot fetch).
|
||||
message GetSandboxLogsRequest {
|
||||
reserved 6;
|
||||
reserved "workspace";
|
||||
// Sandbox id.
|
||||
string sandbox_id = 1;
|
||||
reserved 1, 6, 7;
|
||||
reserved "sandbox_id", "workspace", "workspace_scope";
|
||||
// Maximum number of log lines to return. 0 means use default (2000).
|
||||
uint32 lines = 2;
|
||||
// Only include logs with timestamp >= this value (ms since epoch). 0 means no filter.
|
||||
@@ -2459,8 +2423,7 @@ message GetSandboxLogsRequest {
|
||||
repeated string sources = 4;
|
||||
// Minimum log level to include (e.g. "INFO", "WARN", "ERROR"). Empty means all levels.
|
||||
string min_level = 5;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 7;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 8;
|
||||
}
|
||||
|
||||
// Batch of log lines pushed from sandbox to server.
|
||||
@@ -2790,11 +2753,14 @@ message SubmitPolicyAnalysisRequest {
|
||||
// to watch. Other values are treated as agent-style (no dedup) so a new
|
||||
// mode does not silently collapse proposals.
|
||||
string analysis_mode = 3;
|
||||
// Sandbox name.
|
||||
// Internal sandbox callback name. This request is not part of the public
|
||||
// SandboxReference contract; the authenticated sandbox principal is the
|
||||
// authoritative identity.
|
||||
string name = 4;
|
||||
// Anonymous network activity counters.
|
||||
repeated NetworkActivitySummary network_activity_summaries = 5;
|
||||
// Workspace scope. Empty defaults to "default".
|
||||
// Internal callback workspace. The gateway validates it against the
|
||||
// authenticated sandbox principal.
|
||||
string workspace = 6;
|
||||
}
|
||||
|
||||
@@ -2813,14 +2779,11 @@ message SubmitPolicyAnalysisResponse {
|
||||
|
||||
// Get draft policy for a sandbox.
|
||||
message GetDraftPolicyRequest {
|
||||
reserved 3;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
reserved 1, 3, 4;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// Optional status filter: "pending", "approved", "rejected", or "" for all.
|
||||
string status_filter = 2;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 5;
|
||||
}
|
||||
|
||||
message GetDraftPolicyResponse {
|
||||
@@ -2836,17 +2799,14 @@ message GetDraftPolicyResponse {
|
||||
|
||||
// Approve a single draft chunk.
|
||||
message ApproveDraftChunkRequest {
|
||||
reserved 3;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
reserved 1, 3, 5;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// Chunk ID to approve.
|
||||
string chunk_id = 2;
|
||||
// Token returned with the reviewed PolicyChunk. Approval fails with
|
||||
// FAILED_PRECONDITION if live decision inputs no longer match it.
|
||||
string review_token = 4;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 6;
|
||||
}
|
||||
|
||||
message ApproveDraftChunkResponse {
|
||||
@@ -2858,16 +2818,13 @@ message ApproveDraftChunkResponse {
|
||||
|
||||
// Reject a single draft chunk.
|
||||
message RejectDraftChunkRequest {
|
||||
reserved 4;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
reserved 1, 4, 5;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// Chunk ID to reject.
|
||||
string chunk_id = 2;
|
||||
// Optional reason for rejection (fed to LLM context in future analysis).
|
||||
string reason = 3;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 6;
|
||||
}
|
||||
|
||||
message RejectDraftChunkResponse {}
|
||||
@@ -2879,17 +2836,14 @@ message DraftChunkApproval {
|
||||
}
|
||||
|
||||
message ApproveAllDraftChunksRequest {
|
||||
reserved 3;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
reserved 1, 3, 5;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// Include chunks with security_notes (default false: skips them).
|
||||
bool include_security_flagged = 2;
|
||||
// Exact reviewed chunks and tokens. The server validates them against one
|
||||
// live snapshot, stages compatible operations in order, and writes once.
|
||||
repeated DraftChunkApproval approvals = 4;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 6;
|
||||
}
|
||||
|
||||
message ApproveAllDraftChunksResponse {
|
||||
@@ -2906,30 +2860,24 @@ message ApproveAllDraftChunksResponse {
|
||||
|
||||
// Edit a pending chunk in-place.
|
||||
message EditDraftChunkRequest {
|
||||
reserved 4;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
reserved 1, 4, 5;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// Chunk ID to edit.
|
||||
string chunk_id = 2;
|
||||
// The modified rule (replaces existing proposed_rule).
|
||||
openshell.sandbox.v1.NetworkPolicyRule proposed_rule = 3;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 5;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 6;
|
||||
}
|
||||
|
||||
message EditDraftChunkResponse {}
|
||||
|
||||
// Reverse an approval (remove merged rule from active policy).
|
||||
message UndoDraftChunkRequest {
|
||||
reserved 3;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
reserved 1, 3, 4;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
// Chunk ID to undo.
|
||||
string chunk_id = 2;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 4;
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 5;
|
||||
}
|
||||
|
||||
message UndoDraftChunkResponse {
|
||||
@@ -2941,12 +2889,9 @@ message UndoDraftChunkResponse {
|
||||
|
||||
// Clear all pending draft chunks for a sandbox.
|
||||
message ClearDraftChunksRequest {
|
||||
reserved 2;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
|
||||
reserved 1, 2, 3;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 4;
|
||||
}
|
||||
|
||||
message ClearDraftChunksResponse {
|
||||
@@ -2956,12 +2901,9 @@ message ClearDraftChunksResponse {
|
||||
|
||||
// Get decision history for a sandbox's draft policy.
|
||||
message GetDraftHistoryRequest {
|
||||
reserved 2;
|
||||
reserved "workspace";
|
||||
// Sandbox name.
|
||||
string name = 1;
|
||||
// Explicit workspace scope. The all-workspaces selection is invalid.
|
||||
openshell.datamodel.v1.WorkspaceSelector workspace_scope = 3;
|
||||
reserved 1, 2, 3;
|
||||
reserved "name", "workspace", "workspace_scope";
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 4;
|
||||
}
|
||||
|
||||
message DraftHistoryEntry {
|
||||
|
||||
+6
-2
@@ -5,6 +5,7 @@ syntax = "proto3";
|
||||
|
||||
package openshell.sandbox.v1;
|
||||
|
||||
import "datamodel.proto";
|
||||
import "google/protobuf/struct.proto";
|
||||
|
||||
// Sandbox-supervisor configuration and policy messages.
|
||||
@@ -317,8 +318,11 @@ message NetworkBinary {
|
||||
|
||||
// Request to get sandbox settings by sandbox ID.
|
||||
message GetSandboxConfigRequest {
|
||||
// The sandbox ID.
|
||||
string sandbox_id = 1;
|
||||
reserved 1;
|
||||
reserved "sandbox_id";
|
||||
// Canonical public sandbox reference. Sandbox-authenticated callers use the
|
||||
// ID variant; user clients may use either supported variant.
|
||||
openshell.datamodel.v1.SandboxReference sandbox_ref = 2;
|
||||
}
|
||||
|
||||
// Request to get gateway-global settings.
|
||||
|
||||
@@ -47,6 +47,18 @@ def _all_workspaces_scope() -> datamodel_pb2.WorkspaceSelector:
|
||||
return datamodel_pb2.WorkspaceSelector(all_workspaces=datamodel_pb2.AllWorkspaces())
|
||||
|
||||
|
||||
def _sandbox_reference_by_name(
|
||||
workspace: str, name: str
|
||||
) -> datamodel_pb2.SandboxReference:
|
||||
return datamodel_pb2.SandboxReference(
|
||||
name=name, workspace_scope=_workspace_scope(workspace)
|
||||
)
|
||||
|
||||
|
||||
def _sandbox_reference_by_id(sandbox_id: str) -> datamodel_pb2.SandboxReference:
|
||||
return datamodel_pb2.SandboxReference(id=sandbox_id)
|
||||
|
||||
|
||||
class _ClientCallDetails(_ClientCallDetailsBase, grpc.ClientCallDetails):
|
||||
pass
|
||||
|
||||
@@ -806,7 +818,7 @@ class SandboxClient:
|
||||
def get(self, sandbox_name: str, *, workspace: str) -> SandboxRef:
|
||||
response = self._stub.GetSandbox(
|
||||
openshell_pb2.GetSandboxRequest(
|
||||
name=sandbox_name, workspace_scope=_workspace_scope(workspace)
|
||||
sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name)
|
||||
),
|
||||
timeout=self._timeout,
|
||||
)
|
||||
@@ -885,7 +897,7 @@ class SandboxClient:
|
||||
def delete(self, sandbox_name: str, *, workspace: str) -> bool:
|
||||
response = self._stub.DeleteSandbox(
|
||||
openshell_pb2.DeleteSandboxRequest(
|
||||
name=sandbox_name, workspace_scope=_workspace_scope(workspace)
|
||||
sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name)
|
||||
),
|
||||
timeout=self._timeout,
|
||||
)
|
||||
@@ -894,7 +906,7 @@ class SandboxClient:
|
||||
def stop(self, sandbox_name: str, *, workspace: str) -> SandboxRef:
|
||||
response = self._stub.StopSandbox(
|
||||
openshell_pb2.StopSandboxRequest(
|
||||
name=sandbox_name, workspace_scope=_workspace_scope(workspace)
|
||||
sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name)
|
||||
),
|
||||
timeout=self._timeout,
|
||||
)
|
||||
@@ -903,7 +915,7 @@ class SandboxClient:
|
||||
def start(self, sandbox_name: str, *, workspace: str) -> SandboxRef:
|
||||
response = self._stub.StartSandbox(
|
||||
openshell_pb2.StartSandboxRequest(
|
||||
name=sandbox_name, workspace_scope=_workspace_scope(workspace)
|
||||
sandbox_ref=_sandbox_reference_by_name(workspace, sandbox_name)
|
||||
),
|
||||
timeout=self._timeout,
|
||||
)
|
||||
@@ -996,7 +1008,7 @@ class SandboxClient:
|
||||
raise SandboxError("command must not be empty")
|
||||
|
||||
request = openshell_pb2.ExecSandboxRequest(
|
||||
sandbox_id=sandbox_id,
|
||||
sandbox_ref=_sandbox_reference_by_id(sandbox_id),
|
||||
command=list(command),
|
||||
workdir=workdir or "",
|
||||
environment=dict(env or {}),
|
||||
|
||||
@@ -43,7 +43,12 @@ from openshell.sandbox import (
|
||||
|
||||
|
||||
def _request_workspace(request: Any) -> str | None:
|
||||
scope = request.workspace_scope
|
||||
sandbox_ref = getattr(request, "sandbox_ref", None)
|
||||
scope = (
|
||||
sandbox_ref.workspace_scope
|
||||
if sandbox_ref is not None and sandbox_ref.HasField("workspace_scope")
|
||||
else request.workspace_scope
|
||||
)
|
||||
if scope.WhichOneof("selection") == "workspace":
|
||||
return cast("str", scope.workspace)
|
||||
return None
|
||||
@@ -53,6 +58,12 @@ def _request_selects_all_workspaces(request: Any) -> bool:
|
||||
return request.workspace_scope.WhichOneof("selection") == "all_workspaces"
|
||||
|
||||
|
||||
def _request_sandbox_name(request: Any) -> str:
|
||||
sandbox_ref = request.sandbox_ref
|
||||
assert sandbox_ref.WhichOneof("identifier") == "name"
|
||||
return cast("str", sandbox_ref.name)
|
||||
|
||||
|
||||
def _client_credentials_fixture() -> dict[str, Any]:
|
||||
return json.loads(
|
||||
(
|
||||
@@ -1993,7 +2004,7 @@ class _FakeSandboxStub:
|
||||
return SimpleNamespace(
|
||||
sandbox=_make_sandbox_proto(
|
||||
"sandbox-1",
|
||||
request.name,
|
||||
_request_sandbox_name(request),
|
||||
workspace=_request_workspace(request) or "default",
|
||||
)
|
||||
)
|
||||
@@ -2017,7 +2028,7 @@ class _FakeSandboxStub:
|
||||
return SimpleNamespace(
|
||||
sandbox=_make_sandbox_proto(
|
||||
"sandbox-1",
|
||||
request.name,
|
||||
_request_sandbox_name(request),
|
||||
phase=openshell_pb2.SANDBOX_PHASE_STOPPED,
|
||||
workspace=_request_workspace(request) or "default",
|
||||
)
|
||||
@@ -2033,7 +2044,7 @@ class _FakeSandboxStub:
|
||||
return SimpleNamespace(
|
||||
sandbox=_make_sandbox_proto(
|
||||
"sandbox-1",
|
||||
request.name,
|
||||
_request_sandbox_name(request),
|
||||
phase=openshell_pb2.SANDBOX_PHASE_STARTING,
|
||||
workspace=_request_workspace(request) or "default",
|
||||
)
|
||||
@@ -2429,13 +2440,13 @@ def test_stop_and_start_forward_workspace_and_return_phase() -> None:
|
||||
|
||||
stopped = client.stop("job-1", workspace="team-a")
|
||||
assert stub.stop_request is not None
|
||||
assert stub.stop_request.name == "job-1"
|
||||
assert _request_sandbox_name(stub.stop_request) == "job-1"
|
||||
assert _request_workspace(stub.stop_request) == "team-a"
|
||||
assert stopped.phase == openshell_pb2.SANDBOX_PHASE_STOPPED
|
||||
|
||||
starting = client.start("job-1", workspace="team-a")
|
||||
assert stub.start_request is not None
|
||||
assert stub.start_request.name == "job-1"
|
||||
assert _request_sandbox_name(stub.start_request) == "job-1"
|
||||
assert _request_workspace(stub.start_request) == "team-a"
|
||||
assert starting.phase == openshell_pb2.SANDBOX_PHASE_STARTING
|
||||
|
||||
@@ -2460,7 +2471,7 @@ def test_wait_ready_handles_terminal_main_process_results(
|
||||
return SimpleNamespace(
|
||||
sandbox=_make_sandbox_proto(
|
||||
"sandbox-1",
|
||||
request.name,
|
||||
_request_sandbox_name(request),
|
||||
phase=phase,
|
||||
workspace=_request_workspace(request) or "default",
|
||||
)
|
||||
|
||||
@@ -31,7 +31,7 @@ func (c *configClient) GetSandbox(ctx context.Context, workspace, sandboxName st
|
||||
}
|
||||
|
||||
resp, err := c.client.GetSandboxConfig(ctx, &sbv1.GetSandboxConfigRequest{
|
||||
SandboxId: sb.ID,
|
||||
SandboxRef: sandboxReferenceByID(sb.ID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -59,7 +59,7 @@ func (c *configClient) Update(ctx context.Context, workspace string, update *Con
|
||||
return nil, &StatusError{Code: ErrorInvalidArgument, Message: convErr.Error()}
|
||||
}
|
||||
if !req.GetGlobal() {
|
||||
req.WorkspaceScope = namedWorkspaceScope(workspace)
|
||||
req.SandboxRef.WorkspaceScope = namedWorkspaceScope(workspace)
|
||||
}
|
||||
resp, err := c.client.UpdateConfig(ctx, req)
|
||||
if err != nil {
|
||||
|
||||
@@ -143,7 +143,7 @@ func TestConfigGetSandbox(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded with resolved ID (stubSandboxResolver returns "sb-<name>").
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxRef().GetId())
|
||||
mock.mu.Unlock()
|
||||
|
||||
// Scalar fields.
|
||||
@@ -235,7 +235,7 @@ func TestConfigGetSandbox_ResolvesNameToID(t *testing.T) {
|
||||
|
||||
// stubSandboxResolver returns ID "sb-<name>" — verify the proto has the resolved ID, not the name.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxId(), "GetSandbox should send resolved sandbox ID, not the name")
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastSandboxReq.GetSandboxRef().GetId(), "GetSandbox should send resolved sandbox ID, not the name")
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -361,7 +361,7 @@ func TestConfigUpdate_SandboxScope(t *testing.T) {
|
||||
mock.mu.Unlock()
|
||||
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, "my-sandbox", req.GetName())
|
||||
assert.Equal(t, "my-sandbox", req.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "max_tokens", req.GetSettingKey())
|
||||
assert.False(t, req.GetGlobal())
|
||||
assert.Equal(t, uint64(4), req.GetExpectedResourceVersion())
|
||||
@@ -397,7 +397,7 @@ func TestConfigUpdate_GlobalScope(t *testing.T) {
|
||||
mock.mu.Unlock()
|
||||
|
||||
assert.True(t, req.GetGlobal())
|
||||
assert.Empty(t, req.GetName())
|
||||
assert.Nil(t, req.GetSandboxRef())
|
||||
}
|
||||
|
||||
func TestConfigUpdate_DeleteSetting(t *testing.T) {
|
||||
|
||||
@@ -314,7 +314,7 @@
|
||||
//
|
||||
// Read a policy back from revision history:
|
||||
//
|
||||
// revisions, err := client.Policy().List(ctx, "default")
|
||||
// revisions, err := client.Policy().List(ctx, "default", "my-sandbox")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
@@ -328,7 +328,7 @@
|
||||
//
|
||||
// List gateway-global policy revisions (no sandbox name or workspace needed):
|
||||
//
|
||||
// revisions, err := client.Policy().List(ctx, "", v1.WithListGlobal(true))
|
||||
// revisions, err := client.Policy().List(ctx, "", "", v1.WithListGlobal(true))
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
|
||||
@@ -236,7 +236,7 @@ func TestExecRun_WithOptions(t *testing.T) {
|
||||
|
||||
mock.mu.Lock()
|
||||
defer mock.mu.Unlock()
|
||||
assert.Equal(t, "sb-test-sandbox", mock.lastExecRequest.GetSandboxId())
|
||||
assert.Equal(t, "sb-test-sandbox", mock.lastExecRequest.GetSandboxRef().GetId())
|
||||
assert.Equal(t, []string{"ls"}, mock.lastExecRequest.GetCommand())
|
||||
assert.Equal(t, "/tmp", mock.lastExecRequest.GetWorkdir())
|
||||
assert.Equal(t, map[string]string{"FOO": "bar"}, mock.lastExecRequest.GetEnvironment())
|
||||
@@ -380,7 +380,7 @@ func TestExecInteractive(t *testing.T) {
|
||||
|
||||
startReq := startInput.GetStart()
|
||||
require.NotNil(t, startReq)
|
||||
assert.Equal(t, "sb-test-sandbox", startReq.GetSandboxId())
|
||||
assert.Equal(t, "sb-test-sandbox", startReq.GetSandboxRef().GetId())
|
||||
assert.Equal(t, []string{"/bin/bash"}, startReq.GetCommand())
|
||||
assert.True(t, startReq.GetTty())
|
||||
assert.Equal(t, uint32(80), startReq.GetCols())
|
||||
@@ -546,7 +546,7 @@ func TestExecRun_ResolvesNameToID(t *testing.T) {
|
||||
mock.mu.Lock()
|
||||
defer mock.mu.Unlock()
|
||||
// Verify the proto request contains the resolved ID, not the name
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxRef().GetId())
|
||||
}
|
||||
|
||||
func TestExecRun_ResolutionError(t *testing.T) {
|
||||
@@ -576,7 +576,7 @@ func TestExecStream_ResolvesNameToID(t *testing.T) {
|
||||
|
||||
mock.mu.Lock()
|
||||
defer mock.mu.Unlock()
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastExecRequest.GetSandboxRef().GetId())
|
||||
}
|
||||
|
||||
func TestExecStream_ResolutionError(t *testing.T) {
|
||||
@@ -609,7 +609,7 @@ func TestExecInteractive_ResolvesNameToID(t *testing.T) {
|
||||
require.NotEmpty(t, mock.receivedInputs)
|
||||
startReq := mock.receivedInputs[0].GetStart()
|
||||
require.NotNil(t, startReq)
|
||||
assert.Equal(t, "sb-my-sandbox", startReq.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", startReq.GetSandboxRef().GetId())
|
||||
}
|
||||
|
||||
func TestExecInteractive_ResolutionError(t *testing.T) {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"context"
|
||||
"maps"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1"
|
||||
@@ -172,9 +171,8 @@ func (c *fakePolicyClient) GetStatus(_ context.Context, workspace, sandboxName s
|
||||
}
|
||||
|
||||
// List returns policy revisions. When the global option is set, it returns
|
||||
// global revisions; otherwise it returns all sandbox-scoped revisions for the
|
||||
// given workspace.
|
||||
func (c *fakePolicyClient) List(_ context.Context, workspace string, opts ...v1.ListPolicyOption) ([]types.SandboxPolicyRevision, error) {
|
||||
// global revisions; otherwise it returns revisions for the specified sandbox.
|
||||
func (c *fakePolicyClient) List(_ context.Context, workspace, sandboxName string, opts ...v1.ListPolicyOption) ([]types.SandboxPolicyRevision, error) {
|
||||
if c.closedFunc() {
|
||||
return nil, &types.StatusError{Code: types.ErrorUnavailable, Message: "client is closed"}
|
||||
}
|
||||
@@ -187,13 +185,7 @@ func (c *fakePolicyClient) List(_ context.Context, workspace string, opts ...v1.
|
||||
if cfg.Global() {
|
||||
revisions = slices.Clone(c.globalRevisions)
|
||||
} else {
|
||||
// Collect all revisions for sandboxes in this workspace.
|
||||
prefix := workspace + "/"
|
||||
for key, revs := range c.sandboxRevisions {
|
||||
if strings.HasPrefix(key, prefix) {
|
||||
revisions = append(revisions, revs...)
|
||||
}
|
||||
}
|
||||
revisions = slices.Clone(c.sandboxRevisions[workspace+"/"+sandboxName])
|
||||
}
|
||||
|
||||
if len(revisions) == 0 {
|
||||
|
||||
@@ -66,7 +66,7 @@ func TestFakePolicy_GetStatus_EmptyReturnsNotFound(t *testing.T) {
|
||||
|
||||
func TestFakePolicy_List_EmptyReturnsNil(t *testing.T) {
|
||||
c := newFakePolicyClient(func() bool { return false })
|
||||
revisions, err := c.List(context.Background(), "default")
|
||||
revisions, err := c.List(context.Background(), "default", "sb-1")
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, revisions)
|
||||
}
|
||||
@@ -96,7 +96,7 @@ func TestFakePolicy_List_Global(t *testing.T) {
|
||||
c.AddRevision("default", "sb-1", types.SandboxPolicyRevision{Version: 1, PolicyHash: "sha256:sb-v1", Status: types.PolicyLoadStatusLoaded})
|
||||
|
||||
// List global revisions.
|
||||
revisions, err := c.List(context.Background(), "", types.WithListGlobal(true))
|
||||
revisions, err := c.List(context.Background(), "", "", types.WithListGlobal(true))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 2)
|
||||
assert.Equal(t, uint32(1), revisions[0].Version)
|
||||
@@ -113,7 +113,7 @@ func TestFakePolicy_List_Sandbox(t *testing.T) {
|
||||
c.AddRevision("default", "sb-1", types.SandboxPolicyRevision{Version: 2, PolicyHash: "sha256:sb-v2", Status: types.PolicyLoadStatusPending})
|
||||
|
||||
// List sandbox-scoped revisions (no global flag).
|
||||
revisions, err := c.List(context.Background(), "default")
|
||||
revisions, err := c.List(context.Background(), "default", "sb-1")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 2)
|
||||
assert.Equal(t, "sha256:sb-v1", revisions[0].PolicyHash)
|
||||
@@ -127,7 +127,7 @@ func TestFakePolicy_List_NoIsolationCrossContamination(t *testing.T) {
|
||||
c.AddRevision("default", "sb-1", types.SandboxPolicyRevision{Version: 1, PolicyHash: "sha256:sb-v1"})
|
||||
|
||||
// Global list returns empty (no global revisions seeded).
|
||||
revisions, err := c.List(context.Background(), "", types.WithListGlobal(true))
|
||||
revisions, err := c.List(context.Background(), "", "", types.WithListGlobal(true))
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, revisions)
|
||||
}
|
||||
@@ -140,14 +140,14 @@ func TestFakePolicy_List_GlobalWithPagination(t *testing.T) {
|
||||
c.AddGlobalRevision(types.SandboxPolicyRevision{Version: 3})
|
||||
|
||||
// Limit to 2.
|
||||
revisions, err := c.List(context.Background(), "", types.WithListGlobal(true), types.WithLimit(2))
|
||||
revisions, err := c.List(context.Background(), "", "", types.WithListGlobal(true), types.WithLimit(2))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 2)
|
||||
assert.Equal(t, uint32(1), revisions[0].Version)
|
||||
assert.Equal(t, uint32(2), revisions[1].Version)
|
||||
|
||||
// Offset by 1, limit 2.
|
||||
revisions, err = c.List(context.Background(), "", types.WithListGlobal(true), types.WithLimit(2), types.WithOffset(1))
|
||||
revisions, err = c.List(context.Background(), "", "", types.WithListGlobal(true), types.WithLimit(2), types.WithOffset(1))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 2)
|
||||
assert.Equal(t, uint32(2), revisions[0].Version)
|
||||
@@ -236,14 +236,14 @@ func TestFakePolicy_DeepCopyWithPolicy(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Get global revision and mutate it.
|
||||
revisions, err := fc.Policy().List(ctx, "", types.WithListGlobal(true))
|
||||
revisions, err := fc.Policy().List(ctx, "", "", types.WithListGlobal(true))
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 1)
|
||||
require.NotNil(t, revisions[0].Policy)
|
||||
revisions[0].Policy.NetworkPolicies["rule-1"] = types.NetworkPolicyRule{Name: "mutated"}
|
||||
|
||||
// Verify internal state is not corrupted.
|
||||
revisions2, err := fc.Policy().List(ctx, "", types.WithListGlobal(true))
|
||||
revisions2, err := fc.Policy().List(ctx, "", "", types.WithListGlobal(true))
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "rule-1", revisions2[0].Policy.NetworkPolicies["rule-1"].Name)
|
||||
|
||||
@@ -256,7 +256,7 @@ func TestFakePolicy_DeepCopyWithPolicy(t *testing.T) {
|
||||
|
||||
func TestFakePolicy_List_ClosedReturnsUnavailable(t *testing.T) {
|
||||
c := newFakePolicyClient(func() bool { return true })
|
||||
_, err := c.List(context.Background(), "", types.WithListGlobal(true))
|
||||
_, err := c.List(context.Background(), "", "", types.WithListGlobal(true))
|
||||
require.Error(t, err)
|
||||
assert.True(t, types.IsUnavailable(err))
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (f *fileClient) Upload(ctx context.Context, workspace, sandboxName string,
|
||||
}
|
||||
|
||||
session, err := f.client.CreateSshSession(ctx, &pb.CreateSshSessionRequest{
|
||||
SandboxId: sb.ID,
|
||||
SandboxRef: sandboxReferenceByID(sb.ID),
|
||||
})
|
||||
if err != nil {
|
||||
return converter.FromGRPCError(err)
|
||||
@@ -93,7 +93,7 @@ func (f *fileClient) Download(ctx context.Context, workspace, sandboxName string
|
||||
}
|
||||
|
||||
session, err := f.client.CreateSshSession(ctx, &pb.CreateSshSessionRequest{
|
||||
SandboxId: sb.ID,
|
||||
SandboxRef: sandboxReferenceByID(sb.ID),
|
||||
})
|
||||
if err != nil {
|
||||
return converter.FromGRPCError(err)
|
||||
|
||||
@@ -109,7 +109,7 @@ func TestFileUpload(t *testing.T) {
|
||||
err := client.Upload(context.Background(), "default", "test-sandbox", localPath, "/remote/upload.txt")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxId())
|
||||
assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxRef().GetId())
|
||||
assert.Equal(t, 1, mock.createCallCount)
|
||||
}
|
||||
|
||||
@@ -164,7 +164,7 @@ func TestFileDownload(t *testing.T) {
|
||||
err := client.Download(context.Background(), "default", "test-sandbox", "/remote/file.txt", localPath)
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxId())
|
||||
assert.Equal(t, "sb-test-sandbox", mock.lastCreateReq.GetSandboxRef().GetId())
|
||||
assert.Equal(t, 1, mock.createCallCount)
|
||||
}
|
||||
|
||||
@@ -261,7 +261,7 @@ func TestFileUpload_ResolvesNameToID(t *testing.T) {
|
||||
_ = client.Upload(context.Background(), "default", "my-sandbox", localPath, "/remote/file.txt")
|
||||
|
||||
// Verify the proto request contains the resolved ID, not the name
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxRef().GetId())
|
||||
}
|
||||
|
||||
func TestFileUpload_ResolutionError(t *testing.T) {
|
||||
@@ -307,7 +307,7 @@ func TestFileDownload_ResolvesNameToID(t *testing.T) {
|
||||
localPath := filepath.Join(t.TempDir(), "downloaded.txt")
|
||||
_ = client.Download(context.Background(), "default", "my-sandbox", "/remote/file.txt", localPath)
|
||||
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", mock.lastCreateReq.GetSandboxRef().GetId())
|
||||
}
|
||||
|
||||
func TestFileDownload_ResolutionError(t *testing.T) {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types"
|
||||
dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1"
|
||||
pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1"
|
||||
)
|
||||
|
||||
@@ -39,8 +40,10 @@ func ExecChunkFromEvent(event *pb.ExecSandboxEvent) (*types.ExecChunk, int, erro
|
||||
// ExecRequestToProto builds a proto ExecSandboxRequest for Run/Stream modes.
|
||||
func ExecRequestToProto(sandboxID string, command []string, opts *types.ExecOptions) *pb.ExecSandboxRequest {
|
||||
req := &pb.ExecSandboxRequest{
|
||||
SandboxId: sandboxID,
|
||||
Command: CopyStringSlice(command),
|
||||
SandboxRef: &dm.SandboxReference{
|
||||
Identifier: &dm.SandboxReference_Id{Id: sandboxID},
|
||||
},
|
||||
Command: CopyStringSlice(command),
|
||||
}
|
||||
if opts != nil {
|
||||
req.Workdir = opts.WorkDir
|
||||
|
||||
@@ -117,7 +117,7 @@ func TestExecRequestToProto(t *testing.T) {
|
||||
})
|
||||
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, "sb-1", req.SandboxId)
|
||||
assert.Equal(t, "sb-1", req.GetSandboxRef().GetId())
|
||||
assert.Equal(t, []string{"ls", "-la"}, req.Command)
|
||||
assert.Equal(t, "/home/user", req.Workdir)
|
||||
assert.Equal(t, map[string]string{"FOO": "bar"}, req.Environment)
|
||||
@@ -128,7 +128,7 @@ func TestExecRequestToProto_NilOptions(t *testing.T) {
|
||||
req := ExecRequestToProto("sb-2", []string{"echo", "hi"}, nil)
|
||||
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, "sb-2", req.SandboxId)
|
||||
assert.Equal(t, "sb-2", req.GetSandboxRef().GetId())
|
||||
assert.Equal(t, []string{"echo", "hi"}, req.Command)
|
||||
assert.Empty(t, req.Workdir)
|
||||
assert.Nil(t, req.Environment)
|
||||
@@ -141,7 +141,7 @@ func TestExecRequestToProto_Interactive(t *testing.T) {
|
||||
})
|
||||
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, "sb-3", req.SandboxId)
|
||||
assert.Equal(t, "sb-3", req.GetSandboxRef().GetId())
|
||||
assert.Equal(t, []string{"/bin/bash"}, req.Command)
|
||||
assert.Equal(t, "/root", req.Workdir)
|
||||
assert.Equal(t, map[string]string{"TERM": "xterm"}, req.Environment)
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
v1 "github.com/NVIDIA/OpenShell/sdk/go/openshell/v1/types"
|
||||
dm "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1"
|
||||
pb "github.com/NVIDIA/OpenShell/sdk/go/proto/openshellv1"
|
||||
sbv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/sandboxv1"
|
||||
)
|
||||
@@ -180,7 +181,6 @@ func ConfigUpdateToProto(cu *v1.ConfigUpdate) (*pb.UpdateConfigRequest, error) {
|
||||
return nil, nil
|
||||
}
|
||||
req := &pb.UpdateConfigRequest{
|
||||
Name: cu.Name,
|
||||
SettingKey: cu.SettingKey,
|
||||
SettingValue: SettingValueToProto(cu.SettingValue),
|
||||
DeleteSetting: cu.DeleteSetting,
|
||||
@@ -188,6 +188,11 @@ func ConfigUpdateToProto(cu *v1.ConfigUpdate) (*pb.UpdateConfigRequest, error) {
|
||||
ExpectedResourceVersion: cu.ExpectedResourceVersion,
|
||||
Annotations: CopyStringMap(cu.Annotations),
|
||||
}
|
||||
if !cu.Global {
|
||||
req.SandboxRef = &dm.SandboxReference{
|
||||
Identifier: &dm.SandboxReference_Name{Name: cu.Name},
|
||||
}
|
||||
}
|
||||
|
||||
// Convert typed SDK SandboxPolicy to proto SandboxPolicy.
|
||||
policy, err := SandboxPolicyToProtoChecked(cu.Policy)
|
||||
|
||||
@@ -429,7 +429,7 @@ func TestConfigUpdateToProto(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, "my-sandbox", req.Name)
|
||||
assert.Equal(t, "my-sandbox", req.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "timeout", req.SettingKey)
|
||||
require.NotNil(t, req.SettingValue)
|
||||
assert.Equal(t, int64(60), req.SettingValue.GetIntValue())
|
||||
@@ -495,7 +495,7 @@ func TestConfigUpdateToProto_GlobalScope(t *testing.T) {
|
||||
|
||||
require.NotNil(t, req)
|
||||
assert.True(t, req.Global)
|
||||
assert.Empty(t, req.Name)
|
||||
assert.Nil(t, req.GetSandboxRef())
|
||||
}
|
||||
|
||||
func TestConfigUpdateToProto_NilSettingValue(t *testing.T) {
|
||||
@@ -779,7 +779,7 @@ func TestConfigUpdateToProto_WithMergeOperations(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, "my-sandbox", req.GetName())
|
||||
assert.Equal(t, "my-sandbox", req.GetSandboxRef().GetName())
|
||||
require.Len(t, req.GetMergeOperations(), 3)
|
||||
|
||||
// First: RemoveRule
|
||||
|
||||
@@ -111,7 +111,7 @@ type PolicyInterface interface {
|
||||
ClearDraftChunks(ctx context.Context, workspace, sandboxName string) (*ClearResult, error)
|
||||
GetDraftHistory(ctx context.Context, workspace, sandboxName string) ([]DraftHistoryEntry, error)
|
||||
GetStatus(ctx context.Context, workspace, sandboxName string, opts ...GetStatusOption) (*PolicyStatusResult, error)
|
||||
List(ctx context.Context, workspace string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error)
|
||||
List(ctx context.Context, workspace, sandboxName string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error)
|
||||
EditDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string, proposedRule *NetworkPolicyRule) error
|
||||
UndoDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string) (*UndoResult, error)
|
||||
}
|
||||
|
||||
@@ -23,9 +23,8 @@ func newPolicyClient(conn grpc.ClientConnInterface) *policyClient {
|
||||
func (p *policyClient) GetDraft(ctx context.Context, workspace, sandboxName string, opts ...GetDraftOption) (*DraftPolicy, error) {
|
||||
cfg := types.ApplyGetDraftOptions(opts)
|
||||
resp, err := p.client.GetDraftPolicy(ctx, &pb.GetDraftPolicyRequest{
|
||||
Name: sandboxName,
|
||||
StatusFilter: cfg.StatusFilter(),
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
StatusFilter: cfg.StatusFilter(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -35,10 +34,9 @@ func (p *policyClient) GetDraft(ctx context.Context, workspace, sandboxName stri
|
||||
|
||||
func (p *policyClient) ApproveDraftChunk(ctx context.Context, workspace, sandboxName, chunkID, reviewToken string) (*ApproveResult, error) {
|
||||
resp, err := p.client.ApproveDraftChunk(ctx, &pb.ApproveDraftChunkRequest{
|
||||
Name: sandboxName,
|
||||
ChunkId: chunkID,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
ReviewToken: reviewToken,
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
ChunkId: chunkID,
|
||||
ReviewToken: reviewToken,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -48,10 +46,9 @@ func (p *policyClient) ApproveDraftChunk(ctx context.Context, workspace, sandbox
|
||||
|
||||
func (p *policyClient) RejectDraftChunk(ctx context.Context, workspace, sandboxName, chunkID, reason string) error {
|
||||
_, err := p.client.RejectDraftChunk(ctx, &pb.RejectDraftChunkRequest{
|
||||
Name: sandboxName,
|
||||
ChunkId: chunkID,
|
||||
Reason: reason,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
ChunkId: chunkID,
|
||||
Reason: reason,
|
||||
})
|
||||
if err != nil {
|
||||
return converter.FromGRPCError(err)
|
||||
@@ -69,10 +66,9 @@ func (p *policyClient) ApproveAllDraftChunks(ctx context.Context, workspace, san
|
||||
})
|
||||
}
|
||||
resp, err := p.client.ApproveAllDraftChunks(ctx, &pb.ApproveAllDraftChunksRequest{
|
||||
Name: sandboxName,
|
||||
IncludeSecurityFlagged: cfg.IncludeSecurityFlagged(),
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
Approvals: approvals,
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -82,8 +78,7 @@ func (p *policyClient) ApproveAllDraftChunks(ctx context.Context, workspace, san
|
||||
|
||||
func (p *policyClient) ClearDraftChunks(ctx context.Context, workspace, sandboxName string) (*ClearResult, error) {
|
||||
resp, err := p.client.ClearDraftChunks(ctx, &pb.ClearDraftChunksRequest{
|
||||
Name: sandboxName,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -93,8 +88,7 @@ func (p *policyClient) ClearDraftChunks(ctx context.Context, workspace, sandboxN
|
||||
|
||||
func (p *policyClient) GetDraftHistory(ctx context.Context, workspace, sandboxName string) ([]DraftHistoryEntry, error) {
|
||||
resp, err := p.client.GetDraftHistory(ctx, &pb.GetDraftHistoryRequest{
|
||||
Name: sandboxName,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -115,12 +109,11 @@ func (p *policyClient) GetDraftHistory(ctx context.Context, workspace, sandboxNa
|
||||
func (p *policyClient) GetStatus(ctx context.Context, workspace, sandboxName string, opts ...GetStatusOption) (*PolicyStatusResult, error) {
|
||||
cfg := types.ApplyGetStatusOptions(opts)
|
||||
req := &pb.GetSandboxPolicyStatusRequest{
|
||||
Name: sandboxName,
|
||||
Version: cfg.Version(),
|
||||
Global: cfg.Global(),
|
||||
}
|
||||
if !cfg.Global() {
|
||||
req.WorkspaceScope = namedWorkspaceScope(workspace)
|
||||
req.SandboxRef = sandboxReferenceByName(workspace, sandboxName)
|
||||
}
|
||||
resp, err := p.client.GetSandboxPolicyStatus(ctx, req)
|
||||
if err != nil {
|
||||
@@ -129,7 +122,7 @@ func (p *policyClient) GetStatus(ctx context.Context, workspace, sandboxName str
|
||||
return converter.PolicyStatusResultFromProto(resp), nil
|
||||
}
|
||||
|
||||
func (p *policyClient) List(ctx context.Context, workspace string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error) {
|
||||
func (p *policyClient) List(ctx context.Context, workspace, sandboxName string, opts ...ListPolicyOption) ([]SandboxPolicyRevision, error) {
|
||||
cfg := types.ApplyListPolicyOptions(opts)
|
||||
req := &pb.ListSandboxPoliciesRequest{
|
||||
Limit: cfg.Limit(),
|
||||
@@ -137,7 +130,7 @@ func (p *policyClient) List(ctx context.Context, workspace string, opts ...ListP
|
||||
Global: cfg.Global(),
|
||||
}
|
||||
if !cfg.Global() {
|
||||
req.WorkspaceScope = namedWorkspaceScope(workspace)
|
||||
req.SandboxRef = sandboxReferenceByName(workspace, sandboxName)
|
||||
}
|
||||
resp, err := p.client.ListSandboxPolicies(ctx, req)
|
||||
if err != nil {
|
||||
@@ -158,10 +151,9 @@ func (p *policyClient) List(ctx context.Context, workspace string, opts ...ListP
|
||||
|
||||
func (p *policyClient) EditDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string, proposedRule *NetworkPolicyRule) error {
|
||||
_, err := p.client.EditDraftChunk(ctx, &pb.EditDraftChunkRequest{
|
||||
Name: sandboxName,
|
||||
ChunkId: chunkID,
|
||||
ProposedRule: converter.NetworkPolicyRuleToProto(proposedRule),
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
ChunkId: chunkID,
|
||||
ProposedRule: converter.NetworkPolicyRuleToProto(proposedRule),
|
||||
})
|
||||
if err != nil {
|
||||
return converter.FromGRPCError(err)
|
||||
@@ -171,9 +163,8 @@ func (p *policyClient) EditDraftChunk(ctx context.Context, workspace, sandboxNam
|
||||
|
||||
func (p *policyClient) UndoDraftChunk(ctx context.Context, workspace, sandboxName, chunkID string) (*UndoResult, error) {
|
||||
resp, err := p.client.UndoDraftChunk(ctx, &pb.UndoDraftChunkRequest{
|
||||
Name: sandboxName,
|
||||
ChunkId: chunkID,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
ChunkId: chunkID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
|
||||
@@ -235,7 +235,7 @@ func TestPolicyGetDraft(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastGetDraftReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastGetDraftReq.GetSandboxRef().GetName())
|
||||
assert.Empty(t, mock.lastGetDraftReq.GetStatusFilter())
|
||||
mock.mu.Unlock()
|
||||
|
||||
@@ -321,7 +321,7 @@ func TestPolicyApproveDraftChunk(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastApproveReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastApproveReq.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "chunk-1", mock.lastApproveReq.GetChunkId())
|
||||
assert.Equal(t, "token-1", mock.lastApproveReq.GetReviewToken())
|
||||
mock.mu.Unlock()
|
||||
@@ -358,7 +358,7 @@ func TestPolicyRejectDraftChunk(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastRejectReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastRejectReq.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "chunk-2", mock.lastRejectReq.GetChunkId())
|
||||
assert.Equal(t, "too broad", mock.lastRejectReq.GetReason())
|
||||
mock.mu.Unlock()
|
||||
@@ -400,7 +400,7 @@ func TestPolicyApproveAllDraftChunks(t *testing.T) {
|
||||
|
||||
// Verify default: security-flagged NOT included.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastApproveAllReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastApproveAllReq.GetSandboxRef().GetName())
|
||||
assert.False(t, mock.lastApproveAllReq.GetIncludeSecurityFlagged())
|
||||
mock.mu.Unlock()
|
||||
|
||||
@@ -466,7 +466,7 @@ func TestPolicyClearDraftChunks(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastClearReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastClearReq.GetSandboxRef().GetName())
|
||||
mock.mu.Unlock()
|
||||
|
||||
assert.Equal(t, uint32(4), result.ChunksCleared)
|
||||
@@ -517,7 +517,7 @@ func TestPolicyGetDraftHistory(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastHistoryReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastHistoryReq.GetSandboxRef().GetName())
|
||||
mock.mu.Unlock()
|
||||
|
||||
assert.Equal(t, "approved", entries[0].EventType)
|
||||
@@ -583,7 +583,7 @@ func TestPolicyGetStatus(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded (no version = latest).
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetSandboxRef().GetName())
|
||||
assert.Equal(t, uint32(0), mock.lastStatusReq.GetVersion())
|
||||
mock.mu.Unlock()
|
||||
|
||||
@@ -649,8 +649,7 @@ func TestPolicyGetStatus_WithGlobal(t *testing.T) {
|
||||
// Verify global flag was forwarded in the proto request.
|
||||
mock.mu.Lock()
|
||||
assert.True(t, mock.lastStatusReq.GetGlobal())
|
||||
assert.Empty(t, mock.lastStatusReq.GetName())
|
||||
assert.Nil(t, mock.lastStatusReq.GetWorkspaceScope())
|
||||
assert.Nil(t, mock.lastStatusReq.GetSandboxRef())
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -675,8 +674,7 @@ func TestPolicyGetStatus_WithGlobalIgnoresNonEmptyName(t *testing.T) {
|
||||
|
||||
mock.mu.Lock()
|
||||
assert.True(t, mock.lastStatusReq.GetGlobal())
|
||||
assert.Equal(t, "some-sandbox", mock.lastStatusReq.GetName())
|
||||
assert.Nil(t, mock.lastStatusReq.GetWorkspaceScope())
|
||||
assert.Nil(t, mock.lastStatusReq.GetSandboxRef())
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -731,8 +729,8 @@ func TestPolicyGetStatus_WithoutGlobal_PreservesExistingBehavior(t *testing.T) {
|
||||
// Verify global flag is false by default.
|
||||
mock.mu.Lock()
|
||||
assert.False(t, mock.lastStatusReq.GetGlobal())
|
||||
assert.Equal(t, "default", mock.lastStatusReq.GetWorkspaceScope().GetWorkspace())
|
||||
assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetName())
|
||||
assert.Equal(t, "default", mock.lastStatusReq.GetSandboxRef().GetWorkspaceScope().GetWorkspace())
|
||||
assert.Equal(t, "my-sandbox", mock.lastStatusReq.GetSandboxRef().GetName())
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -773,14 +771,15 @@ func TestPolicyList(t *testing.T) {
|
||||
client, cleanup := setupPolicyTest(t, mock)
|
||||
defer cleanup()
|
||||
|
||||
revisions, err := client.List(context.Background(), "default")
|
||||
revisions, err := client.List(context.Background(), "default", "my-sandbox")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 2)
|
||||
|
||||
// Verify request was forwarded (no pagination options).
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "default", mock.lastListReq.GetWorkspaceScope().GetWorkspace())
|
||||
assert.Equal(t, "my-sandbox", mock.lastListReq.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "default", mock.lastListReq.GetSandboxRef().GetWorkspaceScope().GetWorkspace())
|
||||
assert.Equal(t, uint32(0), mock.lastListReq.GetLimit())
|
||||
assert.Equal(t, uint32(0), mock.lastListReq.GetOffset())
|
||||
mock.mu.Unlock()
|
||||
@@ -805,7 +804,7 @@ func TestPolicyList_WithPagination(t *testing.T) {
|
||||
client, cleanup := setupPolicyTest(t, mock)
|
||||
defer cleanup()
|
||||
|
||||
revisions, err := client.List(context.Background(), "default",
|
||||
revisions, err := client.List(context.Background(), "default", "my-sandbox",
|
||||
types.WithLimit(10),
|
||||
types.WithOffset(20),
|
||||
)
|
||||
@@ -827,7 +826,7 @@ func TestPolicyList_Empty(t *testing.T) {
|
||||
client, cleanup := setupPolicyTest(t, mock)
|
||||
defer cleanup()
|
||||
|
||||
revisions, err := client.List(context.Background(), "default")
|
||||
revisions, err := client.List(context.Background(), "default", "my-sandbox")
|
||||
|
||||
require.NoError(t, err)
|
||||
assert.Nil(t, revisions)
|
||||
@@ -845,7 +844,7 @@ func TestPolicyList_WithGlobal(t *testing.T) {
|
||||
defer cleanup()
|
||||
|
||||
// List with global flag and empty workspace.
|
||||
revisions, err := client.List(context.Background(), "", types.WithListGlobal(true))
|
||||
revisions, err := client.List(context.Background(), "", "", types.WithListGlobal(true))
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 1)
|
||||
@@ -855,7 +854,7 @@ func TestPolicyList_WithGlobal(t *testing.T) {
|
||||
// Verify global flag was forwarded in the proto request.
|
||||
mock.mu.Lock()
|
||||
assert.True(t, mock.lastListReq.GetGlobal())
|
||||
assert.Nil(t, mock.lastListReq.GetWorkspaceScope())
|
||||
assert.Nil(t, mock.lastListReq.GetSandboxRef())
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -870,14 +869,14 @@ func TestPolicyList_WithGlobalIgnoresWorkspace(t *testing.T) {
|
||||
client, cleanup := setupPolicyTest(t, mock)
|
||||
defer cleanup()
|
||||
|
||||
revisions, err := client.List(context.Background(), "some-workspace", types.WithListGlobal(true))
|
||||
revisions, err := client.List(context.Background(), "some-workspace", "", types.WithListGlobal(true))
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 1)
|
||||
|
||||
mock.mu.Lock()
|
||||
assert.True(t, mock.lastListReq.GetGlobal())
|
||||
assert.Nil(t, mock.lastListReq.GetWorkspaceScope())
|
||||
assert.Nil(t, mock.lastListReq.GetSandboxRef())
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -893,7 +892,7 @@ func TestPolicyList_WithGlobalAndPagination(t *testing.T) {
|
||||
defer cleanup()
|
||||
|
||||
// Global flag composes with pagination options.
|
||||
revisions, err := client.List(context.Background(), "",
|
||||
revisions, err := client.List(context.Background(), "", "",
|
||||
types.WithListGlobal(true),
|
||||
types.WithLimit(10),
|
||||
types.WithOffset(20),
|
||||
@@ -920,7 +919,7 @@ func TestPolicyList_WithoutGlobal_PreservesExistingBehavior(t *testing.T) {
|
||||
client, cleanup := setupPolicyTest(t, mock)
|
||||
defer cleanup()
|
||||
|
||||
revisions, err := client.List(context.Background(), "default")
|
||||
revisions, err := client.List(context.Background(), "default", "my-sandbox")
|
||||
|
||||
require.NoError(t, err)
|
||||
require.Len(t, revisions, 1)
|
||||
@@ -928,7 +927,8 @@ func TestPolicyList_WithoutGlobal_PreservesExistingBehavior(t *testing.T) {
|
||||
// Verify global flag is false by default.
|
||||
mock.mu.Lock()
|
||||
assert.False(t, mock.lastListReq.GetGlobal())
|
||||
assert.Equal(t, "default", mock.lastListReq.GetWorkspaceScope().GetWorkspace())
|
||||
assert.Equal(t, "my-sandbox", mock.lastListReq.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "default", mock.lastListReq.GetSandboxRef().GetWorkspaceScope().GetWorkspace())
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -939,7 +939,7 @@ func TestPolicyList_Error(t *testing.T) {
|
||||
client, cleanup := setupPolicyTest(t, mock)
|
||||
defer cleanup()
|
||||
|
||||
revisions, err := client.List(context.Background(), "default")
|
||||
revisions, err := client.List(context.Background(), "default", "my-sandbox")
|
||||
|
||||
assert.Nil(t, revisions)
|
||||
require.Error(t, err)
|
||||
@@ -966,7 +966,7 @@ func TestPolicyEditDraftChunk(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastEditReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastEditReq.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "chunk-1", mock.lastEditReq.GetChunkId())
|
||||
require.NotNil(t, mock.lastEditReq.GetProposedRule())
|
||||
assert.Equal(t, "allow-https", mock.lastEditReq.GetProposedRule().GetName())
|
||||
@@ -1005,7 +1005,7 @@ func TestPolicyUndoDraftChunk(t *testing.T) {
|
||||
|
||||
// Verify request was forwarded.
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "my-sandbox", mock.lastUndoReq.GetName())
|
||||
assert.Equal(t, "my-sandbox", mock.lastUndoReq.GetSandboxRef().GetName())
|
||||
assert.Equal(t, "chunk-3", mock.lastUndoReq.GetChunkId())
|
||||
mock.mu.Unlock()
|
||||
|
||||
|
||||
@@ -89,8 +89,7 @@ func validateTemplateCreateSpec(spec *SandboxSpec) error {
|
||||
|
||||
func (s *sandboxClient) Get(ctx context.Context, workspace, name string) (*Sandbox, error) {
|
||||
resp, err := s.client.GetSandbox(ctx, &pb.GetSandboxRequest{
|
||||
Name: name,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, name),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -136,8 +135,7 @@ func (s *sandboxClient) list(ctx context.Context, req *pb.ListSandboxesRequest,
|
||||
|
||||
func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) error {
|
||||
_, err := s.client.DeleteSandbox(ctx, &pb.DeleteSandboxRequest{
|
||||
Name: name,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, name),
|
||||
})
|
||||
if err != nil {
|
||||
return converter.FromGRPCError(err)
|
||||
@@ -147,8 +145,7 @@ func (s *sandboxClient) Delete(ctx context.Context, workspace, name string) erro
|
||||
|
||||
func (s *sandboxClient) Stop(ctx context.Context, workspace, name string) (*Sandbox, error) {
|
||||
resp, err := s.client.StopSandbox(ctx, &pb.StopSandboxRequest{
|
||||
Name: name,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, name),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -158,8 +155,7 @@ func (s *sandboxClient) Stop(ctx context.Context, workspace, name string) (*Sand
|
||||
|
||||
func (s *sandboxClient) Start(ctx context.Context, workspace, name string) (*Sandbox, error) {
|
||||
resp, err := s.client.StartSandbox(ctx, &pb.StartSandboxRequest{
|
||||
Name: name,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, name),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -169,10 +165,9 @@ func (s *sandboxClient) Start(ctx context.Context, workspace, name string) (*San
|
||||
|
||||
func (s *sandboxClient) AttachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*AttachProviderResult, error) {
|
||||
resp, err := s.client.AttachSandboxProvider(ctx, &pb.AttachSandboxProviderRequest{
|
||||
SandboxName: sandboxName,
|
||||
ProviderName: providerName,
|
||||
ExpectedResourceVersion: expectedResourceVersion,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -185,10 +180,9 @@ func (s *sandboxClient) AttachProvider(ctx context.Context, workspace, sandboxNa
|
||||
|
||||
func (s *sandboxClient) DetachProvider(ctx context.Context, workspace, sandboxName, providerName string, expectedResourceVersion uint64) (*DetachProviderResult, error) {
|
||||
resp, err := s.client.DetachSandboxProvider(ctx, &pb.DetachSandboxProviderRequest{
|
||||
SandboxName: sandboxName,
|
||||
ProviderName: providerName,
|
||||
ExpectedResourceVersion: expectedResourceVersion,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -201,8 +195,7 @@ func (s *sandboxClient) DetachProvider(ctx context.Context, workspace, sandboxNa
|
||||
|
||||
func (s *sandboxClient) ListProviders(ctx context.Context, workspace, sandboxName string) ([]*Provider, error) {
|
||||
resp, err := s.client.ListSandboxProviders(ctx, &pb.ListSandboxProvidersRequest{
|
||||
SandboxName: sandboxName,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -295,7 +288,7 @@ func (s *sandboxClient) Watch(ctx context.Context, workspace, name string, opts
|
||||
|
||||
streamCtx, streamCancel := context.WithCancel(ctx)
|
||||
stream, err := s.client.WatchSandbox(streamCtx, &pb.WatchSandboxRequest{
|
||||
Id: sb.ID,
|
||||
SandboxRef: sandboxReferenceByID(sb.ID),
|
||||
FollowStatus: true,
|
||||
StopOnTerminal: watchOpts.StopOnTerminal,
|
||||
})
|
||||
@@ -368,11 +361,10 @@ func (s *sandboxClient) GetLogs(ctx context.Context, workspace, sandboxName stri
|
||||
|
||||
cfg := types.ApplyLogOptions(opts)
|
||||
req := &pb.GetSandboxLogsRequest{
|
||||
SandboxId: sb.ID,
|
||||
Lines: cfg.Lines(),
|
||||
Sources: cfg.Sources(),
|
||||
MinLevel: cfg.MinLevel(),
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByID(sb.ID),
|
||||
Lines: cfg.Lines(),
|
||||
Sources: cfg.Sources(),
|
||||
MinLevel: cfg.MinLevel(),
|
||||
}
|
||||
if !cfg.Since().IsZero() {
|
||||
req.SinceMs = converter.MillisFromTime(cfg.Since())
|
||||
|
||||
@@ -54,6 +54,14 @@ func newMockSandboxServer() *mockSandboxServer {
|
||||
}
|
||||
}
|
||||
|
||||
func sandboxRefName(ref *dm.SandboxReference) string {
|
||||
return ref.GetName()
|
||||
}
|
||||
|
||||
func sandboxRefID(ref *dm.SandboxReference) string {
|
||||
return ref.GetId()
|
||||
}
|
||||
|
||||
func (s *mockSandboxServer) CreateSandbox(_ context.Context, req *pb.CreateSandboxRequest) (*pb.SandboxResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
@@ -82,9 +90,10 @@ func (s *mockSandboxServer) GetSandbox(_ context.Context, req *pb.GetSandboxRequ
|
||||
if s.getErr != nil {
|
||||
return nil, s.getErr
|
||||
}
|
||||
sb, ok := s.sandboxes[req.GetName()]
|
||||
name := sandboxRefName(req.GetSandboxRef())
|
||||
sb, ok := s.sandboxes[name]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName())
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name)
|
||||
}
|
||||
cloned := proto.Clone(sb).(*pb.Sandbox)
|
||||
return &pb.SandboxResponse{Sandbox: cloned}, nil
|
||||
@@ -117,20 +126,22 @@ func (s *mockSandboxServer) DeleteSandbox(_ context.Context, req *pb.DeleteSandb
|
||||
if s.deleteErr != nil {
|
||||
return nil, s.deleteErr
|
||||
}
|
||||
_, ok := s.sandboxes[req.GetName()]
|
||||
name := sandboxRefName(req.GetSandboxRef())
|
||||
_, ok := s.sandboxes[name]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName())
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name)
|
||||
}
|
||||
delete(s.sandboxes, req.GetName())
|
||||
delete(s.sandboxes, name)
|
||||
return &pb.DeleteSandboxResponse{Deleted: true}, nil
|
||||
}
|
||||
|
||||
func (s *mockSandboxServer) StopSandbox(_ context.Context, req *pb.StopSandboxRequest) (*pb.SandboxResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
sb, ok := s.sandboxes[req.GetName()]
|
||||
name := sandboxRefName(req.GetSandboxRef())
|
||||
sb, ok := s.sandboxes[name]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName())
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name)
|
||||
}
|
||||
sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_STOPPED
|
||||
return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil
|
||||
@@ -139,9 +150,10 @@ func (s *mockSandboxServer) StopSandbox(_ context.Context, req *pb.StopSandboxRe
|
||||
func (s *mockSandboxServer) StartSandbox(_ context.Context, req *pb.StartSandboxRequest) (*pb.SandboxResponse, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
sb, ok := s.sandboxes[req.GetName()]
|
||||
name := sandboxRefName(req.GetSandboxRef())
|
||||
sb, ok := s.sandboxes[name]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetName())
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name)
|
||||
}
|
||||
sb.Status.Phase = pb.SandboxPhase_SANDBOX_PHASE_STARTING
|
||||
return &pb.SandboxResponse{Sandbox: proto.Clone(sb).(*pb.Sandbox)}, nil
|
||||
@@ -153,9 +165,10 @@ func (s *mockSandboxServer) AttachSandboxProvider(_ context.Context, req *pb.Att
|
||||
if s.attachErr != nil {
|
||||
return nil, s.attachErr
|
||||
}
|
||||
sb, ok := s.sandboxes[req.GetSandboxName()]
|
||||
name := sandboxRefName(req.GetSandboxRef())
|
||||
sb, ok := s.sandboxes[name]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetSandboxName())
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name)
|
||||
}
|
||||
sb.Spec.Providers = append(sb.Spec.Providers, req.GetProviderName())
|
||||
return &pb.AttachSandboxProviderResponse{Sandbox: sb, Attached: true}, nil
|
||||
@@ -167,9 +180,10 @@ func (s *mockSandboxServer) DetachSandboxProvider(_ context.Context, req *pb.Det
|
||||
if s.detachErr != nil {
|
||||
return nil, s.detachErr
|
||||
}
|
||||
sb, ok := s.sandboxes[req.GetSandboxName()]
|
||||
name := sandboxRefName(req.GetSandboxRef())
|
||||
sb, ok := s.sandboxes[name]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", req.GetSandboxName())
|
||||
return nil, status.Errorf(codes.NotFound, "sandbox %q not found", name)
|
||||
}
|
||||
return &pb.DetachSandboxProviderResponse{Sandbox: sb, Detached: true}, nil
|
||||
}
|
||||
@@ -178,7 +192,7 @@ func (s *mockSandboxServer) ListSandboxProviders(_ context.Context, req *pb.List
|
||||
if s.listProvErr != nil {
|
||||
return nil, s.listProvErr
|
||||
}
|
||||
provs := s.providers[req.GetSandboxName()]
|
||||
provs := s.providers[sandboxRefName(req.GetSandboxRef())]
|
||||
return &pb.ListSandboxProvidersResponse{Providers: provs}, nil
|
||||
}
|
||||
|
||||
@@ -945,12 +959,12 @@ func TestSandboxWatch_ResolvesNameToID(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
defer w.Stop()
|
||||
|
||||
// Verify the WatchSandboxRequest.Id contains the resolved ID, not the name
|
||||
// Verify the WatchSandboxRequest reference contains the resolved ID, not the name.
|
||||
mock.mu.Lock()
|
||||
req := mock.watchRequest
|
||||
mock.mu.Unlock()
|
||||
require.NotNil(t, req)
|
||||
assert.Equal(t, "resolved-id-123", req.GetId(), "Watch should send resolved sandbox ID, not the name")
|
||||
assert.Equal(t, "resolved-id-123", req.GetSandboxRef().GetId(), "Watch should send resolved sandbox ID, not the name")
|
||||
}
|
||||
|
||||
func TestSandboxWatch_ResolutionError(t *testing.T) {
|
||||
@@ -1161,7 +1175,7 @@ func TestSandboxGetLogs(t *testing.T) {
|
||||
|
||||
// Verify name→id resolution: the proto request should contain the sandbox ID
|
||||
mock.mu.Lock()
|
||||
assert.Equal(t, "sb-id-123", mock.getLogsRequest.GetSandboxId())
|
||||
assert.Equal(t, "sb-id-123", mock.getLogsRequest.GetSandboxRef().GetId())
|
||||
mock.mu.Unlock()
|
||||
}
|
||||
|
||||
@@ -1195,7 +1209,7 @@ func TestSandboxGetLogs_WithOptions(t *testing.T) {
|
||||
mock.mu.Lock()
|
||||
req := mock.getLogsRequest
|
||||
mock.mu.Unlock()
|
||||
assert.Equal(t, "sb-id-opts", req.GetSandboxId())
|
||||
assert.Equal(t, "sb-id-opts", req.GetSandboxRef().GetId())
|
||||
assert.Equal(t, uint32(50), req.GetLines())
|
||||
assert.Equal(t, since.UnixMilli(), req.GetSinceMs())
|
||||
assert.Equal(t, []string{"gateway", "sandbox"}, req.GetSources())
|
||||
|
||||
@@ -21,11 +21,10 @@ func newServiceClient(conn grpc.ClientConnInterface) *serviceClient {
|
||||
|
||||
func (s *serviceClient) Expose(ctx context.Context, workspace, sandboxName, serviceName string, targetPort uint32, domain bool) (*ServiceEndpoint, error) {
|
||||
resp, err := s.client.ExposeService(ctx, &pb.ExposeServiceRequest{
|
||||
Sandbox: sandboxName,
|
||||
Service: serviceName,
|
||||
TargetPort: targetPort,
|
||||
Domain: domain,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
Service: serviceName,
|
||||
TargetPort: targetPort,
|
||||
Domain: domain,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -35,9 +34,8 @@ func (s *serviceClient) Expose(ctx context.Context, workspace, sandboxName, serv
|
||||
|
||||
func (s *serviceClient) Get(ctx context.Context, workspace, sandboxName, serviceName string) (*ServiceEndpoint, error) {
|
||||
resp, err := s.client.GetService(ctx, &pb.GetServiceRequest{
|
||||
Sandbox: sandboxName,
|
||||
Service: serviceName,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
Service: serviceName,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -46,9 +44,10 @@ func (s *serviceClient) Get(ctx context.Context, workspace, sandboxName, service
|
||||
}
|
||||
|
||||
func (s *serviceClient) List(ctx context.Context, workspace, sandboxName string, opts ...ListOptions) ([]*ServiceEndpoint, error) {
|
||||
req := &pb.ListServicesRequest{
|
||||
Sandbox: sandboxName,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
req := &pb.ListServicesRequest{WorkspaceScope: namedWorkspaceScope(workspace)}
|
||||
if sandboxName != "" {
|
||||
req.SandboxRef = sandboxReferenceByName(workspace, sandboxName)
|
||||
req.WorkspaceScope = nil
|
||||
}
|
||||
return s.list(ctx, req, opts...)
|
||||
}
|
||||
@@ -83,9 +82,8 @@ func (s *serviceClient) list(ctx context.Context, req *pb.ListServicesRequest, o
|
||||
|
||||
func (s *serviceClient) Delete(ctx context.Context, workspace, sandboxName, serviceName string) error {
|
||||
_, err := s.client.DeleteService(ctx, &pb.DeleteServiceRequest{
|
||||
Sandbox: sandboxName,
|
||||
Service: serviceName,
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
SandboxRef: sandboxReferenceByName(workspace, sandboxName),
|
||||
Service: serviceName,
|
||||
})
|
||||
if err != nil {
|
||||
return converter.FromGRPCError(err)
|
||||
|
||||
@@ -55,7 +55,7 @@ func (s *mockServiceServer) ExposeService(_ context.Context, req *pb.ExposeServi
|
||||
Metadata: &dm.ObjectMeta{
|
||||
Id: "ep-" + req.GetService(),
|
||||
},
|
||||
SandboxName: req.GetSandbox(),
|
||||
SandboxName: sandboxRefName(req.GetSandboxRef()),
|
||||
ServiceName: req.GetService(),
|
||||
TargetPort: req.GetTargetPort(),
|
||||
Domain: req.GetDomain(),
|
||||
@@ -65,7 +65,7 @@ func (s *mockServiceServer) ExposeService(_ context.Context, req *pb.ExposeServi
|
||||
resp.Url = "https://" + req.GetService() + ".example.com"
|
||||
}
|
||||
|
||||
s.endpoints[serviceKey(req.GetSandbox(), req.GetService())] = resp
|
||||
s.endpoints[serviceKey(sandboxRefName(req.GetSandboxRef()), req.GetService())] = resp
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -76,9 +76,10 @@ func (s *mockServiceServer) GetService(_ context.Context, req *pb.GetServiceRequ
|
||||
return nil, s.getErr
|
||||
}
|
||||
|
||||
ep, ok := s.endpoints[serviceKey(req.GetSandbox(), req.GetService())]
|
||||
sandboxName := sandboxRefName(req.GetSandboxRef())
|
||||
ep, ok := s.endpoints[serviceKey(sandboxName, req.GetService())]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), req.GetSandbox())
|
||||
return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), sandboxName)
|
||||
}
|
||||
return ep, nil
|
||||
}
|
||||
@@ -93,8 +94,9 @@ func (s *mockServiceServer) ListServices(_ context.Context, req *pb.ListServices
|
||||
|
||||
var services []*pb.ServiceEndpointResponse
|
||||
for key, ep := range s.endpoints {
|
||||
prefix := req.GetSandbox() + "/"
|
||||
if req.GetSandbox() == "" || (len(key) >= len(prefix) && key[:len(prefix)] == prefix) {
|
||||
sandboxName := sandboxRefName(req.GetSandboxRef())
|
||||
prefix := sandboxName + "/"
|
||||
if sandboxName == "" || (len(key) >= len(prefix) && key[:len(prefix)] == prefix) {
|
||||
services = append(services, ep)
|
||||
}
|
||||
}
|
||||
@@ -108,10 +110,11 @@ func (s *mockServiceServer) DeleteService(_ context.Context, req *pb.DeleteServi
|
||||
return nil, s.deleteErr
|
||||
}
|
||||
|
||||
key := serviceKey(req.GetSandbox(), req.GetService())
|
||||
sandboxName := sandboxRefName(req.GetSandboxRef())
|
||||
key := serviceKey(sandboxName, req.GetService())
|
||||
_, ok := s.endpoints[key]
|
||||
if !ok {
|
||||
return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), req.GetSandbox())
|
||||
return nil, status.Errorf(codes.NotFound, "service %q not found in sandbox %q", req.GetService(), sandboxName)
|
||||
}
|
||||
delete(s.endpoints, key)
|
||||
return &pb.DeleteServiceResponse{Deleted: true}, nil
|
||||
@@ -278,7 +281,7 @@ func TestServiceListAll_SelectsAllWorkspaces(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, endpoints)
|
||||
require.NotNil(t, mock.lastList)
|
||||
assert.Empty(t, mock.lastList.GetSandbox())
|
||||
assert.Nil(t, mock.lastList.GetSandboxRef())
|
||||
assert.NotNil(t, mock.lastList.GetWorkspaceScope().GetAllWorkspaces())
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ func newSSHClient(conn grpc.ClientConnInterface, sandboxes SandboxInterface) *ss
|
||||
|
||||
func (s *sshClient) CreateSession(ctx context.Context, _, sandboxID string) (*SSHSession, error) {
|
||||
resp, err := s.client.CreateSshSession(ctx, &pb.CreateSshSessionRequest{
|
||||
SandboxId: sandboxID,
|
||||
SandboxRef: sandboxReferenceByID(sandboxID),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, converter.FromGRPCError(err)
|
||||
@@ -94,7 +94,7 @@ func (s *sshClient) Tunnel(ctx context.Context, workspace, sandboxName string, p
|
||||
initFrame := &pb.TcpForwardFrame{
|
||||
Payload: &pb.TcpForwardFrame_Init{
|
||||
Init: &pb.TcpForwardInit{
|
||||
SandboxId: sandbox.ID,
|
||||
SandboxRef: sandboxReferenceByID(sandbox.ID),
|
||||
ServiceId: cfg.serviceID,
|
||||
AuthorizationToken: session.Token,
|
||||
Target: &pb.TcpForwardInit_Ssh{
|
||||
|
||||
@@ -51,13 +51,14 @@ func (s *mockSSHServer) CreateSshSession(_ context.Context, req *pb.CreateSshSes
|
||||
return nil, s.createErr
|
||||
}
|
||||
|
||||
token := "tok-" + req.GetSandboxId()
|
||||
sandboxID := sandboxRefID(req.GetSandboxRef())
|
||||
token := "tok-" + sandboxID
|
||||
if s.nextToken != "" {
|
||||
token = s.nextToken
|
||||
}
|
||||
|
||||
resp := &pb.CreateSshSessionResponse{
|
||||
SandboxId: req.GetSandboxId(),
|
||||
SandboxId: sandboxID,
|
||||
Token: token,
|
||||
GatewayHost: "gw.example.com",
|
||||
GatewayPort: 2222,
|
||||
@@ -65,7 +66,7 @@ func (s *mockSSHServer) CreateSshSession(_ context.Context, req *pb.CreateSshSes
|
||||
HostKeyFingerprint: "SHA256:abc123",
|
||||
ExpiresAtMs: 1700000000000,
|
||||
}
|
||||
s.sessions[req.GetSandboxId()] = resp
|
||||
s.sessions[sandboxID] = resp
|
||||
s.tokens[token] = true
|
||||
return resp, nil
|
||||
}
|
||||
@@ -330,7 +331,7 @@ func TestSSHTunnel_Success(t *testing.T) {
|
||||
mock.mu.Unlock()
|
||||
|
||||
require.NotNil(t, init)
|
||||
assert.Equal(t, "sb-123", init.GetSandboxId())
|
||||
assert.Equal(t, "sb-123", init.GetSandboxRef().GetId())
|
||||
assert.NotEmpty(t, init.GetAuthorizationToken())
|
||||
assert.NotNil(t, init.GetSsh(), "target should be SshRelayTarget")
|
||||
}
|
||||
|
||||
@@ -56,8 +56,8 @@ func (t *tcpClient) Forward(ctx context.Context, workspace, sandboxName string,
|
||||
initFrame := &pb.TcpForwardFrame{
|
||||
Payload: &pb.TcpForwardFrame_Init{
|
||||
Init: &pb.TcpForwardInit{
|
||||
SandboxId: sb.ID,
|
||||
ServiceId: cfg.serviceID,
|
||||
SandboxRef: sandboxReferenceByID(sb.ID),
|
||||
ServiceId: cfg.serviceID,
|
||||
Target: &pb.TcpForwardInit_Tcp{
|
||||
Tcp: &pb.TcpRelayTarget{
|
||||
Host: "127.0.0.1",
|
||||
|
||||
@@ -130,7 +130,7 @@ func TestTCPForward_InitFrame(t *testing.T) {
|
||||
mock.mu.Unlock()
|
||||
|
||||
require.NotNil(t, init)
|
||||
assert.Equal(t, "sb-my-sandbox", init.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", init.GetSandboxRef().GetId())
|
||||
assert.Empty(t, init.GetServiceId(), "service_id should be empty per FR-007a")
|
||||
assert.Empty(t, init.GetAuthorizationToken())
|
||||
|
||||
@@ -336,7 +336,7 @@ func TestTCPForward_WithServiceID(t *testing.T) {
|
||||
|
||||
require.NotNil(t, init)
|
||||
assert.Equal(t, "audit-svc", init.GetServiceId())
|
||||
assert.Equal(t, "sb-my-sandbox", init.GetSandboxId())
|
||||
assert.Equal(t, "sb-my-sandbox", init.GetSandboxRef().GetId())
|
||||
}
|
||||
|
||||
func TestTCPForward_WithoutOptions_BackwardCompat(t *testing.T) {
|
||||
@@ -416,7 +416,7 @@ func TestTCPForward_ResolvesNameToID(t *testing.T) {
|
||||
|
||||
require.NotNil(t, init)
|
||||
// stubSandboxResolver returns ID "sb-<name>" — verify the proto has the resolved ID, not the name
|
||||
assert.Equal(t, "sb-my-sandbox", init.GetSandboxId(), "Forward should send resolved sandbox ID, not the name")
|
||||
assert.Equal(t, "sb-my-sandbox", init.GetSandboxRef().GetId(), "Forward should send resolved sandbox ID, not the name")
|
||||
}
|
||||
|
||||
func TestTCPForward_ResolutionError(t *testing.T) {
|
||||
|
||||
@@ -16,3 +16,16 @@ func allWorkspacesScope() *dm.WorkspaceSelector {
|
||||
Selection: &dm.WorkspaceSelector_AllWorkspaces{AllWorkspaces: &dm.AllWorkspaces{}},
|
||||
}
|
||||
}
|
||||
|
||||
func sandboxReferenceByName(workspace, name string) *dm.SandboxReference {
|
||||
return &dm.SandboxReference{
|
||||
Identifier: &dm.SandboxReference_Name{Name: name},
|
||||
WorkspaceScope: namedWorkspaceScope(workspace),
|
||||
}
|
||||
}
|
||||
|
||||
func sandboxReferenceByID(id string) *dm.SandboxReference {
|
||||
return &dm.SandboxReference{
|
||||
Identifier: &dm.SandboxReference_Id{Id: id},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -204,6 +204,102 @@ func (*AllWorkspaces) Descriptor() ([]byte, []int) {
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
// Identifies one existing sandbox in the public API.
|
||||
//
|
||||
// Exactly one identifier is required. `id` is the immutable, globally unique
|
||||
// gateway-generated ObjectMeta.id and MUST NOT include workspace_scope. `name`
|
||||
// is the human-readable ObjectMeta.name and requires a named workspace_scope;
|
||||
// all_workspaces is never valid for a sandbox reference.
|
||||
type SandboxReference struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// Types that are valid to be assigned to Identifier:
|
||||
//
|
||||
// *SandboxReference_Id
|
||||
// *SandboxReference_Name
|
||||
Identifier isSandboxReference_Identifier `protobuf_oneof:"identifier"`
|
||||
WorkspaceScope *WorkspaceSelector `protobuf:"bytes,3,opt,name=workspace_scope,json=workspaceScope,proto3" json:"workspace_scope,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
||||
func (x *SandboxReference) Reset() {
|
||||
*x = SandboxReference{}
|
||||
mi := &file_datamodel_proto_msgTypes[2]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
|
||||
func (x *SandboxReference) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*SandboxReference) ProtoMessage() {}
|
||||
|
||||
func (x *SandboxReference) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_datamodel_proto_msgTypes[2]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use SandboxReference.ProtoReflect.Descriptor instead.
|
||||
func (*SandboxReference) Descriptor() ([]byte, []int) {
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
func (x *SandboxReference) GetIdentifier() isSandboxReference_Identifier {
|
||||
if x != nil {
|
||||
return x.Identifier
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (x *SandboxReference) GetId() string {
|
||||
if x != nil {
|
||||
if x, ok := x.Identifier.(*SandboxReference_Id); ok {
|
||||
return x.Id
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SandboxReference) GetName() string {
|
||||
if x != nil {
|
||||
if x, ok := x.Identifier.(*SandboxReference_Name); ok {
|
||||
return x.Name
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *SandboxReference) GetWorkspaceScope() *WorkspaceSelector {
|
||||
if x != nil {
|
||||
return x.WorkspaceScope
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type isSandboxReference_Identifier interface {
|
||||
isSandboxReference_Identifier()
|
||||
}
|
||||
|
||||
type SandboxReference_Id struct {
|
||||
Id string `protobuf:"bytes,1,opt,name=id,proto3,oneof"`
|
||||
}
|
||||
|
||||
type SandboxReference_Name struct {
|
||||
Name string `protobuf:"bytes,2,opt,name=name,proto3,oneof"`
|
||||
}
|
||||
|
||||
func (*SandboxReference_Id) isSandboxReference_Identifier() {}
|
||||
|
||||
func (*SandboxReference_Name) isSandboxReference_Identifier() {}
|
||||
|
||||
// Kubernetes-style metadata shared by all top-level OpenShell domain objects.
|
||||
//
|
||||
// This structure provides consistent metadata (identity, labels, annotations,
|
||||
@@ -239,7 +335,7 @@ type ObjectMeta struct {
|
||||
|
||||
func (x *ObjectMeta) Reset() {
|
||||
*x = ObjectMeta{}
|
||||
mi := &file_datamodel_proto_msgTypes[2]
|
||||
mi := &file_datamodel_proto_msgTypes[3]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -251,7 +347,7 @@ func (x *ObjectMeta) String() string {
|
||||
func (*ObjectMeta) ProtoMessage() {}
|
||||
|
||||
func (x *ObjectMeta) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_datamodel_proto_msgTypes[2]
|
||||
mi := &file_datamodel_proto_msgTypes[3]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -264,7 +360,7 @@ func (x *ObjectMeta) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use ObjectMeta.ProtoReflect.Descriptor instead.
|
||||
func (*ObjectMeta) Descriptor() ([]byte, []int) {
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{2}
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
func (x *ObjectMeta) GetId() string {
|
||||
@@ -333,7 +429,7 @@ type WorkspaceStatus struct {
|
||||
|
||||
func (x *WorkspaceStatus) Reset() {
|
||||
*x = WorkspaceStatus{}
|
||||
mi := &file_datamodel_proto_msgTypes[3]
|
||||
mi := &file_datamodel_proto_msgTypes[4]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -345,7 +441,7 @@ func (x *WorkspaceStatus) String() string {
|
||||
func (*WorkspaceStatus) ProtoMessage() {}
|
||||
|
||||
func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_datamodel_proto_msgTypes[3]
|
||||
mi := &file_datamodel_proto_msgTypes[4]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -358,7 +454,7 @@ func (x *WorkspaceStatus) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use WorkspaceStatus.ProtoReflect.Descriptor instead.
|
||||
func (*WorkspaceStatus) Descriptor() ([]byte, []int) {
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{3}
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
func (x *WorkspaceStatus) GetPhase() WorkspacePhase {
|
||||
@@ -384,7 +480,7 @@ type Workspace struct {
|
||||
|
||||
func (x *Workspace) Reset() {
|
||||
*x = Workspace{}
|
||||
mi := &file_datamodel_proto_msgTypes[4]
|
||||
mi := &file_datamodel_proto_msgTypes[5]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -396,7 +492,7 @@ func (x *Workspace) String() string {
|
||||
func (*Workspace) ProtoMessage() {}
|
||||
|
||||
func (x *Workspace) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_datamodel_proto_msgTypes[4]
|
||||
mi := &file_datamodel_proto_msgTypes[5]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -409,7 +505,7 @@ func (x *Workspace) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Workspace.ProtoReflect.Descriptor instead.
|
||||
func (*Workspace) Descriptor() ([]byte, []int) {
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{4}
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{5}
|
||||
}
|
||||
|
||||
func (x *Workspace) GetMetadata() *ObjectMeta {
|
||||
@@ -442,7 +538,7 @@ type CredentialHandle struct {
|
||||
|
||||
func (x *CredentialHandle) Reset() {
|
||||
*x = CredentialHandle{}
|
||||
mi := &file_datamodel_proto_msgTypes[5]
|
||||
mi := &file_datamodel_proto_msgTypes[6]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -454,7 +550,7 @@ func (x *CredentialHandle) String() string {
|
||||
func (*CredentialHandle) ProtoMessage() {}
|
||||
|
||||
func (x *CredentialHandle) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_datamodel_proto_msgTypes[5]
|
||||
mi := &file_datamodel_proto_msgTypes[6]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -467,7 +563,7 @@ func (x *CredentialHandle) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use CredentialHandle.ProtoReflect.Descriptor instead.
|
||||
func (*CredentialHandle) Descriptor() ([]byte, []int) {
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{5}
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{6}
|
||||
}
|
||||
|
||||
func (x *CredentialHandle) GetDriver() string {
|
||||
@@ -518,7 +614,7 @@ type Provider struct {
|
||||
|
||||
func (x *Provider) Reset() {
|
||||
*x = Provider{}
|
||||
mi := &file_datamodel_proto_msgTypes[6]
|
||||
mi := &file_datamodel_proto_msgTypes[7]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
@@ -530,7 +626,7 @@ func (x *Provider) String() string {
|
||||
func (*Provider) ProtoMessage() {}
|
||||
|
||||
func (x *Provider) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_datamodel_proto_msgTypes[6]
|
||||
mi := &file_datamodel_proto_msgTypes[7]
|
||||
if x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
@@ -543,7 +639,7 @@ func (x *Provider) ProtoReflect() protoreflect.Message {
|
||||
|
||||
// Deprecated: Use Provider.ProtoReflect.Descriptor instead.
|
||||
func (*Provider) Descriptor() ([]byte, []int) {
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{6}
|
||||
return file_datamodel_proto_rawDescGZIP(), []int{7}
|
||||
}
|
||||
|
||||
func (x *Provider) GetMetadata() *ObjectMeta {
|
||||
@@ -604,7 +700,13 @@ const file_datamodel_proto_rawDesc = "" +
|
||||
"\tworkspace\x18\x01 \x01(\tH\x00R\tworkspace\x12N\n" +
|
||||
"\x0eall_workspaces\x18\x02 \x01(\v2%.openshell.datamodel.v1.AllWorkspacesH\x00R\rallWorkspacesB\v\n" +
|
||||
"\tselection\"\x0f\n" +
|
||||
"\rAllWorkspaces\"\xeb\x03\n" +
|
||||
"\rAllWorkspaces\"\x9c\x01\n" +
|
||||
"\x10SandboxReference\x12\x10\n" +
|
||||
"\x02id\x18\x01 \x01(\tH\x00R\x02id\x12\x14\n" +
|
||||
"\x04name\x18\x02 \x01(\tH\x00R\x04name\x12R\n" +
|
||||
"\x0fworkspace_scope\x18\x03 \x01(\v2).openshell.datamodel.v1.WorkspaceSelectorR\x0eworkspaceScopeB\f\n" +
|
||||
"\n" +
|
||||
"identifier\"\xeb\x03\n" +
|
||||
"\n" +
|
||||
"ObjectMeta\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12\x12\n" +
|
||||
@@ -671,43 +773,45 @@ func file_datamodel_proto_rawDescGZIP() []byte {
|
||||
}
|
||||
|
||||
var file_datamodel_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||
var file_datamodel_proto_msgTypes = make([]protoimpl.MessageInfo, 14)
|
||||
var file_datamodel_proto_msgTypes = make([]protoimpl.MessageInfo, 15)
|
||||
var file_datamodel_proto_goTypes = []any{
|
||||
(WorkspacePhase)(0), // 0: openshell.datamodel.v1.WorkspacePhase
|
||||
(*WorkspaceSelector)(nil), // 1: openshell.datamodel.v1.WorkspaceSelector
|
||||
(*AllWorkspaces)(nil), // 2: openshell.datamodel.v1.AllWorkspaces
|
||||
(*ObjectMeta)(nil), // 3: openshell.datamodel.v1.ObjectMeta
|
||||
(*WorkspaceStatus)(nil), // 4: openshell.datamodel.v1.WorkspaceStatus
|
||||
(*Workspace)(nil), // 5: openshell.datamodel.v1.Workspace
|
||||
(*CredentialHandle)(nil), // 6: openshell.datamodel.v1.CredentialHandle
|
||||
(*Provider)(nil), // 7: openshell.datamodel.v1.Provider
|
||||
nil, // 8: openshell.datamodel.v1.ObjectMeta.LabelsEntry
|
||||
nil, // 9: openshell.datamodel.v1.ObjectMeta.AnnotationsEntry
|
||||
nil, // 10: openshell.datamodel.v1.CredentialHandle.MetadataEntry
|
||||
nil, // 11: openshell.datamodel.v1.Provider.CredentialsEntry
|
||||
nil, // 12: openshell.datamodel.v1.Provider.ConfigEntry
|
||||
nil, // 13: openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry
|
||||
nil, // 14: openshell.datamodel.v1.Provider.CredentialHandlesEntry
|
||||
(*SandboxReference)(nil), // 3: openshell.datamodel.v1.SandboxReference
|
||||
(*ObjectMeta)(nil), // 4: openshell.datamodel.v1.ObjectMeta
|
||||
(*WorkspaceStatus)(nil), // 5: openshell.datamodel.v1.WorkspaceStatus
|
||||
(*Workspace)(nil), // 6: openshell.datamodel.v1.Workspace
|
||||
(*CredentialHandle)(nil), // 7: openshell.datamodel.v1.CredentialHandle
|
||||
(*Provider)(nil), // 8: openshell.datamodel.v1.Provider
|
||||
nil, // 9: openshell.datamodel.v1.ObjectMeta.LabelsEntry
|
||||
nil, // 10: openshell.datamodel.v1.ObjectMeta.AnnotationsEntry
|
||||
nil, // 11: openshell.datamodel.v1.CredentialHandle.MetadataEntry
|
||||
nil, // 12: openshell.datamodel.v1.Provider.CredentialsEntry
|
||||
nil, // 13: openshell.datamodel.v1.Provider.ConfigEntry
|
||||
nil, // 14: openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry
|
||||
nil, // 15: openshell.datamodel.v1.Provider.CredentialHandlesEntry
|
||||
}
|
||||
var file_datamodel_proto_depIdxs = []int32{
|
||||
2, // 0: openshell.datamodel.v1.WorkspaceSelector.all_workspaces:type_name -> openshell.datamodel.v1.AllWorkspaces
|
||||
8, // 1: openshell.datamodel.v1.ObjectMeta.labels:type_name -> openshell.datamodel.v1.ObjectMeta.LabelsEntry
|
||||
9, // 2: openshell.datamodel.v1.ObjectMeta.annotations:type_name -> openshell.datamodel.v1.ObjectMeta.AnnotationsEntry
|
||||
0, // 3: openshell.datamodel.v1.WorkspaceStatus.phase:type_name -> openshell.datamodel.v1.WorkspacePhase
|
||||
3, // 4: openshell.datamodel.v1.Workspace.metadata:type_name -> openshell.datamodel.v1.ObjectMeta
|
||||
4, // 5: openshell.datamodel.v1.Workspace.status:type_name -> openshell.datamodel.v1.WorkspaceStatus
|
||||
10, // 6: openshell.datamodel.v1.CredentialHandle.metadata:type_name -> openshell.datamodel.v1.CredentialHandle.MetadataEntry
|
||||
3, // 7: openshell.datamodel.v1.Provider.metadata:type_name -> openshell.datamodel.v1.ObjectMeta
|
||||
11, // 8: openshell.datamodel.v1.Provider.credentials:type_name -> openshell.datamodel.v1.Provider.CredentialsEntry
|
||||
12, // 9: openshell.datamodel.v1.Provider.config:type_name -> openshell.datamodel.v1.Provider.ConfigEntry
|
||||
13, // 10: openshell.datamodel.v1.Provider.credential_expires_at_ms:type_name -> openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry
|
||||
14, // 11: openshell.datamodel.v1.Provider.credential_handles:type_name -> openshell.datamodel.v1.Provider.CredentialHandlesEntry
|
||||
6, // 12: openshell.datamodel.v1.Provider.CredentialHandlesEntry.value:type_name -> openshell.datamodel.v1.CredentialHandle
|
||||
13, // [13:13] is the sub-list for method output_type
|
||||
13, // [13:13] is the sub-list for method input_type
|
||||
13, // [13:13] is the sub-list for extension type_name
|
||||
13, // [13:13] is the sub-list for extension extendee
|
||||
0, // [0:13] is the sub-list for field type_name
|
||||
1, // 1: openshell.datamodel.v1.SandboxReference.workspace_scope:type_name -> openshell.datamodel.v1.WorkspaceSelector
|
||||
9, // 2: openshell.datamodel.v1.ObjectMeta.labels:type_name -> openshell.datamodel.v1.ObjectMeta.LabelsEntry
|
||||
10, // 3: openshell.datamodel.v1.ObjectMeta.annotations:type_name -> openshell.datamodel.v1.ObjectMeta.AnnotationsEntry
|
||||
0, // 4: openshell.datamodel.v1.WorkspaceStatus.phase:type_name -> openshell.datamodel.v1.WorkspacePhase
|
||||
4, // 5: openshell.datamodel.v1.Workspace.metadata:type_name -> openshell.datamodel.v1.ObjectMeta
|
||||
5, // 6: openshell.datamodel.v1.Workspace.status:type_name -> openshell.datamodel.v1.WorkspaceStatus
|
||||
11, // 7: openshell.datamodel.v1.CredentialHandle.metadata:type_name -> openshell.datamodel.v1.CredentialHandle.MetadataEntry
|
||||
4, // 8: openshell.datamodel.v1.Provider.metadata:type_name -> openshell.datamodel.v1.ObjectMeta
|
||||
12, // 9: openshell.datamodel.v1.Provider.credentials:type_name -> openshell.datamodel.v1.Provider.CredentialsEntry
|
||||
13, // 10: openshell.datamodel.v1.Provider.config:type_name -> openshell.datamodel.v1.Provider.ConfigEntry
|
||||
14, // 11: openshell.datamodel.v1.Provider.credential_expires_at_ms:type_name -> openshell.datamodel.v1.Provider.CredentialExpiresAtMsEntry
|
||||
15, // 12: openshell.datamodel.v1.Provider.credential_handles:type_name -> openshell.datamodel.v1.Provider.CredentialHandlesEntry
|
||||
7, // 13: openshell.datamodel.v1.Provider.CredentialHandlesEntry.value:type_name -> openshell.datamodel.v1.CredentialHandle
|
||||
14, // [14:14] is the sub-list for method output_type
|
||||
14, // [14:14] is the sub-list for method input_type
|
||||
14, // [14:14] is the sub-list for extension type_name
|
||||
14, // [14:14] is the sub-list for extension extendee
|
||||
0, // [0:14] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_datamodel_proto_init() }
|
||||
@@ -719,13 +823,17 @@ func file_datamodel_proto_init() {
|
||||
(*WorkspaceSelector_Workspace)(nil),
|
||||
(*WorkspaceSelector_AllWorkspaces)(nil),
|
||||
}
|
||||
file_datamodel_proto_msgTypes[2].OneofWrappers = []any{
|
||||
(*SandboxReference_Id)(nil),
|
||||
(*SandboxReference_Name)(nil),
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_datamodel_proto_rawDesc), len(file_datamodel_proto_rawDesc)),
|
||||
NumEnums: 1,
|
||||
NumMessages: 14,
|
||||
NumMessages: 15,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
},
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,7 @@
|
||||
package sandboxv1
|
||||
|
||||
import (
|
||||
datamodelv1 "github.com/NVIDIA/OpenShell/sdk/go/proto/datamodelv1"
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
structpb "google.golang.org/protobuf/types/known/structpb"
|
||||
@@ -1497,8 +1498,9 @@ func (x *NetworkBinary) GetHarness() bool {
|
||||
// Request to get sandbox settings by sandbox ID.
|
||||
type GetSandboxConfigRequest struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
// The sandbox ID.
|
||||
SandboxId string `protobuf:"bytes,1,opt,name=sandbox_id,json=sandboxId,proto3" json:"sandbox_id,omitempty"`
|
||||
// Canonical public sandbox reference. Sandbox-authenticated callers use the
|
||||
// ID variant; user clients may use either supported variant.
|
||||
SandboxRef *datamodelv1.SandboxReference `protobuf:"bytes,2,opt,name=sandbox_ref,json=sandboxRef,proto3" json:"sandbox_ref,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -1533,11 +1535,11 @@ func (*GetSandboxConfigRequest) Descriptor() ([]byte, []int) {
|
||||
return file_sandbox_proto_rawDescGZIP(), []int{16}
|
||||
}
|
||||
|
||||
func (x *GetSandboxConfigRequest) GetSandboxId() string {
|
||||
func (x *GetSandboxConfigRequest) GetSandboxRef() *datamodelv1.SandboxReference {
|
||||
if x != nil {
|
||||
return x.SandboxId
|
||||
return x.SandboxRef
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
// Request to get gateway-global settings.
|
||||
@@ -2069,7 +2071,7 @@ var File_sandbox_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_sandbox_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\rsandbox.proto\x12\x14openshell.sandbox.v1\x1a\x1cgoogle/protobuf/struct.proto\"\xa8\x05\n" +
|
||||
"\rsandbox.proto\x12\x14openshell.sandbox.v1\x1a\x0fdatamodel.proto\x1a\x1cgoogle/protobuf/struct.proto\"\xa8\x05\n" +
|
||||
"\rSandboxPolicy\x12\x18\n" +
|
||||
"\aversion\x18\x01 \x01(\rR\aversion\x12F\n" +
|
||||
"\n" +
|
||||
@@ -2199,10 +2201,11 @@ const file_sandbox_proto_rawDesc = "" +
|
||||
"\x03any\x18\x02 \x03(\tR\x03any\"A\n" +
|
||||
"\rNetworkBinary\x12\x12\n" +
|
||||
"\x04path\x18\x01 \x01(\tR\x04path\x12\x1c\n" +
|
||||
"\aharness\x18\x02 \x01(\bB\x02\x18\x01R\aharness\"8\n" +
|
||||
"\x17GetSandboxConfigRequest\x12\x1d\n" +
|
||||
"\n" +
|
||||
"sandbox_id\x18\x01 \x01(\tR\tsandboxId\"\x19\n" +
|
||||
"\aharness\x18\x02 \x01(\bB\x02\x18\x01R\aharness\"v\n" +
|
||||
"\x17GetSandboxConfigRequest\x12I\n" +
|
||||
"\vsandbox_ref\x18\x02 \x01(\v2(.openshell.datamodel.v1.SandboxReferenceR\n" +
|
||||
"sandboxRefJ\x04\b\x01\x10\x02R\n" +
|
||||
"sandbox_id\"\x19\n" +
|
||||
"\x17GetGatewayConfigRequest\"\x82\x02\n" +
|
||||
"\x18GetGatewayConfigResponse\x12X\n" +
|
||||
"\bsettings\x18\x01 \x03(\v2<.openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntryR\bsettings\x12+\n" +
|
||||
@@ -2271,41 +2274,42 @@ func file_sandbox_proto_rawDescGZIP() []byte {
|
||||
var file_sandbox_proto_enumTypes = make([]protoimpl.EnumInfo, 2)
|
||||
var file_sandbox_proto_msgTypes = make([]protoimpl.MessageInfo, 32)
|
||||
var file_sandbox_proto_goTypes = []any{
|
||||
(SettingScope)(0), // 0: openshell.sandbox.v1.SettingScope
|
||||
(PolicySource)(0), // 1: openshell.sandbox.v1.PolicySource
|
||||
(*SandboxPolicy)(nil), // 2: openshell.sandbox.v1.SandboxPolicy
|
||||
(*FilesystemPolicy)(nil), // 3: openshell.sandbox.v1.FilesystemPolicy
|
||||
(*LandlockPolicy)(nil), // 4: openshell.sandbox.v1.LandlockPolicy
|
||||
(*ProcessPolicy)(nil), // 5: openshell.sandbox.v1.ProcessPolicy
|
||||
(*NetworkPolicyRule)(nil), // 6: openshell.sandbox.v1.NetworkPolicyRule
|
||||
(*NetworkMiddlewareConfig)(nil), // 7: openshell.sandbox.v1.NetworkMiddlewareConfig
|
||||
(*MiddlewareEndpointSelector)(nil), // 8: openshell.sandbox.v1.MiddlewareEndpointSelector
|
||||
(*NetworkCredentialBinding)(nil), // 9: openshell.sandbox.v1.NetworkCredentialBinding
|
||||
(*NetworkEndpoint)(nil), // 10: openshell.sandbox.v1.NetworkEndpoint
|
||||
(*McpOptions)(nil), // 11: openshell.sandbox.v1.McpOptions
|
||||
(*GraphqlOperation)(nil), // 12: openshell.sandbox.v1.GraphqlOperation
|
||||
(*L7DenyRule)(nil), // 13: openshell.sandbox.v1.L7DenyRule
|
||||
(*L7Rule)(nil), // 14: openshell.sandbox.v1.L7Rule
|
||||
(*L7Allow)(nil), // 15: openshell.sandbox.v1.L7Allow
|
||||
(*L7QueryMatcher)(nil), // 16: openshell.sandbox.v1.L7QueryMatcher
|
||||
(*NetworkBinary)(nil), // 17: openshell.sandbox.v1.NetworkBinary
|
||||
(*GetSandboxConfigRequest)(nil), // 18: openshell.sandbox.v1.GetSandboxConfigRequest
|
||||
(*GetGatewayConfigRequest)(nil), // 19: openshell.sandbox.v1.GetGatewayConfigRequest
|
||||
(*GetGatewayConfigResponse)(nil), // 20: openshell.sandbox.v1.GetGatewayConfigResponse
|
||||
(*SettingValue)(nil), // 21: openshell.sandbox.v1.SettingValue
|
||||
(*EffectiveSetting)(nil), // 22: openshell.sandbox.v1.EffectiveSetting
|
||||
(*GetSandboxConfigResponse)(nil), // 23: openshell.sandbox.v1.GetSandboxConfigResponse
|
||||
(*SupervisorMiddlewareService)(nil), // 24: openshell.sandbox.v1.SupervisorMiddlewareService
|
||||
nil, // 25: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry
|
||||
nil, // 26: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry
|
||||
nil, // 27: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry
|
||||
nil, // 28: openshell.sandbox.v1.L7DenyRule.QueryEntry
|
||||
nil, // 29: openshell.sandbox.v1.L7DenyRule.ParamsEntry
|
||||
nil, // 30: openshell.sandbox.v1.L7Allow.QueryEntry
|
||||
nil, // 31: openshell.sandbox.v1.L7Allow.ParamsEntry
|
||||
nil, // 32: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry
|
||||
nil, // 33: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry
|
||||
(*structpb.Struct)(nil), // 34: google.protobuf.Struct
|
||||
(SettingScope)(0), // 0: openshell.sandbox.v1.SettingScope
|
||||
(PolicySource)(0), // 1: openshell.sandbox.v1.PolicySource
|
||||
(*SandboxPolicy)(nil), // 2: openshell.sandbox.v1.SandboxPolicy
|
||||
(*FilesystemPolicy)(nil), // 3: openshell.sandbox.v1.FilesystemPolicy
|
||||
(*LandlockPolicy)(nil), // 4: openshell.sandbox.v1.LandlockPolicy
|
||||
(*ProcessPolicy)(nil), // 5: openshell.sandbox.v1.ProcessPolicy
|
||||
(*NetworkPolicyRule)(nil), // 6: openshell.sandbox.v1.NetworkPolicyRule
|
||||
(*NetworkMiddlewareConfig)(nil), // 7: openshell.sandbox.v1.NetworkMiddlewareConfig
|
||||
(*MiddlewareEndpointSelector)(nil), // 8: openshell.sandbox.v1.MiddlewareEndpointSelector
|
||||
(*NetworkCredentialBinding)(nil), // 9: openshell.sandbox.v1.NetworkCredentialBinding
|
||||
(*NetworkEndpoint)(nil), // 10: openshell.sandbox.v1.NetworkEndpoint
|
||||
(*McpOptions)(nil), // 11: openshell.sandbox.v1.McpOptions
|
||||
(*GraphqlOperation)(nil), // 12: openshell.sandbox.v1.GraphqlOperation
|
||||
(*L7DenyRule)(nil), // 13: openshell.sandbox.v1.L7DenyRule
|
||||
(*L7Rule)(nil), // 14: openshell.sandbox.v1.L7Rule
|
||||
(*L7Allow)(nil), // 15: openshell.sandbox.v1.L7Allow
|
||||
(*L7QueryMatcher)(nil), // 16: openshell.sandbox.v1.L7QueryMatcher
|
||||
(*NetworkBinary)(nil), // 17: openshell.sandbox.v1.NetworkBinary
|
||||
(*GetSandboxConfigRequest)(nil), // 18: openshell.sandbox.v1.GetSandboxConfigRequest
|
||||
(*GetGatewayConfigRequest)(nil), // 19: openshell.sandbox.v1.GetGatewayConfigRequest
|
||||
(*GetGatewayConfigResponse)(nil), // 20: openshell.sandbox.v1.GetGatewayConfigResponse
|
||||
(*SettingValue)(nil), // 21: openshell.sandbox.v1.SettingValue
|
||||
(*EffectiveSetting)(nil), // 22: openshell.sandbox.v1.EffectiveSetting
|
||||
(*GetSandboxConfigResponse)(nil), // 23: openshell.sandbox.v1.GetSandboxConfigResponse
|
||||
(*SupervisorMiddlewareService)(nil), // 24: openshell.sandbox.v1.SupervisorMiddlewareService
|
||||
nil, // 25: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry
|
||||
nil, // 26: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry
|
||||
nil, // 27: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry
|
||||
nil, // 28: openshell.sandbox.v1.L7DenyRule.QueryEntry
|
||||
nil, // 29: openshell.sandbox.v1.L7DenyRule.ParamsEntry
|
||||
nil, // 30: openshell.sandbox.v1.L7Allow.QueryEntry
|
||||
nil, // 31: openshell.sandbox.v1.L7Allow.ParamsEntry
|
||||
nil, // 32: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry
|
||||
nil, // 33: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry
|
||||
(*structpb.Struct)(nil), // 34: google.protobuf.Struct
|
||||
(*datamodelv1.SandboxReference)(nil), // 35: openshell.datamodel.v1.SandboxReference
|
||||
}
|
||||
var file_sandbox_proto_depIdxs = []int32{
|
||||
3, // 0: openshell.sandbox.v1.SandboxPolicy.filesystem:type_name -> openshell.sandbox.v1.FilesystemPolicy
|
||||
@@ -2327,27 +2331,28 @@ var file_sandbox_proto_depIdxs = []int32{
|
||||
15, // 16: openshell.sandbox.v1.L7Rule.allow:type_name -> openshell.sandbox.v1.L7Allow
|
||||
30, // 17: openshell.sandbox.v1.L7Allow.query:type_name -> openshell.sandbox.v1.L7Allow.QueryEntry
|
||||
31, // 18: openshell.sandbox.v1.L7Allow.params:type_name -> openshell.sandbox.v1.L7Allow.ParamsEntry
|
||||
32, // 19: openshell.sandbox.v1.GetGatewayConfigResponse.settings:type_name -> openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry
|
||||
21, // 20: openshell.sandbox.v1.EffectiveSetting.value:type_name -> openshell.sandbox.v1.SettingValue
|
||||
0, // 21: openshell.sandbox.v1.EffectiveSetting.scope:type_name -> openshell.sandbox.v1.SettingScope
|
||||
2, // 22: openshell.sandbox.v1.GetSandboxConfigResponse.policy:type_name -> openshell.sandbox.v1.SandboxPolicy
|
||||
33, // 23: openshell.sandbox.v1.GetSandboxConfigResponse.settings:type_name -> openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry
|
||||
1, // 24: openshell.sandbox.v1.GetSandboxConfigResponse.policy_source:type_name -> openshell.sandbox.v1.PolicySource
|
||||
24, // 25: openshell.sandbox.v1.GetSandboxConfigResponse.supervisor_middleware_services:type_name -> openshell.sandbox.v1.SupervisorMiddlewareService
|
||||
6, // 26: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry.value:type_name -> openshell.sandbox.v1.NetworkPolicyRule
|
||||
7, // 27: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry.value:type_name -> openshell.sandbox.v1.NetworkMiddlewareConfig
|
||||
12, // 28: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry.value:type_name -> openshell.sandbox.v1.GraphqlOperation
|
||||
16, // 29: openshell.sandbox.v1.L7DenyRule.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
16, // 30: openshell.sandbox.v1.L7DenyRule.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
16, // 31: openshell.sandbox.v1.L7Allow.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
16, // 32: openshell.sandbox.v1.L7Allow.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
21, // 33: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.SettingValue
|
||||
22, // 34: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.EffectiveSetting
|
||||
35, // [35:35] is the sub-list for method output_type
|
||||
35, // [35:35] is the sub-list for method input_type
|
||||
35, // [35:35] is the sub-list for extension type_name
|
||||
35, // [35:35] is the sub-list for extension extendee
|
||||
0, // [0:35] is the sub-list for field type_name
|
||||
35, // 19: openshell.sandbox.v1.GetSandboxConfigRequest.sandbox_ref:type_name -> openshell.datamodel.v1.SandboxReference
|
||||
32, // 20: openshell.sandbox.v1.GetGatewayConfigResponse.settings:type_name -> openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry
|
||||
21, // 21: openshell.sandbox.v1.EffectiveSetting.value:type_name -> openshell.sandbox.v1.SettingValue
|
||||
0, // 22: openshell.sandbox.v1.EffectiveSetting.scope:type_name -> openshell.sandbox.v1.SettingScope
|
||||
2, // 23: openshell.sandbox.v1.GetSandboxConfigResponse.policy:type_name -> openshell.sandbox.v1.SandboxPolicy
|
||||
33, // 24: openshell.sandbox.v1.GetSandboxConfigResponse.settings:type_name -> openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry
|
||||
1, // 25: openshell.sandbox.v1.GetSandboxConfigResponse.policy_source:type_name -> openshell.sandbox.v1.PolicySource
|
||||
24, // 26: openshell.sandbox.v1.GetSandboxConfigResponse.supervisor_middleware_services:type_name -> openshell.sandbox.v1.SupervisorMiddlewareService
|
||||
6, // 27: openshell.sandbox.v1.SandboxPolicy.NetworkPoliciesEntry.value:type_name -> openshell.sandbox.v1.NetworkPolicyRule
|
||||
7, // 28: openshell.sandbox.v1.SandboxPolicy.NetworkMiddlewaresEntry.value:type_name -> openshell.sandbox.v1.NetworkMiddlewareConfig
|
||||
12, // 29: openshell.sandbox.v1.NetworkEndpoint.GraphqlPersistedQueriesEntry.value:type_name -> openshell.sandbox.v1.GraphqlOperation
|
||||
16, // 30: openshell.sandbox.v1.L7DenyRule.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
16, // 31: openshell.sandbox.v1.L7DenyRule.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
16, // 32: openshell.sandbox.v1.L7Allow.QueryEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
16, // 33: openshell.sandbox.v1.L7Allow.ParamsEntry.value:type_name -> openshell.sandbox.v1.L7QueryMatcher
|
||||
21, // 34: openshell.sandbox.v1.GetGatewayConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.SettingValue
|
||||
22, // 35: openshell.sandbox.v1.GetSandboxConfigResponse.SettingsEntry.value:type_name -> openshell.sandbox.v1.EffectiveSetting
|
||||
36, // [36:36] is the sub-list for method output_type
|
||||
36, // [36:36] is the sub-list for method input_type
|
||||
36, // [36:36] is the sub-list for extension type_name
|
||||
36, // [36:36] is the sub-list for extension extendee
|
||||
0, // [0:36] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_sandbox_proto_init() }
|
||||
|
||||
@@ -58,20 +58,34 @@ const enc = (s: string) => new TextEncoder().encode(s);
|
||||
|
||||
type ScopedRequest = {
|
||||
workspaceScope?: { selection?: { case?: string; value?: unknown } };
|
||||
sandboxRef?: {
|
||||
identifier?: { case?: string; value?: unknown };
|
||||
workspaceScope?: { selection?: { case?: string; value?: unknown } };
|
||||
};
|
||||
};
|
||||
|
||||
function selectedWorkspace(req: ScopedRequest): string | undefined {
|
||||
const selection = req.workspaceScope?.selection;
|
||||
const selection = (req.sandboxRef?.workspaceScope ?? req.workspaceScope)?.selection;
|
||||
return selection?.case === 'workspace' && typeof selection.value === 'string' ? selection.value : undefined;
|
||||
}
|
||||
|
||||
function requestSandboxName(req: ScopedRequest): string | undefined {
|
||||
const identifier = req.sandboxRef?.identifier;
|
||||
return identifier?.case === 'name' && typeof identifier.value === 'string' ? identifier.value : undefined;
|
||||
}
|
||||
|
||||
function requestSandboxId(req: ScopedRequest): string | undefined {
|
||||
const identifier = req.sandboxRef?.identifier;
|
||||
return identifier?.case === 'id' && typeof identifier.value === 'string' ? identifier.value : undefined;
|
||||
}
|
||||
|
||||
function selectsAllWorkspaces(req: ScopedRequest): boolean {
|
||||
return req.workspaceScope?.selection?.case === 'allWorkspaces';
|
||||
}
|
||||
|
||||
describe('exec / execStream', () => {
|
||||
it('resolves the id via get, frames tty:false, and buffers the result (backward compat)', async () => {
|
||||
let execReq: { sandboxId?: string; tty?: boolean; command?: string[] } = {};
|
||||
let execReq: ScopedRequest & { tty?: boolean; command?: string[] } = {};
|
||||
const sandbox = client({
|
||||
getSandbox: () => readySandbox('sb', 'sb-id-1'),
|
||||
// eslint-disable-next-line require-yield
|
||||
@@ -85,7 +99,7 @@ describe('exec / execStream', () => {
|
||||
});
|
||||
|
||||
const result = await sandbox.exec('sb', ['/bin/sh', '-c', 'echo hi']);
|
||||
expect(execReq.sandboxId).toBe('sb-id-1');
|
||||
expect(requestSandboxId(execReq)).toBe('sb-id-1');
|
||||
expect(execReq.tty).toBe(false);
|
||||
expect(execReq.command).toEqual(['/bin/sh', '-c', 'echo hi']);
|
||||
expect(result.exitCode).toBe(3);
|
||||
@@ -352,13 +366,14 @@ describe('create', () => {
|
||||
},
|
||||
getSandbox: (req) => {
|
||||
const workspace = selectedWorkspace(req);
|
||||
if (req.name === 'exec') observed.execGet = workspace;
|
||||
else if (req.name === 'interactive') observed.interactiveGet = workspace;
|
||||
else if (req.name === 'ssh') observed.sshGet = workspace;
|
||||
else if (req.name === 'forward') observed.forwardGet = workspace;
|
||||
else if (req.name === 'config' && workspace) observed.configGets.push(workspace);
|
||||
const name = requestSandboxName(req);
|
||||
if (name === 'exec') observed.execGet = workspace;
|
||||
else if (name === 'interactive') observed.interactiveGet = workspace;
|
||||
else if (name === 'ssh') observed.sshGet = workspace;
|
||||
else if (name === 'forward') observed.forwardGet = workspace;
|
||||
else if (name === 'config' && workspace) observed.configGets.push(workspace);
|
||||
else observed.get = req;
|
||||
return readySandbox(req.name, `${req.name}-id`, 7n, undefined, workspace ?? 'default');
|
||||
return readySandbox(name ?? '', `${name}-id`, 7n, undefined, workspace ?? 'default');
|
||||
},
|
||||
listSandboxes: (req) => {
|
||||
observed.list = req;
|
||||
@@ -384,16 +399,26 @@ describe('create', () => {
|
||||
attachSandboxProvider: (req) => {
|
||||
observed.attach = req;
|
||||
return {
|
||||
sandbox: readySandbox(req.sandboxName, 'attach-id', 7n, undefined, selectedWorkspace(req) ?? 'default')
|
||||
.sandbox,
|
||||
sandbox: readySandbox(
|
||||
requestSandboxName(req) ?? '',
|
||||
'attach-id',
|
||||
7n,
|
||||
undefined,
|
||||
selectedWorkspace(req) ?? 'default',
|
||||
).sandbox,
|
||||
attached: true,
|
||||
};
|
||||
},
|
||||
detachSandboxProvider: (req) => {
|
||||
observed.detach = req;
|
||||
return {
|
||||
sandbox: readySandbox(req.sandboxName, 'detach-id', 7n, undefined, selectedWorkspace(req) ?? 'default')
|
||||
.sandbox,
|
||||
sandbox: readySandbox(
|
||||
requestSandboxName(req) ?? '',
|
||||
'detach-id',
|
||||
7n,
|
||||
undefined,
|
||||
selectedWorkspace(req) ?? 'default',
|
||||
).sandbox,
|
||||
detached: true,
|
||||
};
|
||||
},
|
||||
@@ -425,7 +450,7 @@ describe('create', () => {
|
||||
yield { payload: { case: 'exit', value: { exitCode: 0 } } };
|
||||
},
|
||||
createSshSession: (req) => ({
|
||||
sandboxId: req.sandboxId,
|
||||
sandboxId: requestSandboxId(req) ?? '',
|
||||
token: 'tok',
|
||||
gatewayHost: 'gw',
|
||||
gatewayPort: 443,
|
||||
@@ -756,7 +781,7 @@ describe('Pushable', () => {
|
||||
describe('execInteractive', () => {
|
||||
it('sends start first with tty/cols/rows, streams output, and resolves done', async () => {
|
||||
const cases: string[] = [];
|
||||
let started: { tty?: boolean; cols?: number; rows?: number; sandboxId?: string } | undefined;
|
||||
let started: (ScopedRequest & { tty?: boolean; cols?: number; rows?: number }) | undefined;
|
||||
const sandbox = client({
|
||||
getSandbox: () => readySandbox('sb', 'sb-id-9'),
|
||||
execSandboxInteractive: async function* (requests) {
|
||||
@@ -800,7 +825,7 @@ describe('execInteractive', () => {
|
||||
expect(started?.tty).toBe(true);
|
||||
expect(started?.cols).toBe(120);
|
||||
expect(started?.rows).toBe(40);
|
||||
expect(started?.sandboxId).toBe('sb-id-9');
|
||||
expect(requestSandboxId(started ?? {})).toBe('sb-id-9');
|
||||
expect(out.join('')).toContain('ready\n');
|
||||
expect(out.join('')).toContain('echo hi');
|
||||
});
|
||||
@@ -864,7 +889,7 @@ describe('exec done settlement', () => {
|
||||
describe('providers', () => {
|
||||
it('attach/detach assemble the request and map the changed flag + sandbox ref', async () => {
|
||||
let attachReq: {
|
||||
sandboxName?: string;
|
||||
sandboxRef?: ScopedRequest['sandboxRef'];
|
||||
providerName?: string;
|
||||
expectedResourceVersion?: bigint;
|
||||
} = {};
|
||||
@@ -884,7 +909,7 @@ describe('providers', () => {
|
||||
});
|
||||
|
||||
const attach = await sandbox.attachProvider('sb', 'claude');
|
||||
expect(attachReq.sandboxName).toBe('sb');
|
||||
expect(requestSandboxName(attachReq)).toBe('sb');
|
||||
expect(attachReq.providerName).toBe('claude');
|
||||
expect(attachReq.expectedResourceVersion).toBe(0n);
|
||||
expect(attach.changed).toBe(true);
|
||||
@@ -961,7 +986,7 @@ describe('config / policy', () => {
|
||||
|
||||
it('setPolicy sends global=false + version pin and (wait) polls until the hash matches', async () => {
|
||||
let updateReq: {
|
||||
name?: string;
|
||||
sandboxRef?: ScopedRequest['sandboxRef'];
|
||||
global?: boolean;
|
||||
expectedResourceVersion?: bigint;
|
||||
policy?: unknown;
|
||||
@@ -1002,7 +1027,7 @@ describe('config / policy', () => {
|
||||
},
|
||||
{ wait: true, expectedResourceVersion: '7' },
|
||||
);
|
||||
expect(updateReq.name).toBe('sb');
|
||||
expect(requestSandboxName(updateReq)).toBe('sb');
|
||||
expect(updateReq.global).toBe(false);
|
||||
expect(updateReq.expectedResourceVersion).toBe(7n);
|
||||
expect(updateReq.policy).toBeDefined();
|
||||
@@ -1035,7 +1060,7 @@ describe('config / policy', () => {
|
||||
|
||||
it('setSetting upserts a single sandbox-scoped setting (global=false)', async () => {
|
||||
let req: {
|
||||
name?: string;
|
||||
sandboxRef?: ScopedRequest['sandboxRef'];
|
||||
settingKey?: string;
|
||||
global?: boolean;
|
||||
settingValue?: unknown;
|
||||
@@ -1054,7 +1079,7 @@ describe('config / policy', () => {
|
||||
const result = await sandbox.setSetting('sb', 'feature.enabled', {
|
||||
value: { case: 'boolValue', value: true },
|
||||
});
|
||||
expect(req.name).toBe('sb');
|
||||
expect(requestSandboxName(req)).toBe('sb');
|
||||
expect(req.settingKey).toBe('feature.enabled');
|
||||
expect(req.global).toBe(false);
|
||||
expect(req.settingValue).toMatchObject({
|
||||
@@ -1171,9 +1196,9 @@ describe('ssh sessions', () => {
|
||||
|
||||
describe('forward', () => {
|
||||
it('binds a local port and relays bytes both ways, minting + revoking a token', async () => {
|
||||
let sshReq: { sandboxId?: string } = {};
|
||||
let sshReq: ScopedRequest = {};
|
||||
let revokedToken: string | undefined;
|
||||
let initFrame: { sandboxId?: string; authorizationToken?: string; target?: unknown } | undefined;
|
||||
let initFrame: (ScopedRequest & { authorizationToken?: string; target?: unknown }) | undefined;
|
||||
const sandbox = client({
|
||||
getSandbox: () => readySandbox('sb', 'sb-id-forward'),
|
||||
createSshSession: (req) => {
|
||||
@@ -1224,8 +1249,8 @@ describe('forward', () => {
|
||||
});
|
||||
|
||||
expect(echoed).toBe('ping-through-forward');
|
||||
expect(sshReq.sandboxId).toBe('sb-id-forward');
|
||||
expect(initFrame?.sandboxId).toBe('sb-id-forward');
|
||||
expect(requestSandboxId(sshReq)).toBe('sb-id-forward');
|
||||
expect(requestSandboxId(initFrame ?? {})).toBe('sb-id-forward');
|
||||
expect(initFrame?.authorizationToken).toBe('fwd-tok');
|
||||
expect(initFrame?.target).toMatchObject({
|
||||
case: 'tcp',
|
||||
@@ -1469,7 +1494,12 @@ describe('raw escape hatch', () => {
|
||||
|
||||
// raw returns the full generated message: the enum stays numeric, where the
|
||||
// curated get() would lowercase status.phase to 'ready'.
|
||||
const resp = await sandbox.raw.getSandbox({ name: 'sb' });
|
||||
const resp = await sandbox.raw.getSandbox({
|
||||
sandboxRef: {
|
||||
identifier: { case: 'name', value: 'sb' },
|
||||
workspaceScope: { selection: { case: 'workspace', value: 'default' } },
|
||||
},
|
||||
});
|
||||
expect(resp.sandbox?.status?.phase).toBe(SandboxPhase.READY);
|
||||
expect(resp.sandbox?.metadata?.name).toBe('sb');
|
||||
|
||||
|
||||
@@ -506,6 +506,17 @@ function listWorkspaceScope(options?: WorkspaceListScope | null): MessageInitSha
|
||||
return options?.allWorkspaces ? { selection: { case: 'allWorkspaces', value: {} } } : workspaceScope(options);
|
||||
}
|
||||
|
||||
function sandboxReferenceByName(name: string, options?: SandboxWorkspaceOptions | null) {
|
||||
return {
|
||||
identifier: { case: 'name' as const, value: name },
|
||||
workspaceScope: workspaceScope(options),
|
||||
};
|
||||
}
|
||||
|
||||
function sandboxReferenceById(id: string) {
|
||||
return { identifier: { case: 'id' as const, value: id } };
|
||||
}
|
||||
|
||||
function requestCallOptions(options?: SandboxCallOptions | null): CallOptions | undefined {
|
||||
if (!options) return undefined;
|
||||
const { workspace: _workspace, ...callOptions } = options;
|
||||
@@ -800,7 +811,7 @@ export class SandboxClient {
|
||||
async get(name: string, options?: SandboxCallOptions | null): Promise<SandboxRef> {
|
||||
try {
|
||||
const resp = await this.grpc.getSandbox(
|
||||
{ name, workspaceScope: workspaceScope(options) },
|
||||
{ sandboxRef: sandboxReferenceByName(name, options) },
|
||||
requestCallOptions(options),
|
||||
);
|
||||
return sandboxRef(resp.sandbox);
|
||||
@@ -825,7 +836,7 @@ export class SandboxClient {
|
||||
|
||||
async delete(name: string, options?: SandboxWorkspaceOptions | null): Promise<boolean> {
|
||||
try {
|
||||
const resp = await this.grpc.deleteSandbox({ name, workspaceScope: workspaceScope(options) });
|
||||
const resp = await this.grpc.deleteSandbox({ sandboxRef: sandboxReferenceByName(name, options) });
|
||||
return resp.deleted;
|
||||
} catch (e) {
|
||||
throw fromConnect(e);
|
||||
@@ -901,7 +912,7 @@ export class SandboxClient {
|
||||
});
|
||||
const stream = this.grpc.execSandbox(
|
||||
{
|
||||
sandboxId: sandbox.id,
|
||||
sandboxRef: sandboxReferenceById(sandbox.id),
|
||||
command,
|
||||
workdir: options?.workdir ?? '',
|
||||
environment: options?.environment ?? {},
|
||||
@@ -984,7 +995,7 @@ export class SandboxClient {
|
||||
payload: {
|
||||
case: 'start',
|
||||
value: {
|
||||
sandboxId,
|
||||
sandboxRef: sandboxReferenceById(sandboxId),
|
||||
command,
|
||||
workdir: options?.workdir ?? '',
|
||||
environment: options?.environment ?? {},
|
||||
@@ -1204,7 +1215,7 @@ export class SandboxClient {
|
||||
const input = new Pushable<MessageInitShape<typeof TcpForwardFrameSchema>>();
|
||||
input.onDrain = () => socket.resume();
|
||||
try {
|
||||
const session = await this.grpc.createSshSession({ sandboxId }, { signal });
|
||||
const session = await this.grpc.createSshSession({ sandboxRef: sandboxReferenceById(sandboxId) }, { signal });
|
||||
// Defense-in-depth: the token feeds forwardTcp authorization, so hold it
|
||||
// to the same trust-boundary contract as createSshSession. A violation
|
||||
// tears down this one socket via the catch below.
|
||||
@@ -1214,7 +1225,7 @@ export class SandboxClient {
|
||||
payload: {
|
||||
case: 'init',
|
||||
value: {
|
||||
sandboxId,
|
||||
sandboxRef: sandboxReferenceById(sandboxId),
|
||||
serviceId: `service-forward:${name}:${targetHost}:${targetPort}`,
|
||||
target: {
|
||||
case: 'tcp',
|
||||
@@ -1277,7 +1288,7 @@ export class SandboxClient {
|
||||
async createSshSession(name: string, options?: SandboxWorkspaceOptions | null): Promise<SshSession> {
|
||||
try {
|
||||
const sandbox = await this.get(name, options);
|
||||
const resp = await this.grpc.createSshSession({ sandboxId: sandbox.id });
|
||||
const resp = await this.grpc.createSshSession({ sandboxRef: sandboxReferenceById(sandbox.id) });
|
||||
// Reject any response outside the proto trust-boundary contract before
|
||||
// handing these values to the caller (they feed OpenSSH ProxyCommand).
|
||||
validateSshResponse(resp, sandbox.id);
|
||||
@@ -1311,10 +1322,9 @@ export class SandboxClient {
|
||||
): Promise<ProviderChange> {
|
||||
try {
|
||||
const resp = await this.grpc.attachSandboxProvider({
|
||||
sandboxName: name,
|
||||
sandboxRef: sandboxReferenceByName(name, options),
|
||||
providerName: provider,
|
||||
expectedResourceVersion: versionPin(options?.expectedResourceVersion),
|
||||
workspaceScope: workspaceScope(options),
|
||||
});
|
||||
return { sandbox: sandboxRef(resp.sandbox), changed: resp.attached };
|
||||
} catch (e) {
|
||||
@@ -1329,10 +1339,9 @@ export class SandboxClient {
|
||||
): Promise<ProviderChange> {
|
||||
try {
|
||||
const resp = await this.grpc.detachSandboxProvider({
|
||||
sandboxName: name,
|
||||
sandboxRef: sandboxReferenceByName(name, options),
|
||||
providerName: provider,
|
||||
expectedResourceVersion: versionPin(options?.expectedResourceVersion),
|
||||
workspaceScope: workspaceScope(options),
|
||||
});
|
||||
return { sandbox: sandboxRef(resp.sandbox), changed: resp.detached };
|
||||
} catch (e) {
|
||||
@@ -1343,8 +1352,7 @@ export class SandboxClient {
|
||||
async listProviders(name: string, options?: SandboxWorkspaceOptions | null): Promise<ProviderRef[]> {
|
||||
try {
|
||||
const resp = await this.grpc.listSandboxProviders({
|
||||
sandboxName: name,
|
||||
workspaceScope: workspaceScope(options),
|
||||
sandboxRef: sandboxReferenceByName(name, options),
|
||||
});
|
||||
return resp.providers.map((p) => providerRef(p));
|
||||
} catch (e) {
|
||||
@@ -1355,7 +1363,10 @@ export class SandboxClient {
|
||||
async getConfig(name: string, options?: SandboxCallOptions | null): Promise<SandboxConfig> {
|
||||
try {
|
||||
const sandbox = await this.get(name, options);
|
||||
const resp = await this.grpc.getSandboxConfig({ sandboxId: sandbox.id }, requestCallOptions(options));
|
||||
const resp = await this.grpc.getSandboxConfig(
|
||||
{ sandboxRef: sandboxReferenceById(sandbox.id) },
|
||||
requestCallOptions(options),
|
||||
);
|
||||
return sandboxConfig(resp);
|
||||
} catch (e) {
|
||||
throw e instanceof SdkError ? e : fromConnect(e);
|
||||
@@ -1373,11 +1384,10 @@ export class SandboxClient {
|
||||
): Promise<UpdateConfigResult> {
|
||||
try {
|
||||
const resp = await this.grpc.updateConfig({
|
||||
name,
|
||||
sandboxRef: sandboxReferenceByName(name, options),
|
||||
policy,
|
||||
global: false,
|
||||
expectedResourceVersion: versionPin(options?.expectedResourceVersion),
|
||||
workspaceScope: workspaceScope(options),
|
||||
});
|
||||
const result = updateConfigResult(resp);
|
||||
if (options?.wait)
|
||||
@@ -1398,11 +1408,10 @@ export class SandboxClient {
|
||||
): Promise<UpdateConfigResult> {
|
||||
try {
|
||||
const resp = await this.grpc.updateConfig({
|
||||
name,
|
||||
sandboxRef: sandboxReferenceByName(name, options),
|
||||
settingKey: key,
|
||||
settingValue: value,
|
||||
global: false,
|
||||
workspaceScope: workspaceScope(options),
|
||||
});
|
||||
return updateConfigResult(resp);
|
||||
} catch (e) {
|
||||
|
||||
@@ -798,6 +798,9 @@ openshell service get my-app web
|
||||
openshell service delete my-app web
|
||||
```
|
||||
|
||||
Use `openshell service list --all-workspaces` for a Platform Admin view across
|
||||
workspaces. A sandbox name and `--all-workspaces` are mutually exclusive.
|
||||
|
||||
Prefer loopback binds unless the user explicitly needs LAN-visible local access.
|
||||
|
||||
---
|
||||
|
||||
@@ -56,6 +56,12 @@ LINE_REWRITES = {
|
||||
"from . import datamodel_pb2 as datamodel__pb2",
|
||||
),
|
||||
],
|
||||
"python/openshell/_proto/sandbox_pb2.py": [
|
||||
(
|
||||
r"^import datamodel_pb2 as datamodel__pb2$",
|
||||
"from . import datamodel_pb2 as datamodel__pb2",
|
||||
),
|
||||
],
|
||||
"python/openshell/_proto/sandbox_pb2_grpc.py": [
|
||||
(
|
||||
r"^import sandbox_pb2 as sandbox__pb2$",
|
||||
|
||||
Reference in New Issue
Block a user