Skip to content

Allow runtime image in workload API#3772

Open
slyt3 wants to merge 8 commits intostacklok:mainfrom
slyt3:issue-3676-runtime-image-api
Open

Allow runtime image in workload API#3772
slyt3 wants to merge 8 commits intostacklok:mainfrom
slyt3:issue-3676-runtime-image-api

Conversation

@slyt3
Copy link
Contributor

@slyt3 slyt3 commented Feb 11, 2026

I added runtime_config to workload to create and update API requests for #3676; It now passes the runtime image override into the build/retriever flow and stores it in RunConfig, updated API docs and tests

Allow runtime_config in create and update workload API requests.
Wire runtime overrides through image retrieval and persist them in RunConfig.
Update API docs and tests for stacklok#3676.

Signed-off-by: Mantas Suminas <mantas.sumin@gmail.com>
@github-actions github-actions bot added the size/S Small PR: 100-299 lines changed label Feb 11, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97e8aa6d9d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@codecov
Copy link

codecov bot commented Feb 11, 2026

Codecov Report

❌ Patch coverage is 91.89189% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.55%. Comparing base (60f23f5) to head (a638c4d).

Files with missing lines Patch % Lines
pkg/runner/protocol.go 83.33% 3 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3772      +/-   ##
==========================================
- Coverage   66.57%   66.55%   -0.02%     
==========================================
  Files         432      432              
  Lines       42215    42246      +31     
==========================================
+ Hits        28103    28117      +14     
- Misses      11970    11990      +20     
+ Partials     2142     2139       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Avoid invalid protocol Dockerfiles when runtime override omits builder_image by merging missing fields with base runtime config. Add tests for merged behavior and override precedence.

Signed-off-by: slyt3 <mantas.sumin@gmail.com>
Trim builder_image and ignore empty runtime_config payloads to avoid forwarding meaningless overrides.
@slyt3 slyt3 requested a review from lujunsan as a code owner February 11, 2026 13:00
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 11, 2026
@slyt3 slyt3 marked this pull request as draft February 11, 2026 13:03
Add explicit returns after fatal nil guards so staticcheck can prove dereferences are safe.
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 11, 2026
Signed-off-by: slyt3 <mantas.sumin@gmail.com>
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 11, 2026
@slyt3
Copy link
Contributor Author

slyt3 commented Feb 11, 2026

I fixed the failing verify check separately: docs/server/swagger.json had an EOF newline mismatch; this is docs formatting only, not part of the #3676 runtime_config logic.

@slyt3 slyt3 marked this pull request as ready for review February 11, 2026 13:17
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 11, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1e6e895eee

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Append additional_packages to base runtime defaults and filter blank package entries in API requests to avoid malformed build config.
@github-actions github-actions bot added size/S Small PR: 100-299 lines changed and removed size/S Small PR: 100-299 lines changed labels Feb 11, 2026
@github-actions github-actions bot removed the size/S Small PR: 100-299 lines changed label Feb 11, 2026
@github-actions github-actions bot added the size/S Small PR: 100-299 lines changed label Feb 11, 2026
Copy link
Collaborator

@JAORMX JAORMX left a comment

Choose a reason for hiding this comment

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

Thanks for working on this! The feature itself makes sense and the test coverage for runtimeConfigFromRequest is solid. A few things to address before merging:

  1. loadRuntimeConfig merge semantic is a breaking change: This changes behavior for all callers (CLI included), not just the API. The append-to-base semantic for AdditionalPackages means CLI users of --runtime-add-package get different behavior than before. Consider scoping this to the API path only, or splitting the merge-semantic change into its own PR.

  2. Input validation for runtime_config fields: AdditionalPackages values end up interpolated into RUN shell commands in Dockerfile templates (apk add, apt-get install). BuilderImage goes into FROM directives. Neither is validated beyond whitespace trimming. We should add validation before merging (I'll handle this separately).

  3. Test style in protocol_test.go: Uses t.Fatal/reflect.DeepEqual instead of testify. The rest of the PR's tests use testify correctly.

  4. Silent ignore for non-protocol images: When runtime_config is passed with a regular Docker image, nothing happens and there's no feedback to the user.

See inline comments for details.

Comment on lines +160 to +196
// Resolve base config from user configuration or defaults.
baseConfig := getBaseRuntimeConfig(transportType)
if override == nil {
return baseConfig
}

// Merge overrides into base config so omitted fields inherit sane defaults.
merged := &templates.RuntimeConfig{
BuilderImage: baseConfig.BuilderImage,
AdditionalPackages: append([]string{}, baseConfig.AdditionalPackages...),
}

if strings.TrimSpace(override.BuilderImage) != "" {
merged.BuilderImage = strings.TrimSpace(override.BuilderImage)
}
if len(override.AdditionalPackages) > 0 {
merged.AdditionalPackages = append(merged.AdditionalPackages, override.AdditionalPackages...)
}

return merged
}

func getBaseRuntimeConfig(transportType templates.TransportType) *templates.RuntimeConfig {
// Try loading from user config
provider := config.NewProvider()
if userConfig, err := provider.GetRuntimeConfig(string(transportType)); err == nil && userConfig != nil {
return userConfig
return &templates.RuntimeConfig{
BuilderImage: userConfig.BuilderImage,
AdditionalPackages: append([]string{}, userConfig.AdditionalPackages...),
}
}

// Fall back to defaults
defaultConfig := templates.GetDefaultRuntimeConfig(transportType)
return &defaultConfig
return &templates.RuntimeConfig{
BuilderImage: defaultConfig.BuilderImage,
AdditionalPackages: append([]string{}, defaultConfig.AdditionalPackages...),
Copy link
Collaborator

Choose a reason for hiding this comment

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

This changes the merge semantic from full-replacement to append-with-base for all callers (CLI included), not just the API path. Before this PR, passing an override replaced everything; now AdditionalPackages are appended to base defaults and BuilderImage falls back to base if empty.

This is a breaking behavioral change for CLI users of --runtime-add-package -- they previously got exactly what they specified, now they get base packages plus their additions with no way to subtract.

A couple of options:

  1. Keep full-replacement semantics here and let the API layer handle merging if that's desired.
  2. If merge is the intended behavior for all paths, this should be a separate, clearly documented PR since it changes existing CLI behavior.

Also: there's double-trimming of BuilderImage -- runtimeConfigFromRequest already trims whitespace at the API boundary, and then loadRuntimeConfig trims again at line 173. Pick one place to trim (prefer the API boundary) and trust internal code receives clean input.

Comment on lines +278 to +282
// Runtime overrides only apply to protocol-scheme image builds.
if runtimeConfigOverride != nil && req.URL == "" {
options = append(options, runner.WithRuntimeConfig(runtimeConfigOverride))
}

Copy link
Collaborator

Choose a reason for hiding this comment

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

The comment says "Runtime overrides only apply to protocol-scheme image builds" but if a user passes runtime_config with a regular Docker image (not go://, npx://, uvx://), no warning or error is returned -- it's silently ignored. This will confuse users who expect it to have an effect.

Consider returning a validation error (or at least a warning in the response) when runtime_config is provided alongside a non-protocol-scheme image.

Comment on lines +446 to +501

func TestLoadRuntimeConfig_MergesMissingOverrideFields(t *testing.T) {
t.Parallel()

base := loadRuntimeConfig(templates.TransportTypeGO, nil)
if base == nil {
t.Fatal("loadRuntimeConfig returned nil base config")
return
}
if base.BuilderImage == "" {
t.Fatal("base runtime config has empty builder image")
}

override := &templates.RuntimeConfig{
AdditionalPackages: []string{"curl"},
}
got := loadRuntimeConfig(templates.TransportTypeGO, override)
if got == nil {
t.Fatal("loadRuntimeConfig returned nil merged config")
return
}

// Missing builder image in override should inherit the base builder image.
if got.BuilderImage != base.BuilderImage {
t.Fatalf("BuilderImage = %q, want base %q", got.BuilderImage, base.BuilderImage)
}

// Additional packages should be appended to base defaults.
expectedPackages := append([]string{}, base.AdditionalPackages...)
expectedPackages = append(expectedPackages, "curl")
if !reflect.DeepEqual(got.AdditionalPackages, expectedPackages) {
t.Fatalf("AdditionalPackages = %v, want %v", got.AdditionalPackages, expectedPackages)
}

// Ensure merged config is detached from input slices.
override.AdditionalPackages[0] = "git"
if got.AdditionalPackages[len(got.AdditionalPackages)-1] != "curl" {
t.Fatalf("AdditionalPackages mutated via override input: got %v", got.AdditionalPackages)
}
}

func TestLoadRuntimeConfig_UsesOverrideBuilderImage(t *testing.T) {
t.Parallel()

customImage := "golang:1.24-alpine"
got := loadRuntimeConfig(templates.TransportTypeGO, &templates.RuntimeConfig{
BuilderImage: customImage,
})
if got == nil {
t.Fatal("loadRuntimeConfig returned nil config")
return
}
if got.BuilderImage != customImage {
t.Fatalf("BuilderImage = %q, want %q", got.BuilderImage, customImage)
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

These tests use t.Fatal/t.Fatalf and reflect.DeepEqual instead of testify assertions (assert.Equal, require.NotNil, etc.). The other test files in this same PR (workload_service_test.go, workloads_types_test.go) correctly use testify, so let's keep things consistent here.

For example:

// Instead of:
if got.BuilderImage != base.BuilderImage {
    t.Fatalf("BuilderImage = %q, want base %q", got.BuilderImage, base.BuilderImage)
}

// Use:
assert.Equal(t, base.BuilderImage, got.BuilderImage)

And reflect.DeepEqual can be replaced with assert.Equal.

Comment on lines +142 to 165
{
name: "with runtime config override",
requestBody: `{"name": "test-workload", "image": "go://github.com/example/server", "runtime_config": {"builder_image": "golang:1.24-alpine", "additional_packages": ["ca-certificates"]}}`,
setupMock: func(_ *testing.T, wm *workloadsmocks.MockManager, _ *runtimemocks.MockRuntime, gm *groupsmocks.MockManager) {
wm.EXPECT().DoesWorkloadExist(gomock.Any(), "test-workload").Return(false, nil)
gm.EXPECT().Exists(gomock.Any(), "default").Return(true, nil)
wm.EXPECT().RunWorkloadDetached(gomock.Any(), gomock.Any()).
DoAndReturn(func(_ context.Context, runConfig *runner.RunConfig) error {
assert.NotNil(t, runConfig.RuntimeConfig)
assert.Equal(t, "golang:1.24-alpine", runConfig.RuntimeConfig.BuilderImage)
assert.Equal(t, []string{"ca-certificates"}, runConfig.RuntimeConfig.AdditionalPackages)
return nil
})
},
expectedRuntimeConfig: &templates.RuntimeConfig{
BuilderImage: "golang:1.24-alpine",
AdditionalPackages: []string{"ca-certificates"},
},
expectedServerOrImage: "go://github.com/example/server",
expectedStatus: http.StatusCreated,
expectedBody: "test-workload",
},
{
name: "with tool filters",
Copy link
Collaborator

Choose a reason for hiding this comment

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

Good test for the happy path! A few edge cases are missing that would strengthen coverage:

  • Empty runtime_config ({}): Verify it doesn't override defaults (i.e., runtimeConfigFromRequest returns nil).
  • runtime_config with a non-protocol-scheme image: e.g., "image": "nginx:latest" + runtime_config set. Verify the behavior is well-defined (currently silently ignored).
  • Update path round-trip: Update a workload without specifying runtime_config and verify whether a previously stored config is preserved or cleared. This matters for UX since users may not expect an update to drop their runtime overrides.

@slyt3
Copy link
Contributor Author

slyt3 commented Mar 18, 2026

@JAORMX thankyouuuu for taking time to look into this, gonna fix those issues!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/S Small PR: 100-299 lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants