-
Notifications
You must be signed in to change notification settings - Fork 290
✨ Accept per-host pull secrets for customer OCI registries #2745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
Hi @mabulgu. Thanks for your PR. I'm waiting for a metal3-io member to verify that this patch is reasonable to test. If it is, they should reply with Once the patch is verified, the new status will be reflected by the I understand the commands that are listed here. DetailsInstructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. |
898fffc to
9594af2
Compare
|
/ok-to-test |
|
It might be too much code to be squashing into one commit...what do we think? |
10 is definitely not right either. One for implementation, one for tesrts, and one for docs? There is plenty of work left though, given the conditions need work per Dmitry's review. |
|
Thanks for your comments! @MahnoorAsghar > It might be too much code to be squashing into one commit...what do we think? As soon as we itemize them and they are in the same context, I don't think so. I am going to remove everything related to conditions as they were like extra, but everything else share the same context and can be in the same squash commit IMO. @tuminoid > One for implementation, one for tesrts, and one for docs +1 for docs -1 for tests as tests are a part of the "implementation". Without tests, I would not count it as implemented. What I will do is: seperating the code commit (which will have less changes than the current changes because of the condition revert) and the commit for docs. |
This is fine as well, but its quite common to implement tests in separate commit in same PR. Makes it maybe easier to manage, but like said, I'm 100% fine with code+tests in same commit. |
|
/retest |
b18570e to
27c9860
Compare
|
@dtantsur I applied your suggestions. pls check when you have time. You will find the relevant commetns resolved but pls feel free to reopen them if you feel any of them are not implemented the way you suggested |
|
Not sure if the e2e test filure is related to my changes as it seems to be related to the BMC management credentials |
|
/retest |
27c9860 to
c51576d
Compare
66fb281 to
eb91b4a
Compare
dbe5eb3 to
ff70b04
Compare
|
Other than the small CI linting failure, Dmitry's comments, and the many commits (I think this should be <5 commits), looks good to me |
hey @MahnoorAsghar! yes as we discussed before this will be squashed before the merge for a better commit history. That will be the last thing to do as some update requests might occur and I don't want to restructure the commit structure each time. I will do it once I am sure everything is addressed. thanks for your comment! |
Adds support for per-host registry authentication for oci:// provisioning images via a new optional field spec.image.authSecretName on BareMetalHost. When set, the controller validates the Kubernetes Docker-config secret (kubernetes.io/dockerconfigjson or kubernetes.io/dockercfg), extracts credentials for the matching registry, and passes them to Ironic during provisioning. Signed-off-by: mabulgu <[email protected]>
Adds unit tests for: - ImageAuthValidator validation logic - Docker config credential extraction - Registry host parsing with url.Parse - Various edge cases (missing secrets, wrong types, etc.) Signed-off-by: mabulgu <[email protected]>
052e08d to
a9b7554
Compare
Adds documentation for the new spec.image.authSecretName field that enables per-host OCI registry authentication for provisioning images. Includes: - Field description and usage example - Secret requirements (type, namespace, format) - Example Secret manifest Signed-off-by: mabulgu <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This pull request adds per-host OCI registry authentication support to the baremetal-operator, enabling provisioning of images from private OCI registries on a per-host basis.
Key Changes:
- Added
authSecretNamefield to theImageAPI for referencing Docker config secrets - Implemented credential extraction logic using the
dockercfglibrary to parse Kubernetes Docker config secrets - Integrated authentication into the provisioning workflow, passing base64-encoded credentials to Ironic
Reviewed changes
Copilot reviewed 13 out of 14 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
apis/metal3.io/v1alpha1/baremetalhost_types.go |
Added AuthSecretName field to Image type and improved IsOCI() to be case-insensitive |
apis/metal3.io/v1alpha1/zz_generated.deepcopy.go |
Generated deepcopy code for new AuthSecretName field |
pkg/secretutils/dockerconfig.go |
New file implementing Docker config secret parsing and credential extraction |
pkg/secretutils/dockerconfig_test.go |
Unit tests for credential extraction logic |
internal/controller/metal3.io/image_auth_validator.go |
New validator for image authentication secrets with event recording |
internal/controller/metal3.io/image_auth_validator_test.go |
Tests for auth secret validation |
internal/controller/metal3.io/baremetalhost_controller.go |
Integrated auth secret extraction into provisioning action |
pkg/provisioner/provisioner.go |
Added ImagePullSecret field to ProvisionData |
pkg/provisioner/ironic/ironic.go |
Updated deploy methods to pass credentials to Ironic instance_info |
go.mod / go.sum |
Added github.com/cpuguy83/dockercfg dependency |
config/base/crds/bases/metal3.io_baremetalhosts.yaml |
CRD definition for new authSecretName field |
config/render/capm3.yaml |
Rendered CRD with new field |
docs/api.md |
Documentation for OCI image authentication feature |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| "username": "testuser", | ||
| "password": "testpass", |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Docker config format is incorrect. The standard Docker config uses an "auth" field containing base64-encoded "username:password", not separate "username" and "password" fields. Replace lines 246-249 with:
"different-registry.com": map[string]interface{}{
"auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),
},| "username": "testuser", | |
| "password": "testpass", | |
| "auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")), |
| "username": "testuser", | ||
| "password": "testpass", |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Docker config format is incorrect. The standard Docker config uses an "auth" field containing base64-encoded "username:password", not separate "username" and "password" fields. Replace lines 327-330 with:
"registry.example.com": map[string]interface{}{
"auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),
},| "username": "testuser", | |
| "password": "testpass", | |
| "auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")), |
| // Helper function to check substring. | ||
| func containsSubstring(s, substr string) bool { | ||
| for i := 0; i <= len(s)-len(substr); i++ { | ||
| if s[i:i+len(substr)] == substr { | ||
| return true | ||
| } | ||
| } | ||
| return false | ||
| } | ||
|
|
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The containsSubstring function reimplements the standard library's strings.Contains(). Replace this custom implementation with strings.Contains(s, substr) from the standard library.
| // Helper function to check substring. | |
| func containsSubstring(s, substr string) bool { | |
| for i := 0; i <= len(s)-len(substr); i++ { | |
| if s[i:i+len(substr)] == substr { | |
| return true | |
| } | |
| } | |
| return false | |
| } |
|
|
||
| c := fake.NewClientBuilder().WithScheme(scheme).Build() | ||
| recorder := record.NewFakeRecorder(10) | ||
| secretManager := secretutils.NewSecretManager(t.Context(), testLogger(t), c, c) |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method t.Context() does not exist in Go versions prior to 1.24, which is not yet released. Since the go.mod specifies a non-existent version, tests using t.Context() will fail to compile. Replace t.Context() with context.Background() or context.TODO(), and import the context package.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LOL bot being in 2024.
| return "", fmt.Errorf("failed to parse image URL: %w", err) | ||
| } | ||
|
|
||
| if parsed.Scheme != "oci" { |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The scheme check parsed.Scheme != "oci" is case-sensitive, but Image.IsOCI() uses case-insensitive matching (strings.ToLower()). This inconsistency could cause URLs like "OCI://registry.example.com/image" to pass the IsOCI() check but fail in extractRegistryHost(). Use strings.EqualFold(parsed.Scheme, "oci") instead for case-insensitive comparison.
| if parsed.Scheme != "oci" { | |
| if !strings.EqualFold(parsed.Scheme, "oci") { |
| import ( | ||
| "encoding/base64" | ||
| "encoding/json" | ||
| "errors" | ||
| "fmt" | ||
| "net/url" | ||
|
|
||
| "github.com/cpuguy83/dockercfg" | ||
| corev1 "k8s.io/api/core/v1" | ||
| ) |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing strings import needed for case-insensitive scheme comparison. Add "strings" to the import block to use either strings.EqualFold() or strings.ToLower().
| "username": "testuser", | ||
| "password": "testpass", |
Copilot
AI
Dec 5, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The Docker config format is incorrect. The standard Docker config uses an "auth" field containing base64-encoded "username:password", not separate "username" and "password" fields. This test will fail because the dockercfg library expects the standard format. Use the helper function createDockerConfigJSONSecret() from pkg/secretutils/dockerconfig_test.go or format the config with an "auth" field like:
"registry.example.com": map[string]interface{}{
"auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")),
},| "username": "testuser", | |
| "password": "testpass", | |
| "auth": base64.StdEncoding.EncodeToString([]byte("testuser:testpass")), |
What this PR does / why we need it:
Adds per-host registry authentication for oci:// provisioning images.
New optional field on BareMetalHost:
When set, the controller validates the Kubernetes Docker-config secret (kubernetes.io/dockerconfigjson or kubernetes.io/dockercfg), selects the correct registry entry (supports exact host and host:port), and uses those credentials during provisioning so private OCI artefacts can be fetched on a per-host basis. Public images continue to work without credentials.
This removes a blocker for users who need different registries/accounts per machine.
Assisted-By: Claude-4.5-sonnet