From 8907b2435ef2215e55d567dcbd58e58443fac1cc Mon Sep 17 00:00:00 2001 From: Ilyas Salikhov Date: Thu, 3 Sep 2026 22:53:16 +0300 Subject: [PATCH] fix(go): inherit template build users and prepare v0.1.4 --- packages/cli/package.json | 2 +- packages/code-interpreter-js/package.json | 2 +- packages/code-interpreter-python/package.json | 2 +- .../code-interpreter-python/pyproject.toml | 2 +- packages/code-interpreter-python/uv.lock | 4 +- packages/go-sdk/README.md | 4 +- packages/go-sdk/template.go | 4 ++ packages/go-sdk/template_test.go | 67 +++++++++++++++++++ packages/go-sdk/version.go | 2 +- packages/js-sdk/package.json | 2 +- packages/python-sdk/package.json | 2 +- packages/python-sdk/pyproject.toml | 2 +- packages/python-sdk/uv.lock | 2 +- reference/manifest.json | 14 ++-- reference/sdk/go/core.md | 4 +- tests/runtime/go/main.go | 39 +++++++++++ 16 files changed, 132 insertions(+), 22 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index 7ed9cfb3..b8a65708 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@abox-dev/cli", - "version": "0.1.3", + "version": "0.1.4", "description": "CLI for AgentBox sandboxes and templates", "homepage": "https://docs.agentbox.ru/en/cli/", "license": "MIT", diff --git a/packages/code-interpreter-js/package.json b/packages/code-interpreter-js/package.json index f089f490..1dfa7308 100644 --- a/packages/code-interpreter-js/package.json +++ b/packages/code-interpreter-js/package.json @@ -1,6 +1,6 @@ { "name": "@abox-dev/code-interpreter", - "version": "0.1.3", + "version": "0.1.4", "packageManager": "pnpm@10.34.5", "description": "AgentBox Code Interpreter - Stateful code execution", "homepage": "https://docs.agentbox.ru/en/sdk/code-interpreter/", diff --git a/packages/code-interpreter-python/package.json b/packages/code-interpreter-python/package.json index 1518cb9f..86e190e0 100644 --- a/packages/code-interpreter-python/package.json +++ b/packages/code-interpreter-python/package.json @@ -1,7 +1,7 @@ { "name": "@abox-dev/code-interpreter-python", "private": true, - "version": "0.1.3", + "version": "0.1.4", "scripts": { "test": "uv run pytest -n 2 --verbose -x tests/test_sandbox_url.py", "test:integration": "uv run pytest -n 2 --verbose -x", diff --git a/packages/code-interpreter-python/pyproject.toml b/packages/code-interpreter-python/pyproject.toml index 9639824b..52ae57a5 100644 --- a/packages/code-interpreter-python/pyproject.toml +++ b/packages/code-interpreter-python/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "abox-code-interpreter" -version = "0.1.3" +version = "0.1.4" description = "AgentBox Code Interpreter - Stateful code execution" authors = [{ name = "RetailDriver LLC" }] license = "MIT" diff --git a/packages/code-interpreter-python/uv.lock b/packages/code-interpreter-python/uv.lock index 3bb7fd06..158f9462 100644 --- a/packages/code-interpreter-python/uv.lock +++ b/packages/code-interpreter-python/uv.lock @@ -16,7 +16,7 @@ members = [ [[package]] name = "abox-code-interpreter" -version = "0.1.3" +version = "0.1.4" source = { editable = "." } dependencies = [ { name = "abox-sdk" }, @@ -58,7 +58,7 @@ dev = [ [[package]] name = "abox-sdk" -version = "0.1.3" +version = "0.1.4" source = { editable = "../python-sdk" } dependencies = [ { name = "attrs" }, diff --git a/packages/go-sdk/README.md b/packages/go-sdk/README.md index de3b72e6..8c777361 100644 --- a/packages/go-sdk/README.md +++ b/packages/go-sdk/README.md @@ -44,8 +44,8 @@ func main() { Code Interpreter is available from `github.com/abox-dev/sdk/packages/go-sdk/codeinterpreter`. -API reference for this release: [core SDK on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk@v0.1.3) and -[Code Interpreter on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk/codeinterpreter@v0.1.3). +API reference for this release: [core SDK on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk@v0.1.4) and +[Code Interpreter on pkg.go.dev](https://pkg.go.dev/github.com/abox-dev/sdk/packages/go-sdk/codeinterpreter@v0.1.4). Documentation: [core SDK](https://docs.agentbox.ru/en/sdk/), [sandboxes](https://docs.agentbox.ru/en/sdk/sandboxes/), diff --git a/packages/go-sdk/template.go b/packages/go-sdk/template.go index 9280c8aa..6161f1d3 100644 --- a/packages/go-sdk/template.go +++ b/packages/go-sdk/template.go @@ -341,7 +341,11 @@ func (builder *TemplateBuilder) Run(commands ...string) *TemplateBuilder { } // RunAs adds shell commands executed as user. +// An empty user inherits the current build user, as Run does. func (builder *TemplateBuilder) RunAs(user string, commands ...string) *TemplateBuilder { + if user == "" { + return builder.Run(commands...) + } return builder.add("RUN", strings.Join(commands, " && "), user) } diff --git a/packages/go-sdk/template_test.go b/packages/go-sdk/template_test.go index b52ffc82..7d0876d4 100644 --- a/packages/go-sdk/template_test.go +++ b/packages/go-sdk/template_test.go @@ -2,17 +2,84 @@ package agentbox import ( "context" + "encoding/json" "errors" "fmt" "net/http" "net/http/httptest" "os" "path/filepath" + "slices" "strings" "testing" "time" ) +func TestTemplateRunUserArguments(t *testing.T) { + tests := []struct { + name string + apply func(*TemplateBuilder) *TemplateBuilder + args []string + }{ + {"run-as-current", func(b *TemplateBuilder) *TemplateBuilder { return b.RunAs("", "echo one", "echo two") }, []string{"echo one && echo two"}}, + {"run-as-explicit", func(b *TemplateBuilder) *TemplateBuilder { return b.RunAs("root", "id -un") }, []string{"id -un", "root"}}, + {"npm-default", func(b *TemplateBuilder) *TemplateBuilder { return b.NPMInstall(PackageInstallOptions{}, "lodash") }, []string{"npm install lodash"}}, + {"npm-dev", func(b *TemplateBuilder) *TemplateBuilder { + return b.NPMInstall(PackageInstallOptions{Dev: true}, "typescript") + }, []string{"npm install --save-dev typescript"}}, + {"npm-global", func(b *TemplateBuilder) *TemplateBuilder { + return b.NPMInstall(PackageInstallOptions{Global: true}, "tsx") + }, []string{"npm install -g tsx", "root"}}, + {"bun-default", func(b *TemplateBuilder) *TemplateBuilder { return b.BunInstall(PackageInstallOptions{}, "lodash") }, []string{"bun install lodash"}}, + {"bun-dev", func(b *TemplateBuilder) *TemplateBuilder { + return b.BunInstall(PackageInstallOptions{Dev: true}, "typescript") + }, []string{"bun install --dev typescript"}}, + {"bun-global", func(b *TemplateBuilder) *TemplateBuilder { + return b.BunInstall(PackageInstallOptions{Global: true}, "tsx") + }, []string{"bun install -g tsx", "root"}}, + {"git-default", func(b *TemplateBuilder) *TemplateBuilder { return b.GitClone("https://example.test/repo.git", nil) }, []string{"git clone 'https://example.test/repo.git'"}}, + {"git-options", func(b *TemplateBuilder) *TemplateBuilder { + return b.GitClone("https://example.test/repo.git", &GitCloneOptions{Path: "repo", Depth: 1}) + }, []string{"git clone 'https://example.test/repo.git' --depth 1 'repo'"}}, + {"git-explicit", func(b *TemplateBuilder) *TemplateBuilder { + return b.GitClone("https://example.test/repo.git", &GitCloneOptions{User: "root"}) + }, []string{"git clone 'https://example.test/repo.git'", "root"}}, + } + for _, currentUser := range []string{"", "app"} { + for _, test := range tests { + t.Run(currentUser+"/"+test.name, func(t *testing.T) { + builder := NewTemplate("").FromBase() + if currentUser != "" { + builder.User(currentUser) + } + data, err := test.apply(builder).JSON() + if err != nil { + t.Fatal(err) + } + var request struct { + Steps []struct { + Type string `json:"type"` + Args []string `json:"args"` + } `json:"steps"` + } + if err := json.Unmarshal(data, &request); err != nil { + t.Fatal(err) + } + if len(request.Steps) == 0 { + t.Fatal("missing build steps") + } + step := request.Steps[len(request.Steps)-1] + if step.Type != "RUN" || !slices.Equal(step.Args, test.args) { + t.Fatalf("RUN arguments = %#v, want %#v (type %q)", step.Args, test.args, step.Type) + } + if currentUser != "" && (request.Steps[0].Type != "USER" || !slices.Equal(request.Steps[0].Args, []string{currentUser})) { + t.Fatal("configured build user was not preserved") + } + }) + } + } +} + func TestTemplateBuilder(t *testing.T) { contextPath := t.TempDir() if err := os.WriteFile(filepath.Join(contextPath, "hello.txt"), []byte("hello"), 0o600); err != nil { diff --git a/packages/go-sdk/version.go b/packages/go-sdk/version.go index 394e3c58..77627826 100644 --- a/packages/go-sdk/version.go +++ b/packages/go-sdk/version.go @@ -1,4 +1,4 @@ package agentbox // Version is the AgentBox SDK release version. -const Version = "0.1.3" +const Version = "0.1.4" diff --git a/packages/js-sdk/package.json b/packages/js-sdk/package.json index d4acbafd..80beef65 100644 --- a/packages/js-sdk/package.json +++ b/packages/js-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@abox-dev/sdk", - "version": "0.1.3", + "version": "0.1.4", "description": "AgentBox SDK for secure cloud sandboxes", "homepage": "https://docs.agentbox.ru/en/sdk/", "license": "MIT", diff --git a/packages/python-sdk/package.json b/packages/python-sdk/package.json index 89cf3bcd..811e3b7a 100644 --- a/packages/python-sdk/package.json +++ b/packages/python-sdk/package.json @@ -1,7 +1,7 @@ { "name": "@abox-dev/python-sdk", "private": true, - "version": "0.1.3", + "version": "0.1.4", "scripts": { "test": "uv run pytest -n 4 --verbose -x tests/test_*.py tests/shared", "test:integration": "uv run pytest -n 4 --verbose -x tests/sync tests/async", diff --git a/packages/python-sdk/pyproject.toml b/packages/python-sdk/pyproject.toml index 144886e6..3cb453c9 100644 --- a/packages/python-sdk/pyproject.toml +++ b/packages/python-sdk/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "abox-sdk" -version = "0.1.3" +version = "0.1.4" description = "AgentBox SDK for secure cloud sandboxes" authors = [{ name = "RetailDriver LLC" }] license = "MIT" diff --git a/packages/python-sdk/uv.lock b/packages/python-sdk/uv.lock index f182f9ee..6d1646f2 100644 --- a/packages/python-sdk/uv.lock +++ b/packages/python-sdk/uv.lock @@ -8,7 +8,7 @@ resolution-markers = [ [[package]] name = "abox-sdk" -version = "0.1.3" +version = "0.1.4" source = { editable = "." } dependencies = [ { name = "attrs" }, diff --git a/reference/manifest.json b/reference/manifest.json index 824b4307..88308a90 100644 --- a/reference/manifest.json +++ b/reference/manifest.json @@ -45,7 +45,7 @@ "sdk/cli/sandbox.md": "16b64c5a4e932eca2452e400e0faf154eba1d3c7373513aa7cd9744e02908313", "sdk/cli/template.md": "f270b22b9ee10a9b954a14f24e04c5449b4d5823bb28002ffde3ed682594cfc5", "sdk/go/code-interpreter.md": "9c56333b8181878d656baa27a4b56b768a0cb01c9d764f7ac8d52d32d8faa70c", - "sdk/go/core.md": "ab075a4a3fbafaefb21e58596311fecb24e614240827c7a322a008c3ac06df1c", + "sdk/go/core.md": "41d700b3837e9aa7932779ba44186aaf0c54a6927ebaafa7945b0037b76db49e", "sdk/javascript/code-interpreter/README.md": "a787206eb86f81fc306b373ce20bc6c91464b0ab3c3ec7650eccbb6be5f676c5", "sdk/javascript/code-interpreter/classes/Sandbox.md": "19d9f1f1e8847606a54d93c5a6e5d4c89f936a745866e8d6a2aa06758b605e60", "sdk/javascript/code-interpreter/enumerations/ChartType.md": "cf1ba00cd3258cc88814b7215e101b0d775ceb15dd833636d58ae538d8107ec1", @@ -184,12 +184,12 @@ }, "monoRevision": "559cf23aa80d22eeb1bd2769d4ab6646b8e40fcb", "packages": { - "@abox-dev/cli": "0.1.3", - "@abox-dev/code-interpreter": "0.1.3", - "@abox-dev/sdk": "0.1.3", - "abox-code-interpreter": "0.1.3", - "abox-sdk": "0.1.3", - "github.com/abox-dev/sdk/packages/go-sdk": "0.1.3" + "@abox-dev/cli": "0.1.4", + "@abox-dev/code-interpreter": "0.1.4", + "@abox-dev/sdk": "0.1.4", + "abox-code-interpreter": "0.1.4", + "abox-sdk": "0.1.4", + "github.com/abox-dev/sdk/packages/go-sdk": "0.1.4" }, "schemaVersion": 1 } diff --git a/reference/sdk/go/core.md b/reference/sdk/go/core.md index 3fe980c2..f991541b 100644 --- a/reference/sdk/go/core.md +++ b/reference/sdk/go/core.md @@ -261,7 +261,7 @@ Create a client, start a sandbox, and run a command: Version is the AgentBox SDK release version. - const Version = "0.1.3" + const Version = "0.1.4" ## func IAMTokenPlaceholder @@ -1927,7 +1927,7 @@ Run adds shell commands executed as the current build user. func (builder *TemplateBuilder) RunAs(user string, commands ...string) *TemplateBuilder -RunAs adds shell commands executed as user. +RunAs adds shell commands executed as user. An empty user inherits the current build user, as Run does. ### func \(\*TemplateBuilder\) SkipCache diff --git a/tests/runtime/go/main.go b/tests/runtime/go/main.go index cb107085..290b83c4 100644 --- a/tests/runtime/go/main.go +++ b/tests/runtime/go/main.go @@ -19,6 +19,7 @@ func main() { must(err) sandbox, err := core.Sandboxes.Create(ctx, &agentbox.CreateSandboxOptions{Timeout: 5 * time.Minute}) must(err) + defer func() { _, _ = sandbox.Kill(context.Background()) }() result, err := sandbox.Commands.Run(ctx, "printf", &agentbox.CommandOptions{Args: []string{"go-runtime-smoke"}}) must(err) if string(result.Stdout) != "go-runtime-smoke" { @@ -34,6 +35,7 @@ func main() { must(err) codeSandbox, err := interpreter.Create(ctx, &agentbox.CreateSandboxOptions{Timeout: 5 * time.Minute}) must(err) + defer func() { _, _ = codeSandbox.Kill(context.Background()) }() execution, err := codeSandbox.RunCode(ctx, "40 + 2", nil) must(err) if !strings.Contains(execution.Text(), "42") { @@ -44,6 +46,43 @@ func main() { if !killed { panic("code interpreter sandbox was not killed") } + templateUserSmoke(ctx, core) +} + +func templateUserSmoke(ctx context.Context, client *agentbox.Client) { + name := fmt.Sprintf("sdk-go-user-smoke-%d", time.Now().UnixNano()) + builder := agentbox.NewTemplate("").FromTemplate("base").User("user").Workdir("/home/user"). + RunAs("", "test \"$(id -un)\" = user", "mkdir -p sdk-user-smoke", "printf '{\"name\":\"sdk-user-smoke\",\"version\":\"1.0.0\"}' >sdk-user-smoke/package.json", "git init --bare sdk-user-smoke/source.git"). + Workdir("/home/user/sdk-user-smoke"). + NPMInstall(agentbox.PackageInstallOptions{}). + GitClone("/home/user/sdk-user-smoke/source.git", &agentbox.GitCloneOptions{Path: "checkout"}). + RunAs("root", "test \"$(id -un)\" = root"). + Run("test \"$(id -un)\" = user", "test -f package-lock.json", "test -d checkout/.git") + reference, err := client.Templates.BuildInBackground(ctx, builder, name, nil) + must(err) + defer func() { + cleanupCtx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + must(client.Templates.Delete(cleanupCtx, reference.TemplateID)) + }() + for { + status, err := client.Templates.BuildStatus(ctx, reference.TemplateID, reference.BuildID, 0) + must(err) + if status.Status == agentbox.BuildReady { + fmt.Println("Go template user inheritance smoke passed") + return + } + if status.Status == agentbox.BuildFailed { + panic(fmt.Sprintf("template user smoke failed: %+v", status.Reason)) + } + timer := time.NewTimer(time.Second) + select { + case <-ctx.Done(): + timer.Stop() + panic(ctx.Err()) + case <-timer.C: + } + } } func must(err error) {