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.

9 changes: 9 additions & 0 deletions apps/api/src/box/dto/create-box.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,15 @@ export class CreateBoxDto {
@IsBoolean()
autoResume?: boolean

@ApiPropertyOptional({
description: 'Capture init stdout/stderr as CRI records in the box-managed log path',
example: true,
})
@IsOptional()
@IsBoolean()
captureLogs?: boolean


@ApiPropertyOptional({
description: 'Array of volumes to attach to the box',
type: [BoxVolume],
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/box/entities/box.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ export class Box {
@Column({ nullable: true })
networkAllowList?: string

@Column({ default: false, type: 'boolean' })
captureLogs = false


@Column('jsonb', { nullable: true })
labels: { [key: string]: string }

Expand Down
8 changes: 6 additions & 2 deletions apps/api/src/box/runner-adapter/runnerAdapter.v0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
DefaultApi,
UpdateNetworkSettingsDTO,
RecoverBoxDTO,
CreateBoxDTO,
} from '@boxlite-ai/runner-api-client'
import { Box } from '../entities/box.entity'
import { BoxState } from '../enums/box-state.enum'
Expand Down Expand Up @@ -250,7 +251,7 @@ export class RunnerAdapterV0 implements RunnerAdapter {
}

async createBox(box: Box, metadata?: { [key: string]: string }): Promise<StartBoxResponse | undefined> {
const response = await this.boxApiClient.create({
const payload: CreateBoxDTO = {
id: box.id,
image: box.image ?? '',
osUser: box.osUser,
Expand All @@ -262,10 +263,12 @@ export class RunnerAdapterV0 implements RunnerAdapter {
networkBlockAll: box.networkBlockAll,
networkAllowList: box.networkAllowList,
metadata,
captureLogs: box.captureLogs,
authToken: box.authToken,
organizationId: box.organizationId,
regionId: box.region,
})
}
const response = await this.boxApiClient.create(payload)

if (!response?.data?.daemonVersion) {
return undefined
Expand Down Expand Up @@ -331,6 +334,7 @@ export class RunnerAdapterV0 implements RunnerAdapter {
networkBlockAll: box.networkBlockAll,
networkAllowList: box.networkAllowList,
errorReason: box.errorReason,
captureLogs: box.captureLogs,
}
await this.boxApiClient.recover(box.id, recoverBoxDTO)
}
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/box/runner-adapter/runnerAdapter.v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export class RunnerAdapterV2 implements RunnerAdapter {
networkBlockAll: box.networkBlockAll,
networkAllowList: box.networkAllowList,
metadata,
captureLogs: box.captureLogs,
authToken: box.authToken,
organizationId: box.organizationId,
regionId: box.region,
Expand Down Expand Up @@ -195,6 +196,7 @@ export class RunnerAdapterV2 implements RunnerAdapter {
networkBlockAll: box.networkBlockAll,
networkAllowList: box.networkAllowList,
errorReason: box.errorReason,
captureLogs: box.captureLogs,
}
await this.jobService.createJob(null, JobType.RECOVER_BOX, this.runner.id, ResourceType.BOX, box.id, recoverBoxDTO)

Expand Down
10 changes: 9 additions & 1 deletion apps/api/src/box/services/box.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,11 @@ export class BoxService {
} else if (image) {
// No volumes requested — try to claim a pre-warmed box matching this image/spec
// before creating a fresh one.
const skipWarmPool = (await this.redis.exists(`warm-pool:skip:${image}`)) === 1
// Log capture must be configured before the container init process starts;
// a pre-warmed box cannot be retrofitted without losing early output.
const needsFreshBoxForLogCapture = createBoxDto.captureLogs
const skipWarmPool =
needsFreshBoxForLogCapture || (await this.redis.exists(`warm-pool:skip:${image}`)) === 1
if (!skipWarmPool) {
const warmPoolBox = await this.warmPoolService.fetchWarmPoolBox({
organizationId: organization.id,
Expand Down Expand Up @@ -236,6 +240,8 @@ export class BoxService {
box.networkAllowList = this.resolveNetworkAllowList(createBoxDto.networkAllowList)
}

box.captureLogs = createBoxDto.captureLogs || false

const lifecyclePolicy = this.resolveLifecyclePolicy({
autoPause: createBoxDto.autoPause,
autoDelete: createBoxDto.autoDelete,
Expand Down Expand Up @@ -310,6 +316,8 @@ export class BoxService {
updateData.networkAllowList = this.resolveNetworkAllowList(createBoxDto.networkAllowList)
}

updateData.captureLogs = createBoxDto.captureLogs || false

if (!warmPoolBox.runnerId) {
throw new BoxError('Runner not found for warm pool box')
}
Expand Down
5 changes: 5 additions & 0 deletions apps/api/src/boxlite-rest/dto/create-box.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,11 @@ export class CreateBoxDto {
@IsBoolean()
auto_resume?: boolean

@IsOptional()
@IsBoolean()
capture_logs?: boolean


@IsOptional()
@ValidateNested()
@Type(() => NetworkSpecDto)
Expand Down
1 change: 1 addition & 0 deletions apps/api/src/boxlite-rest/mappers/box-to-box.mapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export function createBoxToCreateBox(dto: RestCreateBoxDto, target?: string): Cr
createDto.memory = dto.memory_mib ? Math.ceil(dto.memory_mib / 1024) : undefined
createDto.disk = dto.disk_size_gb
createDto.target = target
createDto.captureLogs = dto.capture_logs
Comment thread
xhebox marked this conversation as resolved.
createDto.autoPause = dto.auto_pause
createDto.autoDelete = dto.auto_delete
createDto.autoResume = dto.auto_resume
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MigrationInterface, QueryRunner } from 'typeorm'

export class AddBoxLogCapture1762539090000 implements MigrationInterface {
name = 'AddBoxLogCapture1762539090000'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "box" ADD "captureLogs" boolean NOT NULL DEFAULT false`)
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "box" DROP COLUMN "captureLogs"`)
}
}
1 change: 1 addition & 0 deletions apps/libs/runner-api-client/src/models/create-box-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { RegistryDTO } from './registry-dto';

export interface CreateBoxDTO {
'authToken'?: string;
'captureLogs'?: boolean;
'cpuQuota'?: number;
'entrypoint'?: Array<string>;
'env'?: { [key: string]: string; };
Expand Down
1 change: 1 addition & 0 deletions apps/libs/runner-api-client/src/models/recover-box-dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import type { DtoVolumeDTO } from './dto-volume-dto';

export interface RecoverBoxDTO {
'backupErrorReason'?: string;
'captureLogs'?: boolean;
'cpuQuota'?: number;
'env'?: { [key: string]: string; };
'errorReason': string;
Expand Down
6 changes: 6 additions & 0 deletions apps/runner/pkg/api/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -1308,6 +1308,9 @@ const docTemplate = `{
"authToken": {
"type": "string"
},
"captureLogs": {
"type": "boolean"
},
"cpuQuota": {
"type": "integer",
"minimum": 1
Expand Down Expand Up @@ -1487,6 +1490,9 @@ const docTemplate = `{
"backupErrorReason": {
"type": "string"
},
"captureLogs": {
"type": "boolean"
},
"cpuQuota": {
"type": "integer",
"minimum": 1
Expand Down
6 changes: 6 additions & 0 deletions apps/runner/pkg/api/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -1216,6 +1216,9 @@
"authToken": {
"type": "string"
},
"captureLogs": {
"type": "boolean"
},
"cpuQuota": {
"type": "integer",
"minimum": 1
Expand Down Expand Up @@ -1381,6 +1384,9 @@
"backupErrorReason": {
"type": "string"
},
"captureLogs": {
"type": "boolean"
},
"cpuQuota": {
"type": "integer",
"minimum": 1
Expand Down
4 changes: 4 additions & 0 deletions apps/runner/pkg/api/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ definitions:
properties:
authToken:
type: string
captureLogs:
type: boolean
cpuQuota:
minimum: 1
type: integer
Expand Down Expand Up @@ -175,6 +177,8 @@ definitions:
properties:
backupErrorReason:
type: string
captureLogs:
type: boolean
cpuQuota:
minimum: 1
type: integer
Expand Down
2 changes: 2 additions & 0 deletions apps/runner/pkg/api/dto/box.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type CreateBoxDTO struct {
Volumes []VolumeDTO `json:"volumes,omitempty"`
NetworkBlockAll *bool `json:"networkBlockAll,omitempty"`
NetworkAllowList *string `json:"networkAllowList,omitempty"`
CaptureLogs bool `json:"captureLogs,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
AuthToken *string `json:"authToken,omitempty"`
OtelEndpoint *string `json:"otelEndpoint,omitempty"`
Expand Down Expand Up @@ -53,6 +54,7 @@ type RecoverBoxDTO struct {
Volumes []VolumeDTO `json:"volumes,omitempty"`
NetworkBlockAll *bool `json:"networkBlockAll,omitempty"`
NetworkAllowList *string `json:"networkAllowList,omitempty"`
CaptureLogs bool `json:"captureLogs,omitempty"`
ErrorReason string `json:"errorReason" validate:"required"`
} // @name RecoverBoxDTO

Expand Down
3 changes: 3 additions & 0 deletions apps/runner/pkg/boxlite/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ func (c *Client) Create(ctx context.Context, boxDto dto.CreateBoxDTO) (string, s
opts = append(opts, boxlite.WithEntrypoint(boxDto.Entrypoint...))
}

if boxDto.CaptureLogs {
opts = append(opts, boxlite.WithCaptureLogs(true))
}
volumeMounts, err := c.getVolumeMounts(ctx, boxDto.Volumes)
if err != nil {
return "", "", err
Expand Down
1 change: 1 addition & 0 deletions apps/runner/pkg/boxlite/stubs.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ func (c *Client) RecoverBox(ctx context.Context, boxId string, recoverDto dto.Re
NetworkBlockAll: recoverDto.NetworkBlockAll,
NetworkAllowList: recoverDto.NetworkAllowList,
FromVolumeId: recoverDto.FromVolumeId,
CaptureLogs: recoverDto.CaptureLogs,
}

_, _, err := c.Create(ctx, createDto)
Expand Down
4 changes: 4 additions & 0 deletions openapi/box.openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1652,6 +1652,10 @@ components:
description: |
Secret placeholder rules for outbound HTTP(S) requests.
Matching requests replace `placeholder` with `value` on the host side.
capture_logs:
type: boolean
description: Capture init stdout/stderr as CRI records in the box-managed log path
default: false
detach:
type: boolean
description: Box survives parent process exit
Expand Down
2 changes: 2 additions & 0 deletions sdks/c/include/boxlite.h
Original file line number Diff line number Diff line change
Expand Up @@ -715,6 +715,8 @@ void boxlite_options_set_auto_resume_enabled(CBoxliteOptions *opts, int val);

void boxlite_options_set_detach(CBoxliteOptions *opts, int val);

void boxlite_options_set_capture_logs(CBoxliteOptions *opts, int val);

// Apply a `CAdvancedBoxOptions` (security, mount isolation, health check) to a
// `CBoxliteOptions`. Clones the advanced configuration into the box options —
// the caller retains ownership of `advanced_opts` and is responsible for
Expand Down
13 changes: 13 additions & 0 deletions sdks/c/src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ pub unsafe extern "C" fn boxlite_options_set_detach(opts: *mut CBoxliteOptions,
options_set_detach(opts, val)
}

#[unsafe(no_mangle)]
pub unsafe extern "C" fn boxlite_options_set_capture_logs(opts: *mut CBoxliteOptions, val: c_int) {
options_set_capture_logs(opts, val)
}

/// Apply a `CAdvancedBoxOptions` (security, mount isolation, health check) to a
/// `CBoxliteOptions`. Clones the advanced configuration into the box options —
/// the caller retains ownership of `advanced_opts` and is responsible for
Expand Down Expand Up @@ -475,6 +480,14 @@ pub unsafe fn options_set_detach(handle: *mut OptionsHandle, val: c_int) {
}
}

pub unsafe fn options_set_capture_logs(handle: *mut OptionsHandle, val: c_int) {
unsafe {
if !handle.is_null() {
(*handle).options.capture_logs = val != 0;
}
}
}

pub unsafe fn options_set_entrypoint(
handle: *mut OptionsHandle,
args: *const *const c_char,
Expand Down
47 changes: 28 additions & 19 deletions sdks/go/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,25 +171,26 @@ type Secret struct {
}

type boxConfig struct {
name string
cpus int
memoryMiB int
diskSizeGB int
rootfsPath string
env [][2]string
volumes []volumeEntry
ports []PortSpec
workDir string
entrypoint []string
cmd []string
autoRemove *bool
autoPause *uint32
autoDelete *uint32
autoResume *bool
detach *bool
network *NetworkSpec
secrets []Secret
advanced *AdvancedBoxOptions // nil = runtime defaults; non-nil = caller-owned advanced opts applied via boxlite_options_set_advanced
name string
cpus int
memoryMiB int
diskSizeGB int
rootfsPath string
env [][2]string
volumes []volumeEntry
ports []PortSpec
workDir string
entrypoint []string
cmd []string
autoRemove *bool
autoPause *uint32
autoDelete *uint32
autoResume *bool
detach *bool
network *NetworkSpec
secrets []Secret
captureLogs bool
advanced *AdvancedBoxOptions // nil = runtime defaults; non-nil = caller-owned advanced opts applied via boxlite_options_set_advanced
}

type volumeEntry struct {
Expand Down Expand Up @@ -327,6 +328,11 @@ func WithDetach(v bool) BoxOption {
return func(c *boxConfig) { c.detach = &v }
}

// WithCaptureLogs captures init stdout/stderr as CRI records in the box-managed log path.
func WithCaptureLogs(v bool) BoxOption {
return func(c *boxConfig) { c.captureLogs = v }
}

// buildAndFreeCOptions runs buildCOptions, immediately frees the C
// handle on success, and returns just the error. Exists for unit
// tests in `_test.go` files, which Go forbids from using cgo
Expand Down Expand Up @@ -502,6 +508,9 @@ func buildCOptions(image string, cfg *boxConfig) (*C.CBoxliteOptions, error) {
if cfg.detach != nil {
C.boxlite_options_set_detach(cOpts, boolToCInt(*cfg.detach))
}
if cfg.captureLogs {
C.boxlite_options_set_capture_logs(cOpts, C.int(1))
}
if cfg.advanced != nil && cfg.advanced.handle != nil {
// Clone the caller-owned advanced options (security, …) onto the box.
// The Go-side handle stays caller-owned; the box has its own copy after
Expand Down
1 change: 1 addition & 0 deletions sdks/node/lib/native-contracts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ export interface JsBoxOptions {
security?: JsSecurityOptions;
healthCheck?: JsHealthCheckOptions;
secrets?: JsSecret[];
captureLogs?: boolean;
}

export interface JsOptions {
Expand Down
Loading
Loading