Skip to content
Merged
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
5 changes: 3 additions & 2 deletions docs/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,9 @@ supported call for a particular sandbox, and Kit returns that error.

Set Daytona's target with `region` on the client. A placement block on the
create request is rejected. Modal accepts cloud and region on the create
request, and rejects a placement target. Blaxel requires `region` on the
client for create and rejects per-request placement.
request, and rejects a placement target. Blaxel create needs a region via
`Config.region` or `BL_REGION`; Go requires `Config.Region`, and TypeScript
accepts either. Per-request placement is rejected.

Closing the client closes your connection to the provider. The sandboxes keep
running. Closing a handle leaves the sandbox as it is. You can keep using the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS

- Other network settings (egress mode, CIDRs, private network, proxies) are rejected.
- Port numbers must be in range.
- Blaxel reserves ports 80, 443, and 8080; this example uses 3000 and 3001.

## Run

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func main() {
Source: &sandbox.SandboxSource{Kind: &sandbox.Image{Image: &sandbox.ImageSource{Reference: "blaxel/base-image:latest"}}},
Network: &sandbox.NetworkConfig{
Ports: []*sandbox.PortBinding{
{Port: 443, Transport: sandbox.PortTransportTLS},
{Port: 8080, Transport: sandbox.PortTransportPlain},
{Port: 3000, Transport: sandbox.PortTransportTLS},
{Port: 3001, Transport: sandbox.PortTransportPlain},
},
},
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Maps `network.ports` to Blaxel runtime ports. Default/plain → TCP, TLS → TLS

- Other network settings (egress mode, CIDRs, private network, proxies) are rejected.
- Port numbers must be in range.
- Blaxel reserves ports 80, 443, and 8080; this example uses 3000 and 3001.

## Run

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ const sandbox = await client.create({
source: { image: { reference: "blaxel/base-image:latest" } },
network: {
ports: [
{ port: 443, transport: PortTransport.Tls },
{ port: 8080, transport: PortTransport.Plain },
{ port: 3000, transport: PortTransport.Tls },
{ port: 3001, transport: PortTransport.Plain },
],
},
});
Expand Down
8 changes: 5 additions & 3 deletions sdks/go/providers/blaxel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ rejected. `Scope` fields other than `Workspace` are rejected.

Create from a registry image with an explicit name. You can set labels,
environment variables, memory, ports, outbound domains, maximum lifetime, and
idle terminate.
idle terminate. Max-age TTL must be at least 5m; idle TTL must be at least 1m.
Create provisioning timeout is not mapped (the Go Blaxel SDK has no create
timeout option).

On the handle: `Get`, `Refresh`, `Delete`, and `Close`. `Get` and `Refresh` look
up the sandbox by name. `Attach`, `Start`, `Stop`, `Pause`, `Archive`, and
Expand All @@ -83,8 +85,8 @@ exit code is success for the SDK—read `GetExitCode`. Blaxel returns stdout and
stderr. Omit or zero `Timeout` for no Blaxel command limit. Command timeouts
must be whole seconds. Cancel the context to stop waiting.

Memory is allocated in MiB. Blaxel derives CPU from memory, so `cpu_cores` and
disk MiB overrides are rejected.
Memory is allocated in MiB and must be at least 256 when set. Blaxel derives
CPU from memory, so `cpu_cores` and disk MiB overrides are rejected.

## License

Expand Down
4 changes: 2 additions & 2 deletions sdks/go/providers/blaxel/creation.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ func (c *creation) lifetime() error {
}
config := cloneMessage(c.request.Lifetime)
if config.MaximumLifetime != nil {
formatted, err := formatBlaxelDuration(config.MaximumLifetime.AsDuration())
formatted, err := formatBlaxelDuration(config.MaximumLifetime.AsDuration(), 5*time.Minute)
if err != nil {
return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeMaximumLifetime, err.Error())
}
Expand Down Expand Up @@ -136,7 +136,7 @@ func mapIdleTerminate(action *sandbox.AutomaticAction) (*blaxel.ExpirationPolicy
if action.After == nil {
return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, "duration is required")
}
formatted, err := formatBlaxelDuration(action.After.AsDuration())
formatted, err := formatBlaxelDuration(action.After.AsDuration(), time.Minute)
if err != nil {
return nil, providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldLifetimeIdleTerminate, err.Error())
}
Expand Down
3 changes: 3 additions & 0 deletions sdks/go/providers/blaxel/provider_checks.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ func validateProviderCreate(request *sandbox.CreateOptions) error {
if resources.CpuLimitCores != nil {
return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesCpuLimitCores, "Blaxel resource limits are not mapped")
}
if resources.MemoryMib != nil && *resources.MemoryMib < 256 {
return providerCheckError(sandbox.ErrorKindInvalidArgument, sandbox.CreateFieldResourcesMemoryMib, "resources.memory_mib is outside supported values")
}
if resources.MemoryLimitMib != nil {
return providerCheckError(sandbox.ErrorKindUnsupported, sandbox.CreateFieldResourcesMemoryLimitMib, "Blaxel resource limits are not mapped")
}
Expand Down
6 changes: 5 additions & 1 deletion sdks/go/providers/blaxel/provider_mappings.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,14 @@ func mapAllocatedResources(source *blaxel.Sandbox) (*sandbox.Resources, error) {
}

// formatBlaxelDuration formats a whole-second duration as a Blaxel TTL string.
func formatBlaxelDuration(value time.Duration) (string, error) {
// minimum is the Blaxel floor for that policy (max-age TTL: 5m, idle TTL: 1m).
func formatBlaxelDuration(value, minimum time.Duration) (string, error) {
if value <= 0 || value%time.Second != 0 {
return "", fmt.Errorf("duration must be a positive whole number of seconds")
}
if value < minimum {
return "", fmt.Errorf("duration must be at least %s", minimum)
}
seconds := int64(value / time.Second)
switch {
case seconds%86400 == 0:
Expand Down
29 changes: 29 additions & 0 deletions sdks/go/providers/blaxel/rejection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,35 @@ func TestRejectedCreateSettings(t *testing.T) {
kind: sandbox.ErrorKindInvalidArgument,
field: sandbox.CreateFieldLifetimeMaximumLifetime,
},
{
name: "maximum lifetime below floor",
request: namedImage(&sandbox.CreateOptions{
Lifetime: &sandbox.LifetimePolicy{MaximumLifetime: sandbox.Duration(4 * time.Minute)},
}),
kind: sandbox.ErrorKindInvalidArgument,
field: sandbox.CreateFieldLifetimeMaximumLifetime,
},
{
name: "idle terminate below floor",
request: namedImage(&sandbox.CreateOptions{
Lifetime: &sandbox.LifetimePolicy{
IdleTerminate: &sandbox.AutomaticAction{
Mode: sandbox.PolicyModeAfter,
After: sandbox.Duration(30 * time.Second),
},
},
}),
kind: sandbox.ErrorKindInvalidArgument,
field: sandbox.CreateFieldLifetimeIdleTerminate,
},
{
name: "memory below floor",
request: namedImage(&sandbox.CreateOptions{
Resources: &sandbox.Resources{MemoryMib: sandbox.Value(uint64(255))},
}),
kind: sandbox.ErrorKindInvalidArgument,
field: sandbox.CreateFieldResourcesMemoryMib,
},
} {
t.Run(tc.name, func(t *testing.T) {
_, err := planCreate(tc.request, "us-pdx-1")
Expand Down
27 changes: 15 additions & 12 deletions sdks/go/providers/blaxel/sandbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,26 +87,29 @@ func TestMapSandboxInfoUsesNameAsID(t *testing.T) {

func TestFormatBlaxelDuration(t *testing.T) {
for _, tc := range []struct {
in time.Duration
want string
ok bool
in time.Duration
minimum time.Duration
want string
ok bool
}{
{30 * time.Second, "30s", true},
{5 * time.Minute, "5m", true},
{2 * time.Hour, "2h", true},
{48 * time.Hour, "2d", true},
{1500 * time.Millisecond, "", false},
{0, "", false},
{30 * time.Second, time.Second, "30s", true},
{5 * time.Minute, 5 * time.Minute, "5m", true},
{2 * time.Hour, 5 * time.Minute, "2h", true},
{48 * time.Hour, 5 * time.Minute, "2d", true},
{30 * time.Second, time.Minute, "", false},
{4 * time.Minute, 5 * time.Minute, "", false},
{1500 * time.Millisecond, time.Second, "", false},
{0, time.Second, "", false},
} {
got, err := formatBlaxelDuration(tc.in)
got, err := formatBlaxelDuration(tc.in, tc.minimum)
if tc.ok {
if err != nil || got != tc.want {
t.Fatalf("%v: got %q err %v", tc.in, got, err)
t.Fatalf("%v (min %v): got %q err %v", tc.in, tc.minimum, got, err)
}
continue
}
if err == nil {
t.Fatalf("%v: expected error", tc.in)
t.Fatalf("%v (min %v): expected error", tc.in, tc.minimum)
}
}
}
6 changes: 3 additions & 3 deletions sdks/typescript/providers/blaxel/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ than `workspace` are rejected.

**Create** requires a nonblank `name` and an image source. Optional memory
(MiB; Blaxel CPU follows memory), labels, environment variables, ports, outbound
domains, max-age TTL, idle-delete policy, and a create timeout (1–50 whole
seconds). Creation calls native `SandboxInstance.create`, not
`createIfNotExists`.
domains, max-age TTL (at least 5m), idle-delete policy (at least 1m; explicit
disable is rejected), and a create timeout (1–50 whole seconds). Creation
calls native `SandboxInstance.create`, not `createIfNotExists`.

**Lifecycle** on the handle: `get` / `refresh` (lookup by name), `delete`, and
`close`. `close` does not delete the sandbox. Start, stop, pause, archive,
Expand Down
28 changes: 27 additions & 1 deletion sdks/typescript/providers/blaxel/src/blaxel.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ import {
ErrorKind,
PolicyMode,
PortTransport,
SandboxState,
WaitCondition,
ConfigFieldAuthApiKeyKey,
ConfigFieldEndpoint,
CreateFieldEnvironment,
CreateFieldLifetimeIdleTerminate,
CreateFieldLifetimeMaximumLifetime,
CreateFieldName,
CreateFieldResourcesMemoryMib,
Expand Down Expand Up @@ -181,6 +183,24 @@ describe("Blaxel mappings", () => {
);
});

it("rejects disabling idle terminate", () => {
expect(() =>
mapCreateRequest(
{
name: "example",
source: { image: { reference: "blaxel/base-image:latest" } },
lifetime: { idleTerminate: { mode: PolicyMode.Disabled } },
},
{ scope: { workspace: "ws" } },
),
).toThrowError(
expect.objectContaining({
kind: ErrorKind.Unsupported,
field: CreateFieldLifetimeIdleTerminate,
}),
);
});

it("classifies native ResponseError status codes", () => {
const native = new ResponseError(
new Response(null, { status: 404, statusText: "Not Found" }),
Expand Down Expand Up @@ -333,8 +353,14 @@ describe("Blaxel provider orchestration", () => {
scope: { workspace: "ws" },
});
if (backend.delete === undefined) throw new Error("delete is missing");
await backend.delete("doomed", {}, {});
const info = await backend.delete("doomed", {}, {});
expect(deleted).toEqual(["doomed"]);
expect(info).toMatchObject({
id: "doomed",
state: SandboxState.Deleted,
terminal: true,
providerState: "TERMINATED",
});
});

it("does not create a sandbox after the signal aborts during mapping", async () => {
Expand Down
8 changes: 7 additions & 1 deletion sdks/typescript/providers/blaxel/src/blaxel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ExecFieldTimeout,
FileFieldParents,
FileType,
SandboxState,
type FileInfo,
type OperationOptions,
type Provider,
Expand Down Expand Up @@ -190,7 +191,12 @@ class BlaxelBackend extends BackendBase implements Backend {
throwIfAborted(options.signal);
const info = mapSandboxInfo(sandbox);
await sandbox.delete();
return info;
return {
...info,
state: SandboxState.Deleted,
terminal: true,
providerState: "TERMINATED",
};
} catch (error) {
throw mapBlaxelError(error, "delete");
}
Expand Down
6 changes: 5 additions & 1 deletion sdks/typescript/providers/blaxel/src/mappings/mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ConfigFieldRegion,
CreateFieldIsolation,
CreateFieldLifetime,
CreateFieldLifetimeIdleTerminate,
CreateFieldLifetimeIdleTerminateAfter,
CreateFieldLifetimeIdleTerminateMode,
CreateFieldLifetimeMaximumLifetime,
Expand Down Expand Up @@ -234,7 +235,10 @@ function mapLifetime(r: CreateOptions, p: SandboxCreateConfiguration): void {
if (v.idleTerminate) {
const mode = v.idleTerminate.mode ?? PolicyMode.Default;
if (mode === PolicyMode.Disabled) {
// Explicit disable: omit idle policy.
unsupported(
CreateFieldLifetimeIdleTerminate,
"disabling idle terminate is not representable",
);
} else if (mode === PolicyMode.After) {
if (v.idleTerminate.after === undefined)
invalid(CreateFieldLifetimeIdleTerminateAfter, "duration is required");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ function mapSandboxState(status: string): SandboxState | undefined {
return SandboxState.Error;
case "TERMINATED":
case "DELETED":
return SandboxState.Terminated;
case "DEACTIVATED":
return SandboxState.Deleted;
default:
return undefined;
}
Expand Down
1 change: 1 addition & 0 deletions specs/providers/blaxel.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ checks:
- { path: placement, unsupported: true }
- { path: resources.cpu_cores, unsupported: true }
- { path: resources.cpu_limit_cores, unsupported: true }
- { path: resources.memory_mib, minimum: 256 }
- { path: resources.memory_limit_mib, unsupported: true }
- { path: resources.disk_mib, unsupported: true }
client:
Expand Down
3 changes: 2 additions & 1 deletion website/content/docs/configuration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ if err != nil {
| ---------- | ------------------------------------------------------------------------------------------- |
| `provider` | Provider constructor |
| `auth` | One of `apiKey`, `tokenPair`, `bearerToken`, `oauthRefresh`. Omit to use provider env creds |
| `scope` | App, environment, or organization |
| `scope` | App, environment, organization, or workspace |
| `endpoint` | Provider endpoint URL override |
| `region` | Target / default placement |
| `timeout` | Default deadline for calls that omit their own |
Expand All @@ -51,6 +51,7 @@ if err != nil {
Provider setup: [Daytona](/docs/daytona).
</ForProvider>
<ForProvider id="modal">Provider setup: [Modal](/docs/modal).</ForProvider>
<ForProvider id="blaxel">Provider setup: [Blaxel](/docs/blaxel).</ForProvider>

## Client

Expand Down
Loading