Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions make/quality.mk
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,13 @@ lint\:python: _ensure-python-deps
@. .venv/bin/activate && cd sdks/python && python -c "import tomllib; config=tomllib.load(open('pyproject.toml','rb')); deps=config.get('project',{}).get('dependencies',[]); import sys; (print(f'ERROR: pyproject.toml has required dependencies: {deps}') or print('Move dependencies to [project.optional-dependencies] instead.') or sys.exit(1)) if deps else print('✓ No required dependencies')"

lint\:node: _ensure-node-deps
@echo "🔍 Checking Node SDK native import boundary..."
@if rg -n "from ['\"]\\.\\./native/|import\\(['\"]\\.\\./native/" \
sdks/node/lib --glob '*.ts' --glob '!native.ts'; then \
echo ""; \
echo "❌ Checked-in Node TypeScript must not import ../native/ outside lib/native.ts."; \
exit 1; \
fi
@echo "🔍 Linting Node SDK (TypeScript type check)..."
@cd sdks/node && npx tsc --noEmit

Expand Down
21 changes: 21 additions & 0 deletions sdks/c/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,27 @@ int main() {
}
```

### Runtime Image Management

```c
CBoxliteImageHandle* images = NULL;
char* json = NULL;

if (boxlite_runtime_images(runtime, &images, &error) == Ok) {
if (boxlite_image_pull(images, "alpine:latest", &json, &error) == Ok) {
printf("Pulled: %s\n", json);
boxlite_free_string(json);
}

if (boxlite_image_list(images, &json, &error) == Ok) {
printf("Images: %s\n", json);
boxlite_free_string(json);
}

boxlite_image_free(images);
}
```

---

## API Overview
Expand Down
46 changes: 46 additions & 0 deletions sdks/c/include/boxlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ typedef struct BoxHandle BoxHandle;
// Opaque handle for Runner API (auto-manages runtime)
typedef struct BoxRunner BoxRunner;

// Opaque handle to runtime image operations
typedef struct ImageHandle ImageHandle;

// Opaque handle to a BoxliteRuntime instance with associated Tokio runtime
typedef struct RuntimeHandle RuntimeHandle;

Expand All @@ -82,6 +85,8 @@ typedef struct FFIError {

typedef struct FFIError CBoxliteError;

typedef struct ImageHandle CBoxliteImageHandle;

typedef struct BoxHandle CBoxHandle;

// C-compatible command descriptor with all BoxCommand options.
Expand Down Expand Up @@ -153,6 +158,41 @@ enum BoxliteErrorCode boxlite_runtime_new(const char *home_dir,
CBoxliteRuntime **out_runtime,
CBoxliteError *out_error);

// Get an image handle for runtime-level image operations.
//
// # Arguments
// * `runtime` - Pointer to the active `CBoxliteRuntime`.
// * `out_handle` - Output parameter to store the created `CBoxliteImageHandle`.
// * `out_error` - Output parameter for error information.
//
// # Returns
// `BoxliteErrorCode::Ok` on success.
enum BoxliteErrorCode boxlite_runtime_images(CBoxliteRuntime *runtime,
CBoxliteImageHandle **out_handle,
CBoxliteError *out_error);

// Pull an image and return metadata as JSON.
//
// # Arguments
// * `handle` - Image handle.
// * `image_ref` - Image reference to pull.
// * `out_json` - Output pointer for JSON string. Caller must free with `boxlite_free_string`.
// * `out_error` - Output parameter for error information.
enum BoxliteErrorCode boxlite_image_pull(CBoxliteImageHandle *handle,
const char *image_ref,
char **out_json,
CBoxliteError *out_error);

// List cached images as JSON.
//
// # Arguments
// * `handle` - Image handle.
// * `out_json` - Output pointer for JSON string. Caller must free with `boxlite_free_string`.
// * `out_error` - Output parameter for error information.
enum BoxliteErrorCode boxlite_image_list(CBoxliteImageHandle *handle,
char **out_json,
CBoxliteError *out_error);

// Create a new box with the given options (JSON).
//
// # Arguments
Expand Down Expand Up @@ -488,6 +528,12 @@ void boxlite_simple_free(CBoxliteSimple *box_runner);
// * `handle` - Pointer to `CBoxHandle` to free.
void boxlite_box_free(CBoxHandle *handle);

// Free an image handle.
//
// # Arguments
// * `handle` - Pointer to `CBoxliteImageHandle` to free.
void boxlite_image_free(CBoxliteImageHandle *handle);

// Free a runtime handle.
//
// # Arguments
Expand Down
62 changes: 61 additions & 1 deletion sdks/c/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ use std::os::raw::{c_char, c_int, c_void};
// Import internal FFI types from shared layer
use boxlite_ffi::error::{BoxliteErrorCode, FFIError};
use boxlite_ffi::runner::{BoxRunner, ExecResult};
use boxlite_ffi::runtime::{BoxHandle, RuntimeHandle};
use boxlite_ffi::runtime::{BoxHandle, ImageHandle, RuntimeHandle};

// Define C-compatible type aliases for the C header
pub type CBoxliteRuntime = RuntimeHandle;
pub type CBoxHandle = BoxHandle;
pub type CBoxliteImageHandle = ImageHandle;
pub type CBoxliteSimple = BoxRunner;
pub type CBoxliteError = FFIError;
pub type CBoxliteExecResult = ExecResult;
Expand Down Expand Up @@ -75,6 +76,56 @@ pub unsafe extern "C" fn boxlite_runtime_new(
boxlite_ffi::ops::runtime_new(home_dir, registries_json, out_runtime, out_error)
}

/// Get an image handle for runtime-level image operations.
///
/// # Arguments
/// * `runtime` - Pointer to the active `CBoxliteRuntime`.
/// * `out_handle` - Output parameter to store the created `CBoxliteImageHandle`.
/// * `out_error` - Output parameter for error information.
///
/// # Returns
/// `BoxliteErrorCode::Ok` on success.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn boxlite_runtime_images(
runtime: *mut CBoxliteRuntime,
out_handle: *mut *mut CBoxliteImageHandle,
out_error: *mut CBoxliteError,
) -> BoxliteErrorCode {
boxlite_ffi::ops::runtime_images(runtime, out_handle, out_error)
}

/// Pull an image and return metadata as JSON.
///
/// # Arguments
/// * `handle` - Image handle.
/// * `image_ref` - Image reference to pull.
/// * `out_json` - Output pointer for JSON string. Caller must free with `boxlite_free_string`.
/// * `out_error` - Output parameter for error information.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn boxlite_image_pull(
handle: *mut CBoxliteImageHandle,
image_ref: *const c_char,
out_json: *mut *mut c_char,
out_error: *mut CBoxliteError,
) -> BoxliteErrorCode {
boxlite_ffi::ops::image_pull(handle, image_ref, out_json, out_error)
}

/// List cached images as JSON.
///
/// # Arguments
/// * `handle` - Image handle.
/// * `out_json` - Output pointer for JSON string. Caller must free with `boxlite_free_string`.
/// * `out_error` - Output parameter for error information.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn boxlite_image_list(
handle: *mut CBoxliteImageHandle,
out_json: *mut *mut c_char,
out_error: *mut CBoxliteError,
) -> BoxliteErrorCode {
boxlite_ffi::ops::image_list(handle, out_json, out_error)
}

/// Create a new box with the given options (JSON).
///
/// # Arguments
Expand Down Expand Up @@ -519,6 +570,15 @@ pub unsafe extern "C" fn boxlite_box_free(handle: *mut CBoxHandle) {
boxlite_ffi::ops::box_free(handle)
}

/// Free an image handle.
///
/// # Arguments
/// * `handle` - Pointer to `CBoxliteImageHandle` to free.
#[unsafe(no_mangle)]
pub unsafe extern "C" fn boxlite_image_free(handle: *mut CBoxliteImageHandle) {
boxlite_ffi::ops::image_free(handle)
}

/// Free a runtime handle.
///
/// # Arguments
Expand Down
3 changes: 2 additions & 1 deletion sdks/c/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ set(TEST_TARGETS
test_lifecycle
test_execute
test_errors
test_images
test_simple_api
test_streaming
test_memory
Expand Down Expand Up @@ -75,7 +76,7 @@ set_tests_properties(test_basic PROPERTIES LABELS "unit")

# Label integration tests (require VM)
set(INTEGRATION_TESTS
test_lifecycle test_execute test_errors test_simple_api
test_lifecycle test_execute test_errors test_images test_simple_api
test_streaming test_memory test_integration test_execute_cmd
)
foreach(TARGET ${INTEGRATION_TESTS})
Expand Down
52 changes: 52 additions & 0 deletions sdks/c/tests/test_images.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* BoxLite C SDK - Image Handle Integration Tests
*/

#include "test_runtime.h"
#include <string.h>

static void test_runtime_images_pull_and_list(void) {
printf("\nTEST: Runtime image handle pull/list\n");

CBoxliteError error = {0};
const char *temp_dir = "/tmp/boxlite-test-images";
reset_test_home(temp_dir);

CBoxliteRuntime *runtime = new_test_runtime(temp_dir, &error);
CBoxliteImageHandle *images = NULL;
BoxliteErrorCode code = boxlite_runtime_images(runtime, &images, &error);
assert(code == Ok);
assert(images != NULL);

char *pull_json = NULL;
code = boxlite_image_pull(images, "alpine:latest", &pull_json, &error);
assert(code == Ok);
assert(pull_json != NULL);
assert(strstr(pull_json, "\"reference\":\"alpine:latest\"") != NULL);
assert(strstr(pull_json, "\"config_digest\":\"sha256:") != NULL);
assert(strstr(pull_json, "\"layer_count\":") != NULL);
printf(" ✓ Pulled image: %s\n", pull_json);
boxlite_free_string(pull_json);

char *list_json = NULL;
code = boxlite_image_list(images, &list_json, &error);
assert(code == Ok);
assert(list_json != NULL);
assert(strstr(list_json, "alpine") != NULL);
assert(strstr(list_json, "\"cached_at\":\"") != NULL);
printf(" ✓ Listed images: %s\n", list_json);
boxlite_free_string(list_json);

boxlite_image_free(images);
boxlite_runtime_free(runtime);
}

int main(void) {
printf("BoxLite C SDK - Image Handle Tests\n");
printf("==================================\n");

test_runtime_images_pull_and_list();

printf("\n✅ All image handle tests passed!\n");
return 0;
}
25 changes: 25 additions & 0 deletions sdks/go/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,31 @@ func main() {
}
```

### Runtime Image Management

```go
ctx := context.Background()
images, err := rt.Images()
if err != nil {
log.Fatal(err)
}
defer images.Close()

pull, err := images.Pull(ctx, "alpine:latest")
if err != nil {
log.Fatal(err)
}
fmt.Println(pull.Reference, pull.ConfigDigest, pull.LayerCount)

cached, err := images.List(ctx)
if err != nil {
log.Fatal(err)
}
for _, image := range cached {
fmt.Println(image.Repository, image.Tag, image.ID)
}
```

## Box Options

- `WithNetwork(boxlite.NetworkSpec{Mode: boxlite.NetworkModeEnabled, AllowNet: []string{"api.openai.com"}})` restricts outbound traffic while keeping networking enabled.
Expand Down
Loading
Loading