From 5e6a411c1f2d0e75600aa0bef1b2f30cffb8ce83 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 7 Aug 2025 12:22:01 +0100 Subject: [PATCH 1/5] fix: use username and password if provided Ref: #31114 Signed-off-by: Evans Mungai --- go.mod | 2 +- pkg/registry/client.go | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/go.mod b/go.mod index e2f8536a1..b0fef95bc 100644 --- a/go.mod +++ b/go.mod @@ -14,6 +14,7 @@ require ( github.com/cyphar/filepath-securejoin v0.4.1 github.com/distribution/distribution/v3 v3.0.0 github.com/evanphx/json-patch/v5 v5.9.11 + github.com/fatih/color v1.13.0 github.com/fluxcd/cli-utils v0.36.0-flux.14 github.com/foxcpp/go-mockdns v1.1.0 github.com/gobwas/glob v0.2.3 @@ -70,7 +71,6 @@ require ( github.com/docker/go-metrics v0.0.1 // indirect github.com/emicklei/go-restful/v3 v3.12.2 // indirect github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect - github.com/fatih/color v1.13.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fxamacker/cbor/v2 v2.8.0 // indirect github.com/go-errors/errors v1.5.1 // indirect diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 3ea68f181..042f06065 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -128,7 +128,13 @@ func NewClient(options ...ClientOption) (*Client, error) { } authorizer.SetUserAgent(version.GetUserAgent()) - authorizer.Credential = credentials.Credential(client.credentialsStore) + if client.username != "" && client.password != "" { + authorizer.Credential = func(_ context.Context, hostport string) (auth.Credential, error) { + return auth.Credential{Username: client.username, Password: client.password}, nil + } + } else { + authorizer.Credential = credentials.Credential(client.credentialsStore) + } if client.enableCache { authorizer.Cache = auth.NewCache() From 9e1cbbebcb9c6fa5ff919133daf69197862b60a6 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 7 Aug 2025 12:50:54 +0100 Subject: [PATCH 2/5] fix linting warning Signed-off-by: Evans Mungai --- pkg/registry/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/registry/client.go b/pkg/registry/client.go index 042f06065..c86215beb 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -129,7 +129,7 @@ func NewClient(options ...ClientOption) (*Client, error) { authorizer.SetUserAgent(version.GetUserAgent()) if client.username != "" && client.password != "" { - authorizer.Credential = func(_ context.Context, hostport string) (auth.Credential, error) { + authorizer.Credential = func(_ context.Context, _ string) (auth.Credential, error) { return auth.Credential{Username: client.username, Password: client.password}, nil } } else { From 5e86e43edadce10aa798f632050850b1f89680df Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 7 Aug 2025 13:16:07 +0100 Subject: [PATCH 3/5] Add tests for pull command using OCI registry Signed-off-by: Evans Mungai --- pkg/cmd/pull_test.go | 184 ++++++++++++++++++++++++++++++++----------- 1 file changed, 138 insertions(+), 46 deletions(-) diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index c30c94b49..b8e7eff82 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -256,6 +256,77 @@ func TestPullCmd(t *testing.T) { } } +// runPullTests is a helper function to run pull command tests with common logic +func runPullTests(t *testing.T, tests []struct { + name string + args string + existFile string + existDir string + wantError bool + wantErrorMsg string + expectFile string + expectDir bool +}, outdir string, additionalFlags string) { + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + cmd := fmt.Sprintf("pull %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s %s", + tt.args, + outdir, + filepath.Join(outdir, "repositories.yaml"), + outdir, + filepath.Join(outdir, "config.json"), + additionalFlags, + ) + // Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182 + if tt.existFile != "" { + file := filepath.Join(outdir, tt.existFile) + _, err := os.Create(file) + if err != nil { + t.Fatal(err) + } + } + if tt.existDir != "" { + file := filepath.Join(outdir, tt.existDir) + err := os.Mkdir(file, 0755) + if err != nil { + t.Fatal(err) + } + } + _, _, err := executeActionCommand(cmd) + if err != nil { + if tt.wantError { + if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() { + t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg) + } + return + } + t.Fatalf("%q reported error: %s", tt.name, err) + } + + ef := filepath.Join(outdir, tt.expectFile) + fi, err := os.Stat(ef) + if err != nil { + t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err) + } + if fi.IsDir() != tt.expectDir { + t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) + } + }) + } +} + +// buildOCIURL is a helper function to build OCI URLs with credentials +func buildOCIURL(registryURL, chartName, version, username, password string) string { + baseURL := fmt.Sprintf("oci://%s/u/ocitestuser/%s", registryURL, chartName) + if version != "" { + baseURL += fmt.Sprintf(" --version %s", version) + } + if username != "" && password != "" { + baseURL += fmt.Sprintf(" --username %s --password %s", username, password) + } + return baseURL +} + func TestPullWithCredentialsCmd(t *testing.T) { srv := repotest.NewTempServer( t, @@ -311,52 +382,7 @@ func TestPullWithCredentialsCmd(t *testing.T) { }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - outdir := srv.Root() - cmd := fmt.Sprintf("pull %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s", - tt.args, - outdir, - filepath.Join(outdir, "repositories.yaml"), - outdir, - filepath.Join(outdir, "config.json"), - ) - // Create file or Dir before helm pull --untar, see: https://github.com/helm/helm/issues/7182 - if tt.existFile != "" { - file := filepath.Join(outdir, tt.existFile) - _, err := os.Create(file) - if err != nil { - t.Fatal(err) - } - } - if tt.existDir != "" { - file := filepath.Join(outdir, tt.existDir) - err := os.Mkdir(file, 0755) - if err != nil { - t.Fatal(err) - } - } - _, _, err := executeActionCommand(cmd) - if err != nil { - if tt.wantError { - if tt.wantErrorMsg != "" && tt.wantErrorMsg == err.Error() { - t.Fatalf("Actual error %s, not equal to expected error %s", err, tt.wantErrorMsg) - } - return - } - t.Fatalf("%q reported error: %s", tt.name, err) - } - - ef := filepath.Join(outdir, tt.expectFile) - fi, err := os.Stat(ef) - if err != nil { - t.Errorf("%q: expected a file at %s. %s", tt.name, ef, err) - } - if fi.IsDir() != tt.expectDir { - t.Errorf("%q: expected directory=%t, but it's not.", tt.name, tt.expectDir) - } - }) - } + runPullTests(t, tests, srv.Root(), "") } func TestPullVersionCompletion(t *testing.T) { @@ -389,6 +415,72 @@ func TestPullVersionCompletion(t *testing.T) { runTestCmd(t, tests) } +func TestPullWithCredentialsCmdOCIRegistry(t *testing.T) { + srv := repotest.NewTempServer( + t, + repotest.WithChartSourceGlob("testdata/testcharts/*.tgz*"), + ) + defer srv.Stop() + + ociSrv, err := repotest.NewOCIServer(t, srv.Root()) + if err != nil { + t.Fatal(err) + } + ociSrv.Run(t) + + if err := srv.LinkIndices(); err != nil { + t.Fatal(err) + } + + // all flags will get "-d outdir" appended. + tests := []struct { + name string + args string + existFile string + existDir string + wantError bool + wantErrorMsg string + expectFile string + expectDir bool + }{ + { + name: "OCI Chart fetch with credentials", + args: buildOCIURL(ociSrv.RegistryURL, "oci-dependent-chart", "0.1.0", ociSrv.TestUsername, ociSrv.TestPassword), + expectFile: "./oci-dependent-chart-0.1.0.tgz", + }, + { + name: "OCI Chart fetch with credentials and untar", + args: buildOCIURL(ociSrv.RegistryURL, "oci-dependent-chart", "0.1.0", ociSrv.TestUsername, ociSrv.TestPassword) + " --untar", + expectFile: "./oci-dependent-chart", + expectDir: true, + }, + { + name: "OCI Chart fetch with credentials and untardir", + args: buildOCIURL(ociSrv.RegistryURL, "oci-dependent-chart", "0.1.0", ociSrv.TestUsername, ociSrv.TestPassword) + " --untar --untardir ocitest-credentials", + expectFile: "./ocitest-credentials", + expectDir: true, + }, + { + name: "Fail fetching OCI chart with wrong credentials", + args: buildOCIURL(ociSrv.RegistryURL, "oci-dependent-chart", "0.1.0", "wronguser", "wrongpass"), + wantError: true, + }, + { + name: "Fail fetching non-existent OCI chart with credentials", + args: buildOCIURL(ociSrv.RegistryURL, "nosuchthing", "0.1.0", ociSrv.TestUsername, ociSrv.TestPassword), + wantError: true, + }, + { + name: "Fail fetching OCI chart without version specified", + args: buildOCIURL(ociSrv.RegistryURL, "nosuchthing", "", ociSrv.TestUsername, ociSrv.TestPassword), + wantErrorMsg: "Error: --version flag is explicitly required for OCI registries", + wantError: true, + }, + } + + runPullTests(t, tests, srv.Root(), "--plain-http") +} + func TestPullFileCompletion(t *testing.T) { checkFileCompletion(t, "pull", false) checkFileCompletion(t, "pull repo/chart", false) From 97af5a5e85036d951db7de6f788309bca4c68e60 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Thu, 7 Aug 2025 13:31:18 +0100 Subject: [PATCH 4/5] Fix linter warning Signed-off-by: Evans Mungai --- pkg/cmd/pull_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/cmd/pull_test.go b/pkg/cmd/pull_test.go index b8e7eff82..6a1d3ec0d 100644 --- a/pkg/cmd/pull_test.go +++ b/pkg/cmd/pull_test.go @@ -267,6 +267,7 @@ func runPullTests(t *testing.T, tests []struct { expectFile string expectDir bool }, outdir string, additionalFlags string) { + t.Helper() for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { cmd := fmt.Sprintf("pull %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s %s", From 4aa2240750595241a724c87118db3ff556bfc2e4 Mon Sep 17 00:00:00 2001 From: Evans Mungai Date: Mon, 18 Aug 2025 09:18:02 +0100 Subject: [PATCH 5/5] Run go mod tidy Signed-off-by: Evans Mungai --- go.mod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e3ed6d975..688094670 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/cyphar/filepath-securejoin v0.4.1 github.com/distribution/distribution/v3 v3.0.0 github.com/evanphx/json-patch/v5 v5.9.11 - github.com/fatih/color v1.18.0 + github.com/fatih/color v1.18.0 github.com/fluxcd/cli-utils v0.36.0-flux.14 github.com/foxcpp/go-mockdns v1.1.0 github.com/gobwas/glob v0.2.3