Skip to content
Closed
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: 5 additions & 0 deletions .changeset/template-rebuild-refresh-envd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@e2b/cli': minor
---

Add `e2b template rebuild <template> --refresh-envd`: rebuild an existing template with the host's current envd while keeping its specs and alias. Old templates bake an old envd into their snapshot, which blocks features gated on a newer envd (e.g. volume mounts need envd >= 0.5.14); this rebuilds in place FROM the template's own latest ready build so only the envd binary is swapped. Streams build logs until the new build is ready. Requires the companion `POST /v2/templates/{templateID}/refresh-envd` endpoint (e2b-dev/infra#3624).
2 changes: 2 additions & 0 deletions packages/cli/src/commands/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { initCommand } from './init'
import { listCommand } from './list'
import { migrateCommand } from './migrate'
import { publishCommand, unPublishCommand } from './publish'
import { rebuildCommand } from './rebuild'

export const templateCommand = new commander.Command('template')
.description('manage sandbox templates')
Expand All @@ -19,3 +20,4 @@ export const templateCommand = new commander.Command('template')
.addCommand(publishCommand)
.addCommand(unPublishCommand)
.addCommand(migrateCommand)
.addCommand(rebuildCommand)
101 changes: 101 additions & 0 deletions packages/cli/src/commands/template/rebuild.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import * as commander from 'commander'
import { defaultBuildLogger, Template } from 'e2b'

import { client, ensureAPIKey } from 'src/api'
import { handleE2BRequestError } from '../../utils/errors'
import {
asBold,
asFormattedError,
asLocal,
asPrimary,
} from '../../utils/format'

const buildStatusPollFrequencyMs = 2_000

/**
* Rebuild a template with the host's current envd. This calls the server-side
* refresh-envd endpoint, which derives a new build FROM the template's own
* latest ready build (base layer cached, only the envd binary swapped) and
* inherits the source build's specs and alias in place. Then it streams build
* logs until the new build is ready.
*/
async function refreshEnvd(templateID: string) {
ensureAPIKey()

const res = await client.api.POST('/v2/templates/{templateID}/refresh-envd', {
params: { path: { templateID } },
})
handleE2BRequestError(res, 'Error requesting envd refresh')

const { buildID, fromEnvdVersion, aliases } = res.data
const name = aliases && aliases.length > 0 ? aliases[0] : templateID

console.log(
`\nRefreshing envd for ${asBold(name)} (from v${fromEnvdVersion}); rebuilding...\n`
)

const onLog = defaultBuildLogger()
let logsOffset = 0
// Poll the existing build status endpoint until the derived build settles.
// The status endpoint returns at most 100 log entries per call, so keep
// draining after a terminal status before deciding the outcome.
for (;;) {
const status = await Template.getBuildStatus(
{ templateId: templateID, buildId: buildID },
{ logsOffset }
)
logsOffset += status.logEntries.length
status.logEntries.forEach(onLog)

if (status.status === 'ready') {
if (status.logEntries.length > 0) continue
break
}
if (status.status === 'error') {
if (status.logEntries.length > 0) continue
console.error(
asFormattedError(status.reason?.message ?? 'Template build failed')
)
process.exit(1)
}

await new Promise((r) => setTimeout(r, buildStatusPollFrequencyMs))
}

console.log(
`\n✅ ${asBold(name)} rebuilt with the current envd.\n\n Confirm the binary changed: start a sandbox from ${asLocal(
name
)}, then run ${asPrimary('/usr/bin/envd -version')}.`
)
}

export const rebuildCommand = new commander.Command('rebuild')
.description(
'rebuild a template with the current envd, keeping its specs and alias. Useful for old templates whose baked-in envd is too old for newer features (e.g. volume mounts need envd >= 0.5.14).'
)
.argument(
'<template>',
'template id or alias to rebuild. Its specs and alias are inherited from the latest ready build.'
)
.option(
'--refresh-envd',
"swap in the host's current envd binary (the only rebuild mode today; required)."
)
Comment thread
AdaAibaby marked this conversation as resolved.
.alias('rb')
.action(async (template: string, opts: { refreshEnvd?: boolean }) => {
if (!opts.refreshEnvd) {
console.error(
`Nothing to rebuild. Pass ${asBold(
'--refresh-envd'
)} to rebuild the template with the current envd.`
)
process.exit(1)
}

try {
await refreshEnvd(template)
} catch (err: any) {
console.error(asFormattedError(err.message))
process.exit(1)
}
})
36 changes: 36 additions & 0 deletions packages/cli/tests/commands/template/rebuild.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import * as path from 'path'
import { execSync } from 'child_process'
import { describe, expect, test } from 'vitest'

// Black-box CLI tests (same idiom as publish.test.ts): run the built binary and
// assert on output up to the network boundary. The --refresh-envd guard and the
// command registration are deterministic and need no API key or network.
describe('template rebuild', () => {
const cliPath = path.join(process.cwd(), 'dist', 'index.js')

function run(args: string): string {
try {
return execSync(`node "${cliPath}" ${args} 2>&1`, {
encoding: 'utf-8',
stdio: 'pipe',
timeout: 10_000,
})
} catch (err: any) {
return (err?.stdout ?? '') + (err?.stderr ?? '')
}
}

test('without --refresh-envd it refuses and points at the flag', () => {
const output = run('template rebuild some-template')
expect(output).toContain('--refresh-envd')
expect(output).not.toContain('Refreshing envd')
})

test('is registered and documented under template help', () => {
const output = run('template rebuild --help')
expect(output).toContain('rebuild')
expect(output).toContain('--refresh-envd')
// The description explains the old-envd motivation.
expect(output).toContain('envd')
})
})
61 changes: 61 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.

56 changes: 56 additions & 0 deletions spec/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1063,6 +1063,26 @@ components:
buildStatus:
$ref: "#/components/schemas/TemplateBuildStatus"

TemplateRefreshEnvdResponse:
required:
- templateID
- buildID
- fromEnvdVersion
properties:
templateID:
type: string
description: Identifier of the template being refreshed
buildID:
type: string
description: Identifier of the new build; poll its status/logs endpoints
fromEnvdVersion:
type: string
description: envd version the source build was baked with
aliases:
type: array
description: Aliases of the template (inherited from the source)
items:
type: string
TemplateRequestResponseV3:
required:
- templateID
Expand Down Expand Up @@ -3240,6 +3260,42 @@ paths:
"500":
$ref: "#/components/responses/500"

/v2/templates/{templateID}/refresh-envd:
post:
summary: Rebuild a template with the current envd
description: |
Derives a new build of the template FROM the template's own latest ready
build, forcing the host's current envd binary to replace the one baked in
by the original build. Specs (cpu, memory) and the alias are inherited
from the source build. Returns the new build to poll via the existing
build status/logs endpoints.
tags: [templates]
security:
- ApiKeyAuth: []
- AuthProviderBearerAuth: []
AuthProviderTeamAuth: []
- AdminApiKeyAuth: []
AdminTeamAuth: []
parameters:
- $ref: "#/components/parameters/templateID"
responses:
"202":
description: The refresh build was requested successfully
content:
application/json:
schema:
$ref: "#/components/schemas/TemplateRefreshEnvdResponse"
"400":
$ref: "#/components/responses/400"
"401":
$ref: "#/components/responses/401"
"403":
$ref: "#/components/responses/403"
"404":
$ref: "#/components/responses/404"
"500":
$ref: "#/components/responses/500"

/v2/templates/{templateID}:
patch:
summary: Update template (v2)
Expand Down