Skip to content

Commit 4823af6

Browse files
yaseenisolatedclaude
authored andcommitted
feat: forward container_runtime from cerebrium.toml to backend (#76)
## Summary The CLI silently dropped `container_runtime` from `cerebrium.toml`, which is the knob users have to opt apps into an alternate runtime. The `ProjectConfig` struct had no field for it, `loader.go` only handled the `cerebrium.runtime.{custom,deepgram,rime}` sub-tables, and `ToPayload()` never emitted `containerRuntime` in the create-app payload. With the field missing from the deploy payload, the backend cannot honor the user's runtime selection, so apps that opt in silently fall back to the default. `git log -S "container_runtime"` on this repo returns zero hits since the Go rewrite — the field was never ported from the Python CLI. Pair this with #72 ("remove client-side payload defaults so backend owns them"): the backend is now authoritative for defaults, so an unsent field cannot be set any other way from a fresh deploy. ## Changes - `pkg/projectconfig/config.go`: add `ContainerRuntime *string` to `ProjectConfig` and emit `payload["containerRuntime"]` in `ToPayload()` when set - `pkg/projectconfig/loader.go`: read `cerebrium.runtime.container_runtime` (coexists with `[cerebrium.runtime.custom]` and partner sub-tables), validate against `{v1, v2}` matching the backend's accepted values - `pkg/projectconfig/loader_test.go`: cover present / absent / coexists-with-custom-runtime / invalid cases ## Test plan - [x] `go test ./pkg/projectconfig/...` — all loader cases pass - [x] `go test ./...` — full suite green - [ ] Deploy a test app with `container_runtime = "v2"` and confirm the backend receives the field in the create-app payload 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c879562 commit 4823af6

4 files changed

Lines changed: 95 additions & 1 deletion

File tree

internal/commands/secrets/add.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/spf13/cobra"
1212
)
1313

14-
// isValidSecretKey checks if a secret key follows Kubernetes secret key naming conventions.
14+
// isValidSecretKey checks whether a secret key matches the allowed format.
1515
// Keys must be 1-253 characters and contain only alphanumeric characters, hyphens, underscores, or dots.
1616
func isValidSecretKey(key string) bool {
1717
if len(key) == 0 || len(key) > 253 {

pkg/projectconfig/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ type ProjectConfig struct {
88
Dependencies DependenciesConfig `mapstructure:"dependencies" toml:"dependencies"`
99
CustomRuntime *CustomRuntimeConfig `mapstructure:"custom" toml:"runtime,omitempty"`
1010
PartnerService *PartnerServiceConfig `mapstructure:"partner" toml:"partner,omitempty"`
11+
12+
// ContainerRuntime selects the runtime variant for the app.
13+
// Read from [cerebrium.runtime] container_runtime in cerebrium.toml.
14+
ContainerRuntime *string `toml:"-"`
1115
}
1216

1317
// DeploymentConfig represents the [cerebrium.deployment] section
@@ -168,6 +172,10 @@ func (pc *ProjectConfig) ToPayload() map[string]any {
168172
payload["computeTier"] = *pc.Scaling.ComputeTier
169173
}
170174

175+
if pc.ContainerRuntime != nil {
176+
payload["containerRuntime"] = *pc.ContainerRuntime
177+
}
178+
171179
// Runtime configuration
172180
if pc.CustomRuntime != nil && pc.PartnerService != nil {
173181
// Both custom runtime and partner service

pkg/projectconfig/loader.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,16 @@ func Load(configPath string) (*ProjectConfig, error) {
7676
config.CustomRuntime = &customRuntime
7777
}
7878

79+
// Parse container_runtime scalar from [cerebrium.runtime].
80+
// Coexists with [cerebrium.runtime.custom] / [cerebrium.runtime.<partner>] sub-tables.
81+
if v.IsSet("cerebrium.runtime.container_runtime") {
82+
cr := v.GetString("cerebrium.runtime.container_runtime")
83+
if cr != "v1" && cr != "v2" {
84+
return nil, fmt.Errorf("invalid container_runtime %q: must be \"v1\" or \"v2\"", cr)
85+
}
86+
config.ContainerRuntime = &cr
87+
}
88+
7989
// Parse partner service sections (deepgram, rime, etc.)
8090
partnerNames := []string{"deepgram", "rime"}
8191
for _, partner := range partnerNames {

pkg/projectconfig/loader_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,82 @@ compute_tier = "bogus"
117117
assert.Contains(t, err.Error(), "invalid compute_tier")
118118
})
119119

120+
t.Run("container_runtime is nil when not specified", func(t *testing.T) {
121+
tmpDir := t.TempDir()
122+
configPath := filepath.Join(tmpDir, "cerebrium.toml")
123+
124+
content := `[cerebrium.deployment]
125+
name = "test-app"
126+
`
127+
err := os.WriteFile(configPath, []byte(content), 0644)
128+
require.NoError(t, err)
129+
130+
config, err := Load(configPath)
131+
require.NoError(t, err)
132+
assert.Nil(t, config.ContainerRuntime)
133+
})
134+
135+
t.Run("preserves explicit container_runtime v2", func(t *testing.T) {
136+
tmpDir := t.TempDir()
137+
configPath := filepath.Join(tmpDir, "cerebrium.toml")
138+
139+
content := `[cerebrium.deployment]
140+
name = "test-app"
141+
142+
[cerebrium.runtime]
143+
container_runtime = "v2"
144+
`
145+
err := os.WriteFile(configPath, []byte(content), 0644)
146+
require.NoError(t, err)
147+
148+
config, err := Load(configPath)
149+
require.NoError(t, err)
150+
require.NotNil(t, config.ContainerRuntime)
151+
assert.Equal(t, "v2", *config.ContainerRuntime)
152+
})
153+
154+
t.Run("container_runtime coexists with custom runtime sub-table", func(t *testing.T) {
155+
tmpDir := t.TempDir()
156+
configPath := filepath.Join(tmpDir, "cerebrium.toml")
157+
158+
content := `[cerebrium.deployment]
159+
name = "test-app"
160+
161+
[cerebrium.runtime]
162+
container_runtime = "v2"
163+
164+
[cerebrium.runtime.custom]
165+
port = 9000
166+
`
167+
err := os.WriteFile(configPath, []byte(content), 0644)
168+
require.NoError(t, err)
169+
170+
config, err := Load(configPath)
171+
require.NoError(t, err)
172+
require.NotNil(t, config.ContainerRuntime)
173+
assert.Equal(t, "v2", *config.ContainerRuntime)
174+
require.NotNil(t, config.CustomRuntime)
175+
assert.Equal(t, 9000, config.CustomRuntime.Port)
176+
})
177+
178+
t.Run("returns error for invalid container_runtime", func(t *testing.T) {
179+
tmpDir := t.TempDir()
180+
configPath := filepath.Join(tmpDir, "cerebrium.toml")
181+
182+
content := `[cerebrium.deployment]
183+
name = "test-app"
184+
185+
[cerebrium.runtime]
186+
container_runtime = "v3"
187+
`
188+
err := os.WriteFile(configPath, []byte(content), 0644)
189+
require.NoError(t, err)
190+
191+
_, err = Load(configPath)
192+
assert.Error(t, err)
193+
assert.Contains(t, err.Error(), "invalid container_runtime")
194+
})
195+
120196
t.Run("returns error when cerebrium key missing", func(t *testing.T) {
121197
tmpDir := t.TempDir()
122198
configPath := filepath.Join(tmpDir, "cerebrium.toml")

0 commit comments

Comments
 (0)