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 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
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
78 changes: 78 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,84 @@ 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'] }) // $HighlightLine
console.log('Paused sandboxes', sandboxes) // $HighlightLine
```
```python
from e2b import Sandbox
# 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=['paused']) # $HighlightLine
print('Paused sandboxes', sandboxes) # $HighlightLine
```
</CodeGroup>

## 5. Removing paused sandboxes
Copy link
Member

Choose a reason for hiding this comment

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

why is js and python little bit different, also could you unify word id, sometimes you have it in lowercase and it other instance in uppercase

Copy link
Member Author

Choose a reason for hiding this comment

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

Can you give me an example?


You can remove paused (and running!) sandboxes by calling the `kill` method on the Sandbox instance.

<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'

const sbx = await Sandbox.create()
console.log('Sandbox created', sbx.sandboxId)

// Pause the sandbox
// You can save the sandbox ID in your database
// to resume the sandbox later
const sandboxId = await sbx.pause()

// Remove the sandbox
await sbx.kill() // $HighlightLine

// Remove sandbox by id
await Sandbox.kill(sandboxId) // $HighlightLine
```
```python
from e2b import Sandbox
# 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

sbx = Sandbox()

# Pause the sandbox
sandbox_id = sbx.pause()

# Remove the sandbox
sbx.kill() # $HighlightLine

# Remove sandbox by id
Sandbox.kill(sandbox_id) # $HighlightLine
```
</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
2 changes: 1 addition & 1 deletion apps/web/src/code/js/basics/metadata.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ await sandbox.keepAlive(60_000)

// Later, can be even from another process
// List all running sandboxes
const runningSandboxes = await Sandbox.list()
const runningSandboxes = await Sandbox.list({ state: ['running'] })
// Find the sandbox by metadata
const found = runningSandboxes.find(s => s.metadata?.userID === 'uniqueID')
if (found) {
Expand Down
2 changes: 1 addition & 1 deletion apps/web/src/code/python/basics/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

# Later, can be even from another process
# List all running sandboxes
running_sandboxes = Sandbox.list()
running_sandboxes = Sandbox.list(state=['running'])

# Find the sandbox by metadata
for running_sandbox in running_sandboxes:
Expand Down
14 changes: 7 additions & 7 deletions packages/cli/src/commands/sandbox/kill.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export const killCommand = new commander.Command('kill')
.description('kill sandbox')
.argument(
'[sandboxID]',
`kill the sandbox specified by ${asBold('[sandboxID]')}`,
`kill the sandbox specified by ${asBold('[sandboxID]')}`
)
.alias('kl')
.option('-a, --all', 'kill all running sandboxes')
Expand All @@ -28,17 +28,17 @@ export const killCommand = new commander.Command('kill')
if (!sandboxID && !all) {
console.error(
`You need to specify ${asBold('[sandboxID]')} or use ${asBold(
'-a/--all',
)} flag`,
'-a/--all'
)} flag`
)
process.exit(1)
}

if (all && sandboxID) {
console.error(
`You cannot use ${asBold('-a/--all')} flag while specifying ${asBold(
'[sandboxID]',
)}`,
'[sandboxID]'
)}`
)
process.exit(1)
}
Expand All @@ -47,12 +47,12 @@ export const killCommand = new commander.Command('kill')
const sandboxes = await e2b.Sandbox.list({ apiKey })

if (sandboxes.length === 0) {
console.log('No running sandboxes')
console.log('No sandboxes found')
process.exit(0)
}

await Promise.all(
sandboxes.map((sandbox) => killSandbox(sandbox.sandboxId, apiKey)),
sandboxes.map((sandbox) => killSandbox(sandbox.sandboxId, apiKey))
)
} else {
await killSandbox(sandboxID, apiKey)
Expand Down
35 changes: 29 additions & 6 deletions packages/cli/src/commands/sandbox/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,19 @@ import { handleE2BRequestError } from '../../utils/errors'
export const listCommand = new commander.Command('list')
.description('list all running sandboxes')
.alias('ls')
.action(async () => {
.option('-s, --state <state>', 'filter by state', (value) => value.split(','))
.option('-f, --filters <filters>', 'filter by metadata', (value) =>
value.replace(/,/g, '&')
)
.action(async (options) => {
try {
const sandboxes = await listSandboxes()
const sandboxes = await listSandboxes({
state: options.state,
filters: options.filters,
})

if (!sandboxes?.length) {
console.log('No running sandboxes.')
console.log('No sandboxes found')
} else {
const table = new tablePrinter.Table({
title: 'Running sandboxes',
Expand All @@ -28,6 +35,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 +47,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 Expand Up @@ -81,13 +91,26 @@ export const listCommand = new commander.Command('list')
}
})

export async function listSandboxes(): Promise<
e2b.components['schemas']['RunningSandbox'][]
type ListSandboxesOptions = {
state?: e2b.components['schemas']['SandboxState'][]
filters?: string
}

export async function listSandboxes({
state,
filters,
}: ListSandboxesOptions = {}): Promise<
e2b.components['schemas']['ListedSandbox'][]
> {
ensureAPIKey()

const signal = connectionConfig.getSignal()
const res = await client.api.GET('/sandboxes', { signal })
const res = await client.api.GET('/sandboxes', {
params: {
query: { state, query: filters },
},
signal,
})

handleE2BRequestError(res.error, 'Error getting running sandboxes')

Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/sandbox/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export function waitForSandboxEnd(sandboxID: string) {
break
}

const response = await listSandboxes()
const response = await listSandboxes({ state: ['running'] })
const sandbox = response.find(
(s) => s.sandboxID === getShortID(sandboxID)
)
Expand Down Expand Up @@ -153,7 +153,7 @@ export const logsCommand = new commander.Command('logs')
console.log(`\nLogs for sandbox ${asBold(sandboxID)}:`)
}

const isRunningPromise = listSandboxes()
const isRunningPromise = listSandboxes({ state: ['running'] })
.then((r) => r.find((s) => s.sandboxID === getShortID(sandboxID)))
.then((s) => !!s)

Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/commands/sandbox/metrics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export const metricsCommand = new commander.Command('metrics')
console.log(`\nMetrics for sandbox ${asBold(sandboxID)}:`)
}

const isRunningPromise = listSandboxes()
const isRunningPromise = listSandboxes({ state: ['running'] })
.then((r) => r.find((s) => s.sandboxID === getShortID(sandboxID)))
.then((s) => !!s)

Expand Down
4 changes: 2 additions & 2 deletions packages/js-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"dev": "tsup --watch",
"example": "tsx example.mts",
"test": "vitest run",
"generate": "python ./../../spec/remove_extra_tags.py sandboxes templates && openapi-typescript ../../spec/openapi_generated.yml -x api_key --support-array-length --alphabetize --output src/api/schema.gen.ts",
"generate": "python ./../../spec/remove_extra_tags.py sandboxes templates && openapi-typescript ../../spec/openapi_generated.yml -x api_key --array-length --alphabetize --output src/api/schema.gen.ts",
"generate-envd-api": "openapi-typescript ../../spec/envd/envd.yaml -x api_key --support-array-length --alphabetize --output src/envd/schema.gen.ts",
"generate-ref": "./scripts/generate_sdk_ref.sh",
"check-deps": "knip",
Expand All @@ -50,7 +50,7 @@
"dotenv": "^16.4.5",
"knip": "^5.17.3",
"npm-check-updates": "^16.14.20",
"openapi-typescript": "^6.7.6",
"openapi-typescript": "^7.6.1",
"playwright": "^1.48.0",
"react": "^18.3.1",
"tsup": "^8.0.2",
Expand Down
6 changes: 6 additions & 0 deletions packages/js-sdk/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ class ApiClient {
Authorization: `Bearer ${config.accessToken}`,
}),
},
querySerializer: {
array: {
style: 'form',
explode: false,
},
},
})

if (config.logger) {
Expand Down
Loading