Add explicit imagePullSecrets field to MCPRegistry#5106
Merged
Conversation
MCPRegistry workloads previously had no first-class image pull secret field — users had to pass them through the raw-JSON podTemplateSpec escape hatch. That works but is undiscoverable in the CRD schema and inconsistent with MCPServer, which has spec.resourceOverrides.proxyDeployment.imagePullSecrets. This adds spec.imagePullSecrets directly to MCPRegistrySpec rather than introducing a nested ResourceOverrides struct, since this is the only override field today and adding scaffolding for future fields that may not arrive is speculative. The field is a []corev1.LocalObjectReference and propagates to: - the registry-api Deployment's PodSpec.ImagePullSecrets - the operator-managed ServiceAccount the registry API runs as When both spec.imagePullSecrets and spec.podTemplateSpec.spec.imagePullSecrets are set, podTemplateSpec wins on the Deployment (atomic replace); the ServiceAccount always uses spec.imagePullSecrets, since podTemplateSpec has no notion of a ServiceAccount. This matches the existing podTemplateSpec merge semantics. The legacy podTemplateSpec workaround keeps working unchanged. Part of #5101 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5106 +/- ##
==========================================
+ Coverage 66.92% 67.03% +0.11%
==========================================
Files 595 595
Lines 60086 60009 -77
==========================================
+ Hits 40213 40230 +17
+ Misses 16804 16725 -79
+ Partials 3069 3054 -15 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
yrobla
reviewed
Apr 29, 2026
Contributor
yrobla
left a comment
There was a problem hiding this comment.
Review: Add spec.imagePullSecrets to MCPRegistrySpec
Clean, well-structured feature — the CRD field, RBAC wiring, and deployment propagation are all correct. Main concerns are a dead dedup path in WithImagePullSecrets, a subtle SA/Deployment behavioral split that users may not expect, and missing integration test coverage for the new field.
Summary of findings:
[warning]WithImagePullSecretsdeduplication logic is dead code —defaultSpecalways starts empty, so theexistingmap never has anything to deduplicate[warning]No integration test exercises the newspec.imagePullSecretsfield — existing integration tests only cover thepodTemplateSpecpath[warning]SA/Deployment split-brain: using onlyspec.podTemplateSpec.spec.imagePullSecrets(the old way) populates the Deployment but leaves the SA without credentials; the docs mention this but the risk is subtle[nit]MergePodTemplateSpecsdocstring says "All other PodSpec fields: User values preserved as-is" — now stale since imagePullSecrets has explicit merge logic[nit]WithImagePullSecretsfunction comment is misleading about what it does
The map-based dedup logic in WithImagePullSecrets was a no-op: the option only ever runs against an empty defaultSpec inside the PodTemplateSpecBuilder, so the "existing" map was always empty. Replace it with a direct assignment now that the merge step in MergePodTemplateSpecs handles user-vs-default precedence atomically. Also rewrite the misleading function comment to match what the code actually does, and document the ImagePullSecrets atomic-replace rule on MergePodTemplateSpecs's godoc bullet list.
Strengthen the godoc on MCPRegistrySpec.ImagePullSecrets to call out that this field is the only path that reaches the operator-managed ServiceAccount. The legacy spec.podTemplateSpec.spec.imagePullSecrets populates the Deployment pod spec only and silently bypasses ServiceAccount-level credential injection used by GKE Workload Identity, OpenShift, EKS IRSA, and similar managed platforms. Regenerate CRD manifests and CRD reference docs so the warning lands in the user-facing documentation.
jhrozek
reviewed
Apr 29, 2026
Existing integration coverage only exercised the legacy
spec.podTemplateSpec.spec.imagePullSecrets path, leaving the new
spec.imagePullSecrets field and the ServiceAccount path untested
end-to-end. Add four scenarios that go through the controller and a
real (envtest) apiserver:
- spec.imagePullSecrets only -> Deployment pod spec carries the value
- spec.imagePullSecrets only -> ServiceAccount carries the value
- spec.imagePullSecrets updated -> both Deployment and SA rotate
- spec.imagePullSecrets + podTemplateSpec.imagePullSecrets together ->
Deployment uses the PodTemplateSpec override (atomic replacement),
SA still tracks spec.imagePullSecrets
This locks in the documented merge rule and proves the SA propagation
that managed Kubernetes platforms (GKE Workload Identity, OpenShift,
EKS IRSA) depend on.
4 tasks
When spec.imagePullSecrets is set to an explicit empty slice (kustomize or helm overlays often produce this unintentionally), the RBAC client was wiping the ServiceAccount's existing ImagePullSecrets. On OpenShift this destroys the auto-managed dockercfg entries and breaks private-image pulls in a way that's silent and undiagnosable from the field's docs. Normalize the contract in pkg/kubernetes/rbac so an empty Secrets or ImagePullSecrets slice is treated identically to nil — both leave the existing SA fields untouched. Document the equivalence on MCPRegistrySpec.ImagePullSecrets so users know recreating the resource is the supported way to clear SA-level pull secrets.
yrobla
approved these changes
Apr 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
MCPRegistry workloads previously had no first-class image pull secrets field — users had to use the raw-JSON
spec.podTemplateSpecescape hatch. That works (the existing integration test atcmd/thv-operator/test-integration/mcp-registry/deployment_update_test.go:63proves it) but it has two drawbacks:spec.resourceOverrides.proxyDeployment.imagePullSecrets. Anyone managing both kinds of resources has to remember the API differs.This PR adds
spec.imagePullSecretstoMCPRegistry. The field is[]corev1.LocalObjectReferenceand propagates to:PodSpec.ImagePullSecretsPlacement choice. I chose
spec.imagePullSecrets(flat, top-level on the spec) rather than introducing aspec.resourceOverrides.deployment.imagePullSecretsstruct. MCPRegistry has noResourceOverridestoday, and adding one with a single field would be speculative scaffolding. If/when other override fields land,imagePullSecretscan be moved or kept as a top-level alias — but adding a struct now without a second field to justify it is unnecessary.Precedence with podTemplateSpec. When both
spec.imagePullSecretsandspec.podTemplateSpec.spec.imagePullSecretsare set:spec.imagePullSecretsis applied first as a controller-generated default.spec.podTemplateSpec.spec.imagePullSecretsis the user override and wins on overlap (the list is treated atomically — user's list replaces the default entirely on the Deployment).spec.imagePullSecrets;podTemplateSpechas no notion of a ServiceAccount.This matches the existing
podTemplateSpecmerge semantics inMergePodTemplateSpecsand is documented in the field's Go doc comment. The legacypodTemplateSpecworkaround keeps working unchanged.Part of #5101
Type of change
Test plan
task test) — operator unit tests pass; the newTestBuildRegistryAPIDeployment_ImagePullSecretscovers explicit field, empty, podTemplateSpec override, podTemplateSpec without imagePullSecrets, and legacy podTemplateSpec-only behaviour.TestEnsureRBACResources_ImagePullSecretsverifies the ServiceAccount picks up the explicit field.task lint-fix)deploy/charts/operator-crds/files/crds/toolhive.stacklok.dev_mcpregistries.yaml) shows the newimagePullSecretsarray property and the legacypodTemplateSpecfield is unchanged.API Compatibility
v1beta1API, OR theapi-break-allowedlabel is applied and the migration guidance is described above.This adds a new optional field. Existing CRs continue to work; no migration required.
Does this introduce a user-facing change?
Yes. Users can now set
spec.imagePullSecretsdirectly onMCPRegistry:The previous
spec.podTemplateSpecworkaround keeps working.Special notes for reviewers
VirtualMCPServer has the same gap (tracked by #5101) but is being landed as a separate follow-up PR to keep each diff small and reviewable. The two CRDs do not share the deployment-build code path so the changes don't combine cleanly.
Generated with Claude Code