Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds filtering sandboxes by state in the SDKs #564

Open
wants to merge 19 commits into
base: beta
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 9 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
7 changes: 7 additions & 0 deletions .changeset/stupid-pens-judge.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@e2b/python-sdk': minor
'e2b': minor
'@e2b/cli': minor
---

Adds filtering sandboxes by state in the SDKs and display of the state in the CLI
32 changes: 32 additions & 0 deletions apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx
Copy link
Member

@mlejva mlejva Feb 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mishushakov will deleting paused sandboxes be included in a separate PR? I'm asking because the new docs addition doesn't mention anything about deleting paused sandboxes.

Other than that, this looks good to me on the docs level

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this will be a separate PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I have decided to add it here as well to avoid merge conflicts

Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,38 @@ print('Sandbox resumed', same_sbx.sandbox_id) # $HighlightLine
```
</CodeGroup>

## 4. Listing paused sandboxes
You can list all paused sandboxes by calling the `Sandbox.list` method by supplying the `state` parameter.

<CodeGroup>
```js
import { Sandbox } from '@e2b/code-interpreter'
// or use Core: https://github.com/e2b-dev/e2b
// import { Sandbox } from 'e2b'
//
// or use Desktop: https://github.com/e2b-dev/desktop
// import { Sandbox } from '@e2b/desktop'

// List all paused sandboxes
const sandboxes = await Sandbox.list({ state: 'paused' })
console.log('Paused sandboxes', sandboxes)
```
```python
from e2b import Sandbox
from e2b.api.client.models.get_sandboxes_state import GetSandboxesState

# or use Core: https://github.com/e2b-dev/e2b
# from e2b import Sandbox
#
# or use Desktop: https://github.com/e2b-dev/desktop
# from e2b_desktop import Sandbox

# List all paused sandboxes
sandboxes = Sandbox.list(state=GetSandboxesState.PAUSED)
print('Paused sandboxes', sandboxes)
```
</CodeGroup>

## Sandbox's timeout
When you resume a sandbox, the sandbox's timeout is reset to the default timeout of an E2B sandbox - 5 minutes.

Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/commands/sandbox/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export const listCommand = new commander.Command('list')
{ name: 'alias', alignment: 'left', title: 'Alias' },
{ name: 'startedAt', alignment: 'left', title: 'Started at' },
{ name: 'endAt', alignment: 'left', title: 'End at' },
{ name: 'state', alignment: 'left', title: 'State' },
{ name: 'cpuCount', alignment: 'left', title: 'vCPUs' },
{ name: 'memoryMB', alignment: 'left', title: 'RAM MiB' },
{ name: 'metadata', alignment: 'left', title: 'Metadata' },
Expand All @@ -39,6 +40,8 @@ export const listCommand = new commander.Command('list')
sandboxID: `${sandbox.sandboxID}-${sandbox.clientID}`,
startedAt: new Date(sandbox.startedAt).toLocaleString(),
endAt: new Date(sandbox.endAt).toLocaleString(),
state:
sandbox.state.charAt(0).toUpperCase() + sandbox.state.slice(1), // capitalize
metadata: JSON.stringify(sandbox.metadata),
}))
.sort(
Expand Down
7 changes: 7 additions & 0 deletions packages/js-sdk/src/api/schema.gen.ts

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

49 changes: 32 additions & 17 deletions packages/js-sdk/src/sandbox/sandboxApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,18 @@ import { NotFoundError, TemplateError } from '../errors'
export interface SandboxApiOpts
extends Partial<
Pick<ConnectionOpts, 'apiKey' | 'debug' | 'domain' | 'requestTimeoutMs'>
> { }
> {}

export interface SandboxListOpts extends SandboxApiOpts {
/**
* Filter the list of sandboxes by metadata, e.g. `{"key": "value"}`, if there are multiple filters they are combined with AND.
*/
filters?: Record<string, string>

/**
* Filter the list of sandboxes by state.
*/
state?: 'running' | 'paused'
}

/**
Expand Down Expand Up @@ -46,10 +51,15 @@ export interface SandboxInfo {
* Sandbox start time.
*/
startedAt: Date

/**
* Sandbox state.
*/
state: 'running' | 'paused'
}

export class SandboxApi {
protected constructor() { }
protected constructor() {}

/**
* Kill the sandbox specified by sandbox ID.
Expand Down Expand Up @@ -94,21 +104,26 @@ export class SandboxApi {
*
* @returns list of running sandboxes.
*/
static async list(
opts?: SandboxListOpts): Promise<SandboxInfo[]> {
static async list(opts?: SandboxListOpts): Promise<SandboxInfo[]> {
const config = new ConnectionConfig(opts)
const client = new ApiClient(config)

let query = undefined
if (opts?.filters) {
const encodedPairs: Record<string, string> = Object.fromEntries(Object.entries(opts.filters).map(([key, value]) => [encodeURIComponent(key),encodeURIComponent(value)]))
const encodedPairs: Record<string, string> = Object.fromEntries(
Object.entries(opts.filters).map(([key, value]) => [
encodeURIComponent(key),
encodeURIComponent(value),
])
)
query = new URLSearchParams(encodedPairs).toString()
}

const res = await client.api.GET('/sandboxes', {
params: {
query: {query},
},
params: {
query: { query },
state: opts?.state,
},
signal: config.getSignal(opts?.requestTimeoutMs),
})

Expand All @@ -127,6 +142,7 @@ export class SandboxApi {
...(sandbox.alias && { name: sandbox.alias }),
metadata: sandbox.metadata ?? {},
startedAt: new Date(sandbox.startedAt),
state: sandbox.state,
})) ?? []
)
}
Expand Down Expand Up @@ -207,13 +223,13 @@ export class SandboxApi {
}

/**
* Pause the sandbox specified by sandbox ID.
*
* @param sandboxId sandbox ID.
* @param opts connection options.
*
* @returns `true` if the sandbox got paused, `false` if the sandbox was already paused.
*/
* Pause the sandbox specified by sandbox ID.
*
* @param sandboxId sandbox ID.
* @param opts connection options.
*
* @returns `true` if the sandbox got paused, `false` if the sandbox was already paused.
*/
protected static async pauseSandbox(
sandboxId: string,
opts?: SandboxApiOpts
Expand Down Expand Up @@ -247,7 +263,6 @@ export class SandboxApi {
return true
}


protected static async resumeSandbox(
sandboxId: string,
timeoutMs: number,
Expand Down Expand Up @@ -324,7 +339,7 @@ export class SandboxApi {
)
throw new TemplateError(
'You need to update the template to use the new SDK. ' +
'You can do this by running `e2b template build` in the directory with the template.'
'You can do this by running `e2b template build` in the directory with the template.'
)
}
return {
Expand Down
18 changes: 15 additions & 3 deletions packages/js-sdk/tests/api/list.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@ sandboxTest.skipIf(isDebug)('list sandboxes', async ({ sandbox }) => {
sandboxTest.skipIf(isDebug)('list sandboxes with filter', async () => {
const uniqueId = Date.now().toString()
// Create an extra sandbox with a uniqueId
const extraSbx = await Sandbox.create({ })
const extraSbx = await Sandbox.create({})
try {
const sbx = await Sandbox.create({metadata: {uniqueId: uniqueId}})
const sbx = await Sandbox.create({ metadata: { uniqueId: uniqueId } })
try {
const sandboxes = await Sandbox.list({filters: {uniqueId}})
const sandboxes = await Sandbox.list({ filters: { uniqueId } })
assert.equal(sandboxes.length, 1)
assert.equal(sandboxes[0].sandboxId, sbx.sandboxId)
} finally {
Expand All @@ -38,3 +38,15 @@ sandboxTest.skipIf(isDebug)('list sandboxes with filter', async () => {
await extraSbx.kill()
}
})

sandboxTest.skipIf(isDebug)('list paused sandboxes', async ({ sandbox }) => {
const pausedSandbox = await sandbox.pause()
const pausedSandboxId = pausedSandbox.split('-')[0] + '-' + '00000000'
const sandboxes = await Sandbox.list({ state: 'paused' })

assert.isAtLeast(sandboxes.length, 1)
assert.include(
sandboxes.map((s) => s.sandboxId),
pausedSandboxId
)
})

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

Loading