Skip to content

feat(registry): oras-go v3 full config stack integration#32065

Draft
TerryHowe wants to merge 15 commits intohelm:mainfrom
TerryHowe:feature/helm-oras-go-v3-integration
Draft

feat(registry): oras-go v3 full config stack integration#32065
TerryHowe wants to merge 15 commits intohelm:mainfrom
TerryHowe:feature/helm-oras-go-v3-integration

Conversation

@TerryHowe
Copy link
Copy Markdown
Contributor

@TerryHowe TerryHowe commented Apr 21, 2026

Summary

Migrates `helm.sh/helm/v4/pkg/registry` from `oras.land/oras-go/v2` to `github.com/oras-project/oras-go/v3` and wires in the full container ecosystem config stack.

Part 1: Mechanical v2 → v3 migration

Area v2 v3
Module path `oras.land/oras-go/v2` `github.com/oras-project/oras-go/v3`
Credential type `auth.Credential` `credentials.Credential`
Auth client field `authorizer.Credential` `authorizer.CredentialFunc`
Credential from store `credentials.Credential(store)` `remote.NewCredentialFunc(store)`
Static credential `auth.StaticCredential(host, ...)` `credentials.StaticCredentialFunc(host, ...)`
Login Ping-probe + `ForceAttemptOAuth2` `remote.Login(ctx, store, reg, cred)`
Logout `credentials.Logout(...)` `remote.Logout(ctx, store, host)`
Repository TLS/client `repo.PlainHTTP`, `repo.Client` `repo.Registry.PlainHTTP`, `repo.Registry.Client`
Store options `DetectDefaultNativeStore: true` removed (field renamed/inverted)
Config loader init n/a `_ "…/registry/remote/config"` blank import

Part 2: Full config stack + NewRepositoryWithProperties

  • `config.LoadConfigs()` called in `NewClient()` to load Docker `config.json`, containers `auth.json`, `registries.conf`, `certs.d`, `policy.json`, and `registries.d`
  • Combined credential store: Helm store (primary) → Docker config → containers auth
  • `newRepository()` uses `configs.RegistryProperties()` + `NewRepositoryWithProperties()` for mirror resolution, per-registry TLS, and CLI flag overrides
  • CLI flags (`--plain-http`, `--insecure`, `--cert-file`, `--ca-file`, `--username`) applied via `applyOverrides()`
  • Legacy path preserved when `ClientOptHTTPClient` or `ClientOptAuthorizer` is used

Part 3: Policy enforcement (policy.json)

  • Wire `policy.json` into `ClientBuilder` via `builder.PolicyEvaluator`
  • Add `ClientOptPolicyEvaluator` for caller override

Part 4: Signature verification (registries.d + signedBy)

  • Per-repository scoped `DefaultSignedByVerifier` built from `registries.d` lookaside config
  • Add `ClientOptSignatureVerification` to enable/disable

Part 5: LoadConfigsWithOptions override paths

  • `ConfigOptions` struct exposes `RegistriesConfigPath`, `PolicyConfigPath`, `CertsDirPaths`, `ContainersAuthPath`
  • Add `ClientOptConfigOptions` option

Part 6: Login path Location rewrite

  • `Login()` applies `Location` rewrite from `registries.conf` so credentials are stored under the canonical registry name rather than an alias

Deprecation fixes

  • Replace deprecated `registry.ParseReference` with `properties.NewReference`
  • Replace deprecated `.Reference` field access with `.Tag` on `properties.Reference`

Test plan

  • `go test ./pkg/registry/` passes (all existing + new tests)
  • `make test-style` (golangci-lint) passes with 0 issues
  • `TestNewClient_WithDenyAllPolicy` — client with deny-all policy evaluator is constructed correctly
  • `TestNewClient_WithConfigOptions` — nonexistent config override paths are silently skipped
  • `TestLogin_LocationRewrite` — login succeeds; no regression on location-rewrite path

Closes: #11771

Copilot AI review requested due to automatic review settings April 21, 2026 19:21
@pull-request-size pull-request-size Bot added the size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. label Apr 21, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Integrates ORAS Go v3’s full registry config stack into Helm’s registry client (policy evaluation, signature verification hooks, configurable config-path loading, and login Location rewrite), while completing remaining v2→v3 API updates.

Changes:

  • Switch to ORAS v3 packages and update reference parsing (properties.NewReference, .Tag).
  • Load and apply full container config stack via remote/config, wiring policy evaluation and optional signature verification into repository construction.
  • Update CLI registry-client wiring to use the command’s out writer and adjust/extend registry tests for the new behavior.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
pkg/registry/transport.go Update ORAS retry transport import to v3 path.
pkg/registry/reference.go Move reference parsing to ORAS v3 properties reference type/API.
pkg/registry/reference_test.go Adjust tests for v3 .Tag field.
pkg/registry/generic.go Refactor GenericClient to delegate repository creation to Client.newRepository().
pkg/registry/client.go Core wiring: config stack loading, builder setup, policy/signature hooks, Location rewrite on login, v3 login/logout APIs.
pkg/registry/client_test.go Update login tests and add tests for policy evaluator/config options/login behavior.
pkg/registry/client_http_test.go Update ORAS content import path to v3.
pkg/cmd/*.go Thread out io.Writer into registry client construction in various commands.
oras-go-v3-plan.md Add migration plan documentation for ORAS v3 integration steps.
go.mod Add ORAS v3 requirement and a local replace to ./oras-go.
go.sum Remove ORAS v2 sums.
Comments suppressed due to low confidence (1)

go.mod:185

  • This committed local-path replace github.com/oras-project/oras-go/v3 => ./oras-go makes the build dependent on a bundled copy of oras-go rather than the module proxy, and can interfere with dependency auditing and reproducible builds. If this is only for local development, it should typically not be committed; otherwise consider vendoring intentionally (documented) or switching to an upstream version/pseudo-version without replace.

replace github.com/oras-project/oras-go/v3 => ./oras-go


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/registry/client.go Outdated
Comment thread pkg/registry/client_test.go Outdated
Comment thread pkg/registry/client_test.go
Comment thread go.mod
Comment thread oras-go-v3-plan.md Outdated
Comment thread pkg/registry/client.go
Comment thread pkg/registry/client.go Outdated
@promptless-for-oss
Copy link
Copy Markdown

Promptless prepared a documentation update related to this change.

Triggered by PR #32065

Added documentation for the new container ecosystem configuration support, including credential discovery from Docker/Podman, configuration file loading (registries.conf, certs.d, policy.json, registries.d), CLI flag precedence, and registry location rewrites.

Review: Document container config stack integration

Copilot AI review requested due to automatic review settings April 21, 2026 19:47
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/cmd/root.go Outdated
Comment thread pkg/registry/client.go Outdated
Copilot AI review requested due to automatic review settings April 21, 2026 20:12
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/cmd/root.go
Comment thread pkg/registry/reference_test.go Outdated
Comment thread pkg/registry/client.go
Copilot AI review requested due to automatic review settings April 21, 2026 20:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/registry/client.go
Comment thread pkg/cmd/show.go Outdated
Comment thread pkg/registry/client.go Outdated
Copilot AI review requested due to automatic review settings April 21, 2026 20:49
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 12 out of 13 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (2)

pkg/registry/client_test.go:101

  • TestLogin_ResetsForceAttemptOAuth2_OnFailure no longer asserts anything about ForceAttemptOAuth2. Rename this test (and its doc comment) to reflect the current behavior being validated (e.g., login fails when the registry is unreachable).
// Verifies that Login restores ForceAttemptOAuth2 to false even when ping fails.
func TestLogin_ResetsForceAttemptOAuth2_OnFailure(t *testing.T) {
	t.Parallel()

pkg/registry/client.go:571

  • LoginOptTLSClientConfig panics for common user error cases (missing/invalid cert/key/CA files, or CA parse failures). Because Helm CLI passes these options directly from flags, this would crash the command instead of returning an actionable error message. Please propagate these errors back through Login() (e.g., record an error in the operation and have Login return it) rather than panicking.
// LoginOptTLSClientConfig returns a function that sets the TLS settings on login.
func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption {
	return func(o *loginOperation) {
		if (certFile == "" || keyFile == "") && caFile == "" {
			return
		}
		// Set file path fields for the config-driven path.
		if certFile != "" && keyFile != "" {
			o.client.certFile = certFile
			o.client.keyFile = keyFile
		}
		if caFile != "" {
			o.client.caFile = caFile
		}
		// Also update the authorizer transport for the legacy path (customHTTPClient=true).
		tlsConfig, err := ensureTLSConfig(o.client.authorizer, nil)
		if err != nil {
			panic(err)
		}
		if certFile != "" && keyFile != "" {
			authCert, err := tls.LoadX509KeyPair(certFile, keyFile)
			if err != nil {
				panic(err)
			}
			tlsConfig.Certificates = []tls.Certificate{authCert}
		}
		if caFile != "" {
			certPool := x509.NewCertPool()
			ca, err := os.ReadFile(caFile)
			if err != nil {
				panic(err)
			}
			if !certPool.AppendCertsFromPEM(ca) {
				panic(fmt.Errorf("unable to parse CA file: %q", caFile))
			}
			tlsConfig.RootCAs = certPool
		}

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/registry/client_test.go
Comment thread pkg/cmd/show.go Outdated
Comment thread pkg/registry/client.go
@TerryHowe
Copy link
Copy Markdown
Contributor Author

  Test plan written → oras-go-v3-test-plan.md                                                                           
   
  Executed against kind cluster:                                                                                        
                                                            
  ┌──────┬──────────────────────────────────────────────────────────────┬───────────────────────────────────────────┐
  │ Step │                             Test                             │                  Result                   │
  ├──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────┤   
  │ 1    │ Login / Logout                                               │ ✅ Login Succeeded / Removing login       │
  │      │                                                              │ credentials                               │   
  ├──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────┤   
  │ 2    │ Push / Pull                                                  │ ✅ Digest matches on both ends            │   
  ├──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────┤   
  │ 3    │ Show / Template                                              │ ✅ Chart metadata and YAML rendered       │   
  │      │                                                              │ correctly                                 │
  ├──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────┤   
  │ 4    │ Install / Upgrade / Rollback / Uninstall                     │ ✅ All lifecycle transitions succeeded    │
  ├──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────┤   
  │ 5    │ Location rewrite (TestLogin_LocationRewrite)                 │ ✅ Credential stored under canonical host │
  │      │                                                              │  only                                     │   
  ├──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────┤
  │ 6    │ Policy enforcement (TestNewClient_WithDenyAllPolicy)         │ ✅ Evaluator instance is the exact        │   
  │      │                                                              │ override (existing test)                  │   
  ├──────┼──────────────────────────────────────────────────────────────┼───────────────────────────────────────────┤
  │ 7    │ registryAuthorizer legacy path                               │ ✅ New test — mock Do confirmed called    │   
  │      │ (TestRegistryAuthorizer_UsedInLegacyPath)                    │                                           │
  └──────┴──────────────────────────────────────────────────────────────┴───────────────────────────────────────────┘

  Steps 5–7 are unit/integration tests (no CLI flag surfaces ConfigOptions or policy), pushed to the branch. 

Update import paths from oras.land/oras-go/v2 to
github.com/oras-project/oras-go/v3. Fix all breaking API changes:
- auth.Credential → credentials.Credential, CredentialFunc field
- ForceAttemptOAuth2 removed; use remote.Login() instead
- auth.StaticCredential → credentials.StaticCredentialFunc
- credentials.Logout → remote.Logout
- repository.PlainHTTP/Client → repository.Registry.PlainHTTP/Client
- StoreOptions.DetectDefaultNativeStore removed
- Blank-import config package to register credential store loader

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Create a symlink at ./oras-go pointing to ../oras-go so the go.mod
replace directive uses ./oras-go instead of ../oras-go.

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Load the full container ecosystem configuration in NewClient() via
config.LoadConfigs(), enabling:
- containers auth.json (Podman/Buildah credentials)
- registries.conf (mirrors, blocked registries, insecure settings)
- certs.d (per-registry CA and client certificates)
- policy.json (ready for policy enforcement)

Use NewRepositoryWithProperties() for Push, Tags, Resolve, and Pull
operations so that registries.conf mirrors and certs.d TLS are applied
automatically. CLI flag overrides (--insecure, --cert-file, --ca-file,
--plain-http, --username/--password) are applied on top via applyOverrides().

When ClientOptHTTPClient or ClientOptAuthorizer is provided (e.g. in tests),
fall back to the legacy direct-authorizer path to preserve custom TLS transport
configuration set via LoginOptTLSClientConfigFromConfig.

GenericClient now holds a *Client reference instead of copying fields,
delegating repository creation to Client.newRepository().

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
…g overrides, and login location rewrite

- Part 3: wire policy.json into ClientBuilder via PolicyEvaluator; add ClientOptPolicyEvaluator
- Part 4: per-repo signature verification via registries.d lookaside; add ClientOptSignatureVerification
- Part 5: expose LoadConfigsWithOptions via ConfigOptions struct; add ClientOptConfigOptions
- Part 6: apply Location rewrite from registries.conf in Login() so credentials are stored under the canonical registry name
- Replace deprecated registry.ParseReference with properties.NewReference
- Replace deprecated orasReference.Reference field access with .Tag

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Use published github.com/oras-project/oras-go/v3 v3.0.0-dev instead of the ./oras-go local replacement.

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
- ClientOptPolicyEvaluator now acts as a true override: only build
  from policy.json when client.policyEvaluator is still nil
- Login: use original alias host for newRegistry() transport settings,
  then redirect reg.Reference.Registry to the canonical host so both
  the connection and credential key are correct
- Logout: apply the same Location rewrite as Login to remove the
  correct credential entry when called with an alias host
- TestNewClient_WithDenyAllPolicy: use require.Same to prove the
  evaluator instance is the exact one passed via the option
- TestLogin_LocationRewrite: write a real registries.conf, login via
  alias, and assert credential is stored under the canonical host only
- oras-go-v3-plan.md: remove claude --resume session identifier

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
- applyOverrides: only set props.Credential when both username and
  password are non-empty, preserving credential-store fallback when
  only a username is present
- root.go: use os.Stderr for registry client status output (Pulled/
  Pushed/Digest lines) so it does not corrupt stdout in data-oriented
  commands like helm template or helm show values

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Signed-off-by: Terry Howe <terrylhowe@gmail.com>
… message

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
…al test plan

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
…, and test names

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
@TerryHowe TerryHowe force-pushed the feature/helm-oras-go-v3-integration branch from 86225b5 to 3991ab2 Compare April 22, 2026 01:53
@TerryHowe TerryHowe marked this pull request as draft April 23, 2026 16:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants