mirror of
https://github.com/NVIDIA/OpenShell.git
synced 2026-10-30 07:33:59 +08:00
* feat(server): add sandbox workload templates Signed-off-by: Gordon Sim <gsim@redhat.com> * feat(go-sdk): add sandbox workload template support Signed-off-by: Gordon Sim <gsim@redhat.com> * feat(rust-sdk): add sandbox workload template support Signed-off-by: Gordon Sim <gsim@redhat.com> * feat(python-sdk): add sandbox workload template support Signed-off-by: Gordon Sim <gsim@redhat.com> * feat(typescript-sdk): add sandbox workload template support Signed-off-by: Gordon Sim <gsim@redhat.com> * docs(agents): document sandbox workload templates Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(cli): support default GPU requests in sandbox templates Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(server): cap sandbox templates per workspace Signed-off-by: Gordon Sim <gsim@redhat.com> * docs(architecture): document sandbox workload template boundaries Signed-off-by: Gordon Sim <gsim@redhat.com> * feat(cli+sdk): expose sandbox workload template provenance Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(cli): include sandbox template annotations in output Signed-off-by: Gordon Sim <gsim@redhat.com> * feat(sandbox): add label selectors to template listing Signed-off-by: Gordon Sim <gsim@redhat.com> * test(e2e): cover sandbox template failure paths Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(server): preserve command and ttl when creating sandbox from template Signed-off-by: Gordon Sim <gsim@redhat.com> * test(server): add field coverage test for template merge Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(go-sdk): add pagination support to fake client Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(cli): warn on env vars that looks like secrets Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(sdk-ts): propagate sandbox workspace through lifecycle calls Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(docs): update workspace management docs Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(ts-sdk): support command and tty when creating from template Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(go-sdk): support command and tty when creating from template Signed-off-by: Gordon Sim <gsim@redhat.com> * test(python-sdk): verify command and tty handling when creating from template Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(go-sdk): update docs and ClientInterface Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(server): validate sandbox create specs before I/O Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(go-sdk): guard empty DNS-1123 label validation Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(python-sdk): allow empty template builder mappings Signed-off-by: Gordon Sim <gsim@redhat.com> * fix(cli): align template GPU JSON default output Signed-off-by: Gordon Sim <gsim@redhat.com> --------- Signed-off-by: Gordon Sim <gsim@redhat.com>
204 lines
7.1 KiB
Rust
204 lines
7.1 KiB
Rust
// SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
//! Shared helpers for CLI integration tests.
|
|
//!
|
|
//! Include this module from a test file with:
|
|
//! ```ignore
|
|
//! mod helpers;
|
|
//! ```
|
|
|
|
#[macro_export]
|
|
macro_rules! unimplemented_sandbox_template_rpcs {
|
|
() => {
|
|
fn create_sandbox_template<'life0, 'async_trait>(
|
|
&'life0 self,
|
|
_request: tonic::Request<openshell_core::proto::CreateSandboxTemplateRequest>,
|
|
) -> std::pin::Pin<
|
|
Box<
|
|
dyn std::future::Future<
|
|
Output = Result<
|
|
tonic::Response<openshell_core::proto::SandboxTemplateResponse>,
|
|
tonic::Status,
|
|
>,
|
|
> + Send
|
|
+ 'async_trait,
|
|
>,
|
|
>
|
|
where
|
|
'life0: 'async_trait,
|
|
Self: 'async_trait,
|
|
{
|
|
Box::pin(async { Err(tonic::Status::unimplemented("unused")) })
|
|
}
|
|
|
|
fn get_sandbox_template<'life0, 'async_trait>(
|
|
&'life0 self,
|
|
_request: tonic::Request<openshell_core::proto::GetSandboxTemplateRequest>,
|
|
) -> std::pin::Pin<
|
|
Box<
|
|
dyn std::future::Future<
|
|
Output = Result<
|
|
tonic::Response<openshell_core::proto::SandboxTemplateResponse>,
|
|
tonic::Status,
|
|
>,
|
|
> + Send
|
|
+ 'async_trait,
|
|
>,
|
|
>
|
|
where
|
|
'life0: 'async_trait,
|
|
Self: 'async_trait,
|
|
{
|
|
Box::pin(async { Err(tonic::Status::unimplemented("unused")) })
|
|
}
|
|
|
|
fn list_sandbox_templates<'life0, 'async_trait>(
|
|
&'life0 self,
|
|
_request: tonic::Request<openshell_core::proto::ListSandboxTemplatesRequest>,
|
|
) -> std::pin::Pin<
|
|
Box<
|
|
dyn std::future::Future<
|
|
Output = Result<
|
|
tonic::Response<openshell_core::proto::ListSandboxTemplatesResponse>,
|
|
tonic::Status,
|
|
>,
|
|
> + Send
|
|
+ 'async_trait,
|
|
>,
|
|
>
|
|
where
|
|
'life0: 'async_trait,
|
|
Self: 'async_trait,
|
|
{
|
|
Box::pin(async { Err(tonic::Status::unimplemented("unused")) })
|
|
}
|
|
|
|
fn delete_sandbox_template<'life0, 'async_trait>(
|
|
&'life0 self,
|
|
_request: tonic::Request<openshell_core::proto::DeleteSandboxTemplateRequest>,
|
|
) -> std::pin::Pin<
|
|
Box<
|
|
dyn std::future::Future<
|
|
Output = Result<
|
|
tonic::Response<openshell_core::proto::DeleteSandboxTemplateResponse>,
|
|
tonic::Status,
|
|
>,
|
|
> + Send
|
|
+ 'async_trait,
|
|
>,
|
|
>
|
|
where
|
|
'life0: 'async_trait,
|
|
Self: 'async_trait,
|
|
{
|
|
Box::pin(async { Err(tonic::Status::unimplemented("unused")) })
|
|
}
|
|
};
|
|
}
|
|
|
|
use rcgen::{
|
|
BasicConstraints, Certificate, CertificateParams, ExtendedKeyUsagePurpose, IsCa, KeyPair,
|
|
};
|
|
|
|
// ── EnvVarGuard ──────────────────────────────────────────────────────────────
|
|
|
|
/// Global mutex that serialises tests which mutate environment variables so
|
|
/// concurrent threads don't clobber each other's state.
|
|
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
|
|
|
struct SavedVar {
|
|
key: &'static str,
|
|
original: Option<String>,
|
|
}
|
|
|
|
/// RAII guard that acquires `ENV_LOCK` and restores all modified environment
|
|
/// variables on drop.
|
|
pub struct EnvVarGuard {
|
|
vars: Vec<SavedVar>,
|
|
_lock: std::sync::MutexGuard<'static, ()>,
|
|
}
|
|
|
|
#[allow(dead_code, unsafe_code)]
|
|
impl EnvVarGuard {
|
|
/// Acquire the global env-var lock and atomically set one or more
|
|
/// environment variables. All variables are restored to their prior
|
|
/// state (or removed) when the guard is dropped.
|
|
pub fn set(pairs: &[(&'static str, &str)]) -> Self {
|
|
let lock = ENV_LOCK
|
|
.lock()
|
|
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
|
let mut vars = Vec::with_capacity(pairs.len());
|
|
for &(key, value) in pairs {
|
|
let original = std::env::var(key).ok();
|
|
unsafe {
|
|
std::env::set_var(key, value);
|
|
}
|
|
vars.push(SavedVar { key, original });
|
|
}
|
|
Self { vars, _lock: lock }
|
|
}
|
|
}
|
|
|
|
#[allow(unsafe_code)]
|
|
impl Drop for EnvVarGuard {
|
|
fn drop(&mut self) {
|
|
for var in &self.vars {
|
|
if let Some(value) = &var.original {
|
|
unsafe {
|
|
std::env::set_var(var.key, value);
|
|
}
|
|
} else {
|
|
unsafe {
|
|
std::env::remove_var(var.key);
|
|
}
|
|
}
|
|
}
|
|
// _lock drops here, releasing the mutex
|
|
}
|
|
}
|
|
|
|
// ── TLS helpers ──────────────────────────────────────────────────────────────
|
|
|
|
/// Install the `rustls` ring crypto provider as the process default.
|
|
///
|
|
/// Safe to call multiple times — subsequent calls are no-ops.
|
|
#[allow(dead_code)]
|
|
pub fn install_rustls_provider() {
|
|
let _ = rustls::crypto::ring::default_provider().install_default();
|
|
}
|
|
|
|
/// Generate a self-signed CA certificate and its key pair.
|
|
#[allow(dead_code)]
|
|
pub fn build_ca() -> (Certificate, KeyPair) {
|
|
let key_pair = KeyPair::generate().unwrap();
|
|
let mut params = CertificateParams::new(Vec::<String>::new()).unwrap();
|
|
params.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
|
|
let cert = params.self_signed(&key_pair).unwrap();
|
|
(cert, key_pair)
|
|
}
|
|
|
|
/// Generate a server certificate signed by `ca`, valid for `localhost`.
|
|
///
|
|
/// Returns `(cert_pem, key_pem)`.
|
|
#[allow(dead_code)]
|
|
pub fn build_server_cert(ca: &Certificate, ca_key: &KeyPair) -> (String, String) {
|
|
let key_pair = KeyPair::generate().unwrap();
|
|
let mut params = CertificateParams::new(vec!["localhost".to_string()]).unwrap();
|
|
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ServerAuth];
|
|
let cert = params.signed_by(&key_pair, ca, ca_key).unwrap();
|
|
(cert.pem(), key_pair.serialize_pem())
|
|
}
|
|
|
|
/// Generate a client authentication certificate signed by `ca`.
|
|
///
|
|
/// Returns `(cert_pem, key_pem)`.
|
|
#[allow(dead_code)]
|
|
pub fn build_client_cert(ca: &Certificate, ca_key: &KeyPair) -> (String, String) {
|
|
let key_pair = KeyPair::generate().unwrap();
|
|
let mut params = CertificateParams::new(Vec::<String>::new()).unwrap();
|
|
params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
|
|
let cert = params.signed_by(&key_pair, ca, ca_key).unwrap();
|
|
(cert.pem(), key_pair.serialize_pem())
|
|
}
|