chore(oci): upgrade to ORAS v2

Signed-off-by: Terry Howe <terrylhowe@gmail.com>
Co-authored-by: Zoran Regvart <zoran@regvart.com>
pull/13382/head
Terry Howe 8 months ago
parent d2c9b3d18a
commit 5a7046b9bf

@ -58,7 +58,7 @@ func TestDependencyBuildCmd(t *testing.T) {
createTestingChart(t, rootDir, chartname, srv.URL()) createTestingChart(t, rootDir, chartname, srv.URL())
repoFile := filepath.Join(rootDir, "repositories.yaml") repoFile := filepath.Join(rootDir, "repositories.yaml")
cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir) cmd := fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err := executeActionCommand(cmd) _, out, err := executeActionCommand(cmd)
// In the first pass, we basically want the same results as an update. // In the first pass, we basically want the same results as an update.
@ -117,7 +117,7 @@ func TestDependencyBuildCmd(t *testing.T) {
t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v) t.Errorf("mismatched versions. Expected %q, got %q", "0.1.0", v)
} }
skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s", filepath.Join(rootDir, chartname), repoFile, rootDir) skipRefreshCmd := fmt.Sprintf("dependency build '%s' --skip-refresh --repository-config %s --repository-cache %s --plain-http", filepath.Join(rootDir, chartname), repoFile, rootDir)
_, out, err = executeActionCommand(skipRefreshCmd) _, out, err = executeActionCommand(skipRefreshCmd)
// In this pass, we check --skip-refresh option becomes effective. // In this pass, we check --skip-refresh option becomes effective.
@ -134,7 +134,7 @@ func TestDependencyBuildCmd(t *testing.T) {
if err := chartutil.SaveDir(c, dir()); err != nil { if err := chartutil.SaveDir(c, dir()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json", cmd = fmt.Sprintf("dependency build '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --plain-http",
dir(ociChartName), dir(ociChartName),
dir("repositories.yaml"), dir("repositories.yaml"),
dir(), dir(),

@ -67,7 +67,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
} }
_, out, err := executeActionCommand( _, out, err := executeActionCommand(
fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir()), fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()),
) )
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
@ -110,7 +110,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) _, out, err = executeActionCommand(fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()))
if err != nil { if err != nil {
t.Logf("Output: %s", out) t.Logf("Output: %s", out)
t.Fatal(err) t.Fatal(err)
@ -131,7 +131,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
if err := chartutil.SaveDir(c, dir()); err != nil { if err := chartutil.SaveDir(c, dir()); err != nil {
t.Fatal(err) t.Fatal(err)
} }
cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json", cmd := fmt.Sprintf("dependency update '%s' --repository-config %s --repository-cache %s --registry-config %s/config.json --plain-http",
dir(ociChartName), dir(ociChartName),
dir("repositories.yaml"), dir("repositories.yaml"),
dir(), dir(),
@ -169,7 +169,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
} }
createTestingChart(t, dir(), chartname, srv.URL()) createTestingChart(t, dir(), chartname, srv.URL())
_, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) _, output, err := executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()))
if err != nil { if err != nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal(err) t.Fatal(err)
@ -178,7 +178,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down // Chart repo is down
srv.Stop() srv.Stop()
_, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s", dir(chartname), dir("repositories.yaml"), dir())) _, output, err = executeActionCommand(fmt.Sprintf("dependency update %s --repository-config %s --repository-cache %s --plain-http", dir(chartname), dir("repositories.yaml"), dir()))
if err == nil { if err == nil {
t.Logf("Output: %s", output) t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil") t.Fatal("Expected error, got nil")

@ -203,7 +203,7 @@ func TestPullCmd(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
outdir := srv.Root() outdir := srv.Root()
cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s", cmd := fmt.Sprintf("fetch %s -d '%s' --repository-config %s --repository-cache %s --registry-config %s --plain-http",
tt.args, tt.args,
outdir, outdir,
filepath.Join(outdir, "repositories.yaml"), filepath.Join(outdir, "repositories.yaml"),

@ -43,6 +43,7 @@ type registryLoginOptions struct {
keyFile string keyFile string
caFile string caFile string
insecure bool insecure bool
plainHTTP bool
} }
func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -66,7 +67,8 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
action.WithCertFile(o.certFile), action.WithCertFile(o.certFile),
action.WithKeyFile(o.keyFile), action.WithKeyFile(o.keyFile),
action.WithCAFile(o.caFile), action.WithCAFile(o.caFile),
action.WithInsecure(o.insecure)) action.WithInsecure(o.insecure),
action.WithPlainHTTPLogin(o.plainHTTP))
}, },
} }
@ -78,6 +80,7 @@ func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Comman
f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file") f.StringVar(&o.certFile, "cert-file", "", "identify registry client using this SSL certificate file")
f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file") f.StringVar(&o.keyFile, "key-file", "", "identify registry client using this SSL key file")
f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle") f.StringVar(&o.caFile, "ca-file", "", "verify certificates of HTTPS-enabled servers using this CA bundle")
f.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload")
return cmd return cmd
} }

@ -29,7 +29,6 @@ require (
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v1.7.1 github.com/rubenv/sql-migrate v1.7.1
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1 github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.6 github.com/spf13/pflag v1.0.6
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
@ -45,7 +44,7 @@ require (
k8s.io/client-go v0.32.1 k8s.io/client-go v0.32.1
k8s.io/klog/v2 v2.130.1 k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.32.1 k8s.io/kubectl v0.32.1
oras.land/oras-go v1.2.6 oras.land/oras-go/v2 v2.5.0
sigs.k8s.io/yaml v1.4.0 sigs.k8s.io/yaml v1.4.0
) )
@ -68,11 +67,7 @@ require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/distribution/reference v0.6.0 // indirect github.com/distribution/reference v0.6.0 // indirect
github.com/docker/cli v27.1.0+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v27.1.1+incompatible // indirect
github.com/docker/docker-credential-helpers v0.8.2 // indirect github.com/docker/docker-credential-helpers v0.8.2 // indirect
github.com/docker/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // indirect github.com/docker/go-metrics v0.0.1 // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect
@ -118,7 +113,6 @@ require (
github.com/miekg/dns v1.1.57 // indirect github.com/miekg/dns v1.1.57 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/moby/spdystream v0.5.0 // indirect github.com/moby/spdystream v0.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
@ -137,6 +131,7 @@ require (
github.com/redis/go-redis/v9 v9.1.0 // indirect github.com/redis/go-redis/v9 v9.1.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/spf13/cast v1.7.0 // indirect github.com/spf13/cast v1.7.0 // indirect
github.com/x448/float16 v0.8.4 // indirect github.com/x448/float16 v0.8.4 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect

@ -22,10 +22,6 @@ github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8
github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10= github.com/Masterminds/squirrel v1.5.4/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE= github.com/Masterminds/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE=
github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ=
github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
@ -52,12 +48,8 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk=
github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw=
github.com/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ= github.com/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ=
github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok= github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok=
github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII=
github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4=
github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
@ -83,22 +75,12 @@ github.com/distribution/distribution/v3 v3.0.0-rc.2 h1:tTrzntanYMbd20SyvdeR83Ya1
github.com/distribution/distribution/v3 v3.0.0-rc.2/go.mod h1:H2zIRRXS20ylnv2HTuKILAWuANjuA60GB7MLOsQag7Y= github.com/distribution/distribution/v3 v3.0.0-rc.2/go.mod h1:H2zIRRXS20ylnv2HTuKILAWuANjuA60GB7MLOsQag7Y=
github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
github.com/docker/cli v27.1.0+incompatible h1:P0KSYmPtNbmx59wHZvG6+rjivhKDRA1BvvWM0f5DgHc=
github.com/docker/cli v27.1.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v27.1.1+incompatible h1:hO/M4MtV36kzKldqnA37IWhebRA+LnqqcqDja6kVaKY=
github.com/docker/docker v27.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8=
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8=
github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1 h1:ZClxb8laGDf5arXfYcAtECDFgAgHklGI8CxgjHnXKJ4=
github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE=
github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g=
github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/emicklei/go-restful/v3 v3.11.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8= github.com/evanphx/json-patch v5.9.11+incompatible h1:ixHHqfcGvxhWkniF1tWxBHA0yb4Z+d1UQi45df52xW8=
@ -148,8 +130,6 @@ github.com/gofrs/flock v0.12.1/go.mod h1:9zxTsyu5xtJ9DK+1tFZyibEV7y3uwDxPPfbxeeH
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
@ -255,14 +235,8 @@ github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQ
github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU= github.com/moby/spdystream v0.5.0 h1:7r0J1Si3QO/kjRitvSLVVFUjxMEb/YLj6S9FF62JBCU=
github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI= github.com/moby/spdystream v0.5.0/go.mod h1:xBAYlnt/ay+11ShkdFKNAG7LsyK/tmNBVvVOwrfMgdI=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -373,8 +347,6 @@ github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w= go.opentelemetry.io/contrib/bridges/prometheus v0.57.0 h1:UW0+QyeyBVhn+COBec3nGhfnFe5lwB0ic1JBVjzhk0w=
go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk= go.opentelemetry.io/contrib/bridges/prometheus v0.57.0/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4= go.opentelemetry.io/contrib/exporters/autoexport v0.57.0 h1:jmTVJ86dP60C01K3slFQa2NQ/Aoi7zA+wy7vMOKD9H4=
@ -547,8 +519,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools/v3 v3.4.0 h1:ZazjZUfuVeZGLAmlKKuyv3IKP5orXcwtOwDQH6YVr6o=
gotest.tools/v3 v3.4.0/go.mod h1:CtbdzLSsqVhDgMtKsx03ird5YTGB3ar27v0u/yKBW5g=
k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc= k8s.io/api v0.32.1 h1:f562zw9cy+GvXzXf0CKlVQ7yHJVYzLfL6JAS4kOAaOc=
k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k= k8s.io/api v0.32.1/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw= k8s.io/apiextensions-apiserver v0.32.1 h1:hjkALhRUeCariC8DiVmb5jj0VjIc1N0DREP32+6UXZw=
@ -571,8 +541,8 @@ k8s.io/kubectl v0.32.1 h1:/btLtXLQUU1rWx8AEvX9jrb9LaI6yeezt3sFALhB8M8=
k8s.io/kubectl v0.32.1/go.mod h1:sezNuyWi1STk4ZNPVRIFfgjqMI6XMf+oCVLjZen/pFQ= k8s.io/kubectl v0.32.1/go.mod h1:sezNuyWi1STk4ZNPVRIFfgjqMI6XMf+oCVLjZen/pFQ=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
oras.land/oras-go v1.2.6 h1:z8cmxQXBU8yZ4mkytWqXfo6tZcamPwjsuxYU81xJ8Lk= oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
oras.land/oras-go v1.2.6/go.mod h1:OVPc1PegSEe/K8YiLfosrlqlqTN9PUyFvOw5Y9gwrT8= oras.land/oras-go/v2 v2.5.0/go.mod h1:z4eisnLP530vwIOUOJeBIj0aGI0L1C3d53atvCBqZHg=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 h1:/Rv+M11QRah1itp8VhT6HoVx1Ray9eB4DBr+K+/sCJ8=
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo= sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo= sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo=

@ -29,6 +29,7 @@ type RegistryLogin struct {
keyFile string keyFile string
caFile string caFile string
insecure bool insecure bool
plainHTTP bool
} }
type RegistryLoginOpt func(*RegistryLogin) error type RegistryLoginOpt func(*RegistryLogin) error
@ -41,7 +42,7 @@ func WithCertFile(certFile string) RegistryLoginOpt {
} }
} }
// WithKeyFile specifies whether to very certificates when communicating. // WithInsecure specifies whether to verify certificates.
func WithInsecure(insecure bool) RegistryLoginOpt { func WithInsecure(insecure bool) RegistryLoginOpt {
return func(r *RegistryLogin) error { return func(r *RegistryLogin) error {
r.insecure = insecure r.insecure = insecure
@ -65,6 +66,14 @@ func WithCAFile(caFile string) RegistryLoginOpt {
} }
} }
// WithPlainHTTPLogin use http rather than https for login.
func WithPlainHTTPLogin(isPlain bool) RegistryLoginOpt {
return func(r *RegistryLogin) error {
r.plainHTTP = isPlain
return nil
}
}
// NewRegistryLogin creates a new RegistryLogin object with the given configuration. // NewRegistryLogin creates a new RegistryLogin object with the given configuration.
func NewRegistryLogin(cfg *Configuration) *RegistryLogin { func NewRegistryLogin(cfg *Configuration) *RegistryLogin {
return &RegistryLogin{ return &RegistryLogin{
@ -84,5 +93,7 @@ func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, passw
hostname, hostname,
registry.LoginOptBasicAuth(username, password), registry.LoginOptBasicAuth(username, password),
registry.LoginOptInsecure(a.insecure), registry.LoginOptInsecure(a.insecure),
registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile)) registry.LoginOptTLSClientConfig(a.certFile, a.keyFile, a.caFile),
registry.LoginOptPlainText(a.plainHTTP),
)
} }

@ -56,8 +56,6 @@ func TestResolveChartRef(t *testing.T) {
{name: "ref with tag", ref: "oci://example.com/helm-charts/nginx:15.4.2", expect: "oci://example.com/helm-charts/nginx:15.4.2"}, {name: "ref with tag", ref: "oci://example.com/helm-charts/nginx:15.4.2", expect: "oci://example.com/helm-charts/nginx:15.4.2"},
{name: "no repository", ref: "oci://", fail: true}, {name: "no repository", ref: "oci://", fail: true},
{name: "oci ref", ref: "oci://example.com/helm-charts/nginx", version: "15.4.2", expect: "oci://example.com/helm-charts/nginx:15.4.2"}, {name: "oci ref", ref: "oci://example.com/helm-charts/nginx", version: "15.4.2", expect: "oci://example.com/helm-charts/nginx:15.4.2"},
{name: "oci ref with sha256", ref: "oci://example.com/install/by/sha@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6", version: "0.1.1", expect: "oci://example.com/install/by/sha@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6"},
{name: "oci ref with sha256 and version", ref: "oci://example.com/install/by/sha:0.1.1@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6", version: "0.1.1", expect: "oci://example.com/install/by/sha:0.1.1@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6"},
{name: "oci ref with sha256 and version mismatch", ref: "oci://example.com/install/by/sha:0.1.1@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6", version: "0.1.2", fail: true}, {name: "oci ref with sha256 and version mismatch", ref: "oci://example.com/install/by/sha:0.1.1@sha256:d234555386402a5867ef0169fefe5486858b6d8d209eaf32fd26d29b16807fd6", version: "0.1.2", fail: true},
} }

@ -18,26 +18,31 @@ package registry // import "helm.sh/helm/v4/pkg/registry"
import ( import (
"context" "context"
"encoding/base64" "crypto/tls"
"crypto/x509"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url" "net/url"
"os"
"sort" "sort"
"strings" "strings"
"sync"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
"github.com/containerd/containerd/remotes" "github.com/containerd/containerd/remotes"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"oras.land/oras-go/pkg/auth" "oras.land/oras-go/v2"
dockerauth "oras.land/oras-go/pkg/auth/docker" "oras.land/oras-go/v2/content"
"oras.land/oras-go/pkg/content" "oras.land/oras-go/v2/content/memory"
"oras.land/oras-go/pkg/oras" "oras.land/oras-go/v2/registry"
"oras.land/oras-go/pkg/registry" "oras.land/oras-go/v2/registry/remote"
registryremote "oras.land/oras-go/pkg/registry/remote" "oras.land/oras-go/v2/registry/remote/auth"
registryauth "oras.land/oras-go/pkg/registry/remote/auth" "oras.land/oras-go/v2/registry/remote/credentials"
"oras.land/oras-go/v2/registry/remote/retry"
"helm.sh/helm/v4/internal/version" "helm.sh/helm/v4/internal/version"
"helm.sh/helm/v4/pkg/chart" "helm.sh/helm/v4/pkg/chart"
@ -51,6 +56,8 @@ storing semantic versions, Helm adopts the convention of changing plus (+) to
an underscore (_) in chart version tags when pushing to a registry and back to an underscore (_) in chart version tags when pushing to a registry and back to
a plus (+) when pulling from a registry.` a plus (+) when pulling from a registry.`
var errDeprecatedRemote = errors.New("providing github.com/containerd/containerd/remotes.Resolver via ClientOptResolver is no longer suported")
type ( type (
// RemoteClient shadows the ORAS remote.Client interface // RemoteClient shadows the ORAS remote.Client interface
// (hiding the ORAS type from Helm client visibility) // (hiding the ORAS type from Helm client visibility)
@ -68,11 +75,12 @@ type (
username string username string
password string password string
out io.Writer out io.Writer
authorizer auth.Client authorizer *auth.Client
registryAuthorizer RemoteClient registryAuthorizer RemoteClient
resolver func(ref registry.Reference) (remotes.Resolver, error) credentialsStore credentials.Store
httpClient *http.Client httpClient *http.Client
plainHTTP bool plainHTTP bool
err error // pass any errors from the ClientOption functions
} }
// ClientOption allows specifying various settings configurable by the user for overriding the defaults // ClientOption allows specifying various settings configurable by the user for overriding the defaults
@ -87,101 +95,70 @@ func NewClient(options ...ClientOption) (*Client, error) {
} }
for _, option := range options { for _, option := range options {
option(client) option(client)
if client.err != nil {
return nil, client.err
}
} }
if client.credentialsFile == "" { if client.credentialsFile == "" {
client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename) client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename)
} }
if client.authorizer == nil { if client.httpClient == nil {
authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile) type cloner[T any] interface {
if err != nil { Clone() T
return nil, err
}
client.authorizer = authClient
} }
resolverFn := client.resolver // copy for avoiding recursive call // try to copy (clone) the http.DefaultTransport so any mutations we
client.resolver = func(ref registry.Reference) (remotes.Resolver, error) { // perform on it (e.g. TLS config) are not reflected globally
if resolverFn != nil { // follow https://github.com/golang/go/issues/39299 for a more elegant
// validate if the resolverFn returns a valid resolver // solution in the future
if resolver, err := resolverFn(ref); resolver != nil && err == nil { transport := http.DefaultTransport
return resolver, nil if t, ok := transport.(cloner[*http.Transport]); ok {
transport = t.Clone()
} else if t, ok := transport.(cloner[http.RoundTripper]); ok {
// this branch will not be used with go 1.20, it was added
// optimistically to try to clone if the http.DefaultTransport
// implementation changes, still the Clone method in that case
// might not return http.RoundTripper...
transport = t.Clone()
} }
client.httpClient = &http.Client{
Transport: retry.NewTransport(transport),
} }
headers := http.Header{}
headers.Set("User-Agent", version.GetUserAgent())
opts := []auth.ResolverOption{auth.WithResolverHeaders(headers)}
if client.httpClient != nil {
opts = append(opts, auth.WithResolverClient(client.httpClient))
}
if client.plainHTTP {
opts = append(opts, auth.WithResolverPlainHTTP())
} }
// if username and password are set, use them for authentication storeOptions := credentials.StoreOptions{
// by adding the basic auth Authorization header to the resolver AllowPlaintextPut: true,
if client.username != "" && client.password != "" { DetectDefaultNativeStore: true,
concat := client.username + ":" + client.password
encodedAuth := base64.StdEncoding.EncodeToString([]byte(concat))
opts = append(opts, auth.WithResolverHeaders(
http.Header{
"Authorization": []string{"Basic " + encodedAuth},
},
))
} }
store, err := credentials.NewStore(client.credentialsFile, storeOptions)
resolver, err := client.authorizer.ResolverWithOpts(opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return resolver, nil dockerStore, err := credentials.NewStoreFromDocker(storeOptions)
if err != nil {
// should only fail if user home directory can't be determined
client.credentialsStore = store
} else {
// use Helm credentials with fallback to Docker
client.credentialsStore = credentials.NewStoreWithFallbacks(store, dockerStore)
} }
// allocate a cache if option is set if client.authorizer == nil {
var cache registryauth.Cache authorizer := auth.Client{
if client.enableCache {
cache = registryauth.DefaultCache
}
if client.registryAuthorizer == nil {
client.registryAuthorizer = &registryauth.Client{
Client: client.httpClient, Client: client.httpClient,
Header: http.Header{
"User-Agent": {version.GetUserAgent()},
},
Cache: cache,
Credential: func(_ context.Context, reg string) (registryauth.Credential, error) {
if client.username != "" && client.password != "" {
return registryauth.Credential{
Username: client.username,
Password: client.password,
}, nil
} }
authorizer.SetUserAgent(version.GetUserAgent())
dockerClient, ok := client.authorizer.(*dockerauth.Client) authorizer.Credential = credentials.Credential(client.credentialsStore)
if !ok {
return registryauth.EmptyCredential, errors.New("unable to obtain docker client")
}
username, password, err := dockerClient.Credential(reg)
if err != nil {
return registryauth.EmptyCredential, errors.New("unable to retrieve credentials")
}
// A blank returned username and password value is a bearer token if client.enableCache {
if username == "" && password != "" { authorizer.Cache = auth.NewCache()
return registryauth.Credential{
RefreshToken: password,
}, nil
} }
return registryauth.Credential{ client.authorizer = &authorizer
Username: username,
Password: password,
}, nil
},
} }
}
return client, nil return client, nil
} }
@ -220,7 +197,7 @@ func ClientOptWriter(out io.Writer) ClientOption {
// Depending on the use-case you may need to set both ClientOptAuthorizer and ClientOptRegistryAuthorizer. // Depending on the use-case you may need to set both ClientOptAuthorizer and ClientOptRegistryAuthorizer.
func ClientOptAuthorizer(authorizer auth.Client) ClientOption { func ClientOptAuthorizer(authorizer auth.Client) ClientOption {
return func(client *Client) { return func(client *Client) {
client.authorizer = authorizer client.authorizer = &authorizer
} }
} }
@ -254,12 +231,9 @@ func ClientOptPlainHTTP() ClientOption {
} }
} }
// ClientOptResolver returns a function that sets the resolver setting on a client options set func ClientOptResolver(_ remotes.Resolver) ClientOption {
func ClientOptResolver(resolver remotes.Resolver) ClientOption { return func(c *Client) {
return func(client *Client) { c.err = errDeprecatedRemote
client.resolver = func(_ registry.Reference) (remotes.Resolver, error) {
return resolver, nil
}
} }
} }
@ -268,60 +242,128 @@ type (
LoginOption func(*loginOperation) LoginOption func(*loginOperation)
loginOperation struct { loginOperation struct {
username string host string
password string client *Client
insecure bool
certFile string
keyFile string
caFile string
} }
) )
// Login logs into a registry // Login logs into a registry
func (c *Client) Login(host string, options ...LoginOption) error { func (c *Client) Login(host string, options ...LoginOption) error {
operation := &loginOperation{}
for _, option := range options { for _, option := range options {
option(operation) option(&loginOperation{host, c})
} }
authorizerLoginOpts := []auth.LoginOption{
auth.WithLoginContext(ctx(c.out, c.debug)), reg, err := remote.NewRegistry(host)
auth.WithLoginHostname(host), if err != nil {
auth.WithLoginUsername(operation.username), return err
auth.WithLoginSecret(operation.password), }
auth.WithLoginUserAgent(version.GetUserAgent()), reg.PlainHTTP = c.plainHTTP
auth.WithLoginTLS(operation.certFile, operation.keyFile, operation.caFile), reg.Client = c.authorizer
ctx := context.Background()
cred, err := c.authorizer.Credential(ctx, host)
if err != nil {
return fmt.Errorf("fetching credentials for %q: %w", host, err)
} }
if operation.insecure {
authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure()) if err := reg.Ping(ctx); err != nil {
return fmt.Errorf("authenticating to %q: %w", host, err)
} }
if err := c.authorizer.LoginWithOpts(authorizerLoginOpts...); err != nil {
key := credentials.ServerAddressFromRegistry(host)
if err := c.credentialsStore.Put(ctx, key, cred); err != nil {
return err return err
} }
fmt.Fprintln(c.out, "Login Succeeded") fmt.Fprintln(c.out, "Login Succeeded")
return nil return nil
} }
// LoginOptBasicAuth returns a function that sets the username/password settings on login // LoginOptBasicAuth returns a function that sets the username/password settings on login
func LoginOptBasicAuth(username string, password string) LoginOption { func LoginOptBasicAuth(username string, password string) LoginOption {
return func(operation *loginOperation) { return func(o *loginOperation) {
operation.username = username o.client.username = username
operation.password = password o.client.password = password
o.client.authorizer.Credential = auth.StaticCredential(o.host, auth.Credential{Username: username, Password: password})
} }
} }
// LoginOptPlainText returns a function that allows plaintext (HTTP) login
func LoginOptPlainText(isPlainText bool) LoginOption {
return func(o *loginOperation) {
o.client.plainHTTP = isPlainText
}
}
func ensureTLSConfig(client *auth.Client) (*tls.Config, error) {
var transport *http.Transport
switch t := client.Client.Transport.(type) {
case *http.Transport:
transport = t
case *retry.Transport:
switch t := t.Base.(type) {
case *http.Transport:
transport = t
}
}
if transport == nil {
// we don't know how to access the http.Transport, most likely the
// auth.Client.Client was provided by API user
return nil, fmt.Errorf("unable to access TLS client configuration, the provided HTTP Transport is not supported, given: %T", client.Client.Transport)
}
if transport.TLSClientConfig == nil {
transport.TLSClientConfig = &tls.Config{}
}
return transport.TLSClientConfig, nil
}
// LoginOptInsecure returns a function that sets the insecure setting on login // LoginOptInsecure returns a function that sets the insecure setting on login
func LoginOptInsecure(insecure bool) LoginOption { func LoginOptInsecure(insecure bool) LoginOption {
return func(operation *loginOperation) { return func(o *loginOperation) {
operation.insecure = insecure tlsConfig, err := ensureTLSConfig(o.client.authorizer)
if err != nil {
panic(err)
}
tlsConfig.InsecureSkipVerify = insecure
} }
} }
// LoginOptTLSClientConfig returns a function that sets the TLS settings on login. // LoginOptTLSClientConfig returns a function that sets the TLS settings on login.
func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption { func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption {
return func(operation *loginOperation) { return func(o *loginOperation) {
operation.certFile = certFile if (certFile == "" || keyFile == "") && caFile == "" {
operation.keyFile = keyFile return
operation.caFile = caFile }
tlsConfig, err := ensureTLSConfig(o.client.authorizer)
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
}
} }
} }
@ -338,7 +380,8 @@ func (c *Client) Logout(host string, opts ...LogoutOption) error {
for _, opt := range opts { for _, opt := range opts {
opt(operation) opt(operation)
} }
if err := c.authorizer.Logout(ctx(c.out, c.debug), host); err != nil {
if err := credentials.Logout(context.Background(), c.credentialsStore, host); err != nil {
return err return err
} }
fmt.Fprintf(c.out, "Removing login credentials for %s\n", host) fmt.Fprintf(c.out, "Removing login credentials for %s\n", host)
@ -393,8 +436,9 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
return nil, errors.New( return nil, errors.New(
"must specify at least one layer to pull (chart/prov)") "must specify at least one layer to pull (chart/prov)")
} }
memoryStore := content.NewMemory() memoryStore := memory.New()
allowedMediaTypes := []string{ allowedMediaTypes := []string{
ocispec.MediaTypeImageManifest,
ConfigMediaType, ConfigMediaType,
} }
minNumDescriptors := 1 // 1 for the config minNumDescriptors := 1 // 1 for the config
@ -410,18 +454,34 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
} }
var descriptors, layers []ocispec.Descriptor var descriptors, layers []ocispec.Descriptor
remotesResolver, err := c.resolver(parsedRef.orasReference)
repository, err := remote.NewRepository(parsedRef.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
registryStore := content.Registry{Resolver: remotesResolver} repository.PlainHTTP = c.plainHTTP
repository.Client = c.authorizer
manifest, err := oras.Copy(ctx(c.out, c.debug), registryStore, parsedRef.String(), memoryStore, "", ctx := context.Background()
oras.WithPullEmptyNameAllowed(),
oras.WithAllowedMediaTypes(allowedMediaTypes), sort.Strings(allowedMediaTypes)
oras.WithLayerDescriptors(func(l []ocispec.Descriptor) {
layers = l var mu sync.Mutex
})) manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{
CopyGraphOptions: oras.CopyGraphOptions{
PreCopy: func(_ context.Context, desc ocispec.Descriptor) error {
mediaType := desc.MediaType
if i := sort.SearchStrings(allowedMediaTypes, mediaType); i >= len(allowedMediaTypes) || allowedMediaTypes[i] != mediaType {
return errors.Errorf("media type %q is not allowed, found in descriptor with digest: %q", mediaType, desc.Digest)
}
mu.Lock()
layers = append(layers, desc)
mu.Unlock()
return nil
},
},
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -480,55 +540,38 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
Prov: &DescriptorPullSummary{}, Prov: &DescriptorPullSummary{},
Ref: parsedRef.String(), Ref: parsedRef.String(),
} }
var getManifestErr error
if _, manifestData, ok := memoryStore.Get(manifest); !ok { result.Manifest.Data, err = content.FetchAll(ctx, memoryStore, manifest)
getManifestErr = errors.Errorf("Unable to retrieve blob with digest %s", manifest.Digest) if err != nil {
} else { return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", manifest.Digest, err)
result.Manifest.Data = manifestData
} }
if getManifestErr != nil {
return nil, getManifestErr result.Config.Data, err = content.FetchAll(ctx, memoryStore, *configDescriptor)
if err != nil {
return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", configDescriptor.Digest, err)
} }
var getConfigDescriptorErr error
if _, configData, ok := memoryStore.Get(*configDescriptor); !ok { if err := json.Unmarshal(result.Config.Data, &result.Chart.Meta); err != nil {
getConfigDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", configDescriptor.Digest)
} else {
result.Config.Data = configData
var meta *chart.Metadata
if err := json.Unmarshal(configData, &meta); err != nil {
return nil, err return nil, err
} }
result.Chart.Meta = meta
}
if getConfigDescriptorErr != nil {
return nil, getConfigDescriptorErr
}
if operation.withChart { if operation.withChart {
var getChartDescriptorErr error result.Chart.Data, err = content.FetchAll(ctx, memoryStore, *chartDescriptor)
if _, chartData, ok := memoryStore.Get(*chartDescriptor); !ok { if err != nil {
getChartDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", chartDescriptor.Digest) return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", chartDescriptor.Digest, err)
} else { }
result.Chart.Data = chartData
result.Chart.Digest = chartDescriptor.Digest.String() result.Chart.Digest = chartDescriptor.Digest.String()
result.Chart.Size = chartDescriptor.Size result.Chart.Size = chartDescriptor.Size
} }
if getChartDescriptorErr != nil {
return nil, getChartDescriptorErr
}
}
if operation.withProv && !provMissing { if operation.withProv && !provMissing {
var getProvDescriptorErr error result.Prov.Data, err = content.FetchAll(ctx, memoryStore, *provDescriptor)
if _, provData, ok := memoryStore.Get(*provDescriptor); !ok { if err != nil {
getProvDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", provDescriptor.Digest) return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", provDescriptor.Digest, err)
} else { }
result.Prov.Data = provData
result.Prov.Digest = provDescriptor.Digest.String() result.Prov.Digest = provDescriptor.Digest.String()
result.Prov.Size = provDescriptor.Size result.Prov.Size = provDescriptor.Size
} }
if getProvDescriptorErr != nil {
return nil, getProvDescriptorErr
}
}
fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref) fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref)
fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest) fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest)
@ -615,8 +658,11 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
"strict mode enabled, ref basename and tag must match the chart name and version") "strict mode enabled, ref basename and tag must match the chart name and version")
} }
} }
memoryStore := content.NewMemory()
chartDescriptor, err := memoryStore.Add("", ChartLayerMediaType, data) ctx := context.Background()
memoryStore := memory.New()
chartDescriptor, err := oras.PushBytes(ctx, memoryStore, ChartLayerMediaType, data)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -626,43 +672,57 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
return nil, err return nil, err
} }
configDescriptor, err := memoryStore.Add("", ConfigMediaType, configData) configDescriptor, err := oras.PushBytes(ctx, memoryStore, ConfigMediaType, configData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
descriptors := []ocispec.Descriptor{chartDescriptor} layers := []ocispec.Descriptor{chartDescriptor}
var provDescriptor ocispec.Descriptor var provDescriptor ocispec.Descriptor
if operation.provData != nil { if operation.provData != nil {
provDescriptor, err = memoryStore.Add("", ProvLayerMediaType, operation.provData) provDescriptor, err = oras.PushBytes(ctx, memoryStore, ProvLayerMediaType, operation.provData)
if err != nil { if err != nil {
return nil, err return nil, err
} }
descriptors = append(descriptors, provDescriptor) layers = append(layers, provDescriptor)
} }
// sort layers for determinism, similar to how ORAS v1 does it
sort.Slice(layers, func(i, j int) bool {
return layers[i].Digest < layers[j].Digest
})
ociAnnotations := generateOCIAnnotations(meta, operation.creationTime) ociAnnotations := generateOCIAnnotations(meta, operation.creationTime)
manifest := ocispec.Manifest{
Versioned: specs.Versioned{SchemaVersion: 2},
Config: configDescriptor,
Layers: layers,
Annotations: ociAnnotations,
}
manifestData, manifest, err := content.GenerateManifest(&configDescriptor, ociAnnotations, descriptors...) manifestData, err := json.Marshal(manifest)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if err := memoryStore.StoreManifest(parsedRef.String(), manifest, manifestData); err != nil { manifestDescriptor, err := oras.TagBytes(ctx, memoryStore, ocispec.MediaTypeImageManifest, manifestData, ref)
if err != nil {
return nil, err return nil, err
} }
remotesResolver, err := c.resolver(parsedRef.orasReference) repository, err := remote.NewRepository(parsedRef.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
registryStore := content.Registry{Resolver: remotesResolver} repository.PlainHTTP = c.plainHTTP
_, err = oras.Copy(ctx(c.out, c.debug), memoryStore, parsedRef.orasReference.String(), registryStore, "", repository.Client = c.authorizer
oras.WithNameValidation(nil))
manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions)
if err != nil { if err != nil {
return nil, err return nil, err
} }
chartSummary := &descriptorPushSummaryWithMeta{ chartSummary := &descriptorPushSummaryWithMeta{
Meta: meta, Meta: meta,
} }
@ -670,8 +730,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
chartSummary.Size = chartDescriptor.Size chartSummary.Size = chartDescriptor.Size
result := &PushResult{ result := &PushResult{
Manifest: &descriptorPushSummary{ Manifest: &descriptorPushSummary{
Digest: manifest.Digest.String(), Digest: manifestDescriptor.Digest.String(),
Size: manifest.Size, Size: manifestDescriptor.Size,
}, },
Config: &descriptorPushSummary{ Config: &descriptorPushSummary{
Digest: configDescriptor.Digest.String(), Digest: configDescriptor.Digest.String(),
@ -725,21 +785,17 @@ func (c *Client) Tags(ref string) ([]string, error) {
return nil, err return nil, err
} }
repository := registryremote.Repository{ ctx := context.Background()
Reference: parsedReference, repository, err := remote.NewRepository(parsedReference.String())
Client: c.registryAuthorizer,
PlainHTTP: c.plainHTTP,
}
var registryTags []string
registryTags, err = registry.Tags(ctx(c.out, c.debug), &repository)
if err != nil { if err != nil {
return nil, err return nil, err
} }
repository.PlainHTTP = c.plainHTTP
repository.Client = c.authorizer
var tagVersions []*semver.Version var tagVersions []*semver.Version
for _, tag := range registryTags { err = repository.Tags(ctx, "", func(tags []string) error {
for _, tag := range tags {
// Change underscore (_) back to plus (+) for Helm // Change underscore (_) back to plus (+) for Helm
// See https://github.com/helm/helm/issues/10166 // See https://github.com/helm/helm/issues/10166
tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+")) tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+"))
@ -748,6 +804,12 @@ func (c *Client) Tags(ref string) ([]string, error) {
} }
} }
return nil
})
if err != nil {
return nil, err
}
// Sort the collection // Sort the collection
sort.Sort(sort.Reverse(semver.Collection(tagVersions))) sort.Sort(sort.Reverse(semver.Collection(tagVersions)))
@ -762,30 +824,28 @@ func (c *Client) Tags(ref string) ([]string, error) {
} }
// Resolve a reference to a descriptor. // Resolve a reference to a descriptor.
func (c *Client) Resolve(ref string) (*ocispec.Descriptor, error) { func (c *Client) Resolve(ref string) (desc ocispec.Descriptor, err error) {
ctx := context.Background() remoteRepository, err := remote.NewRepository(ref)
parsedRef, err := newReference(ref)
if err != nil { if err != nil {
return nil, err return desc, err
}
if parsedRef.Registry == "" {
return nil, nil
} }
remoteRepository.PlainHTTP = c.plainHTTP
remotesResolver, err := c.resolver(parsedRef.orasReference) parsedReference, err := newReference(ref)
if err != nil { if err != nil {
return nil, err return desc, err
} }
_, desc, err := remotesResolver.Resolve(ctx, ref) ctx := context.Background()
return &desc, err parsedString := parsedReference.String()
return remoteRepository.Resolve(ctx, parsedString)
} }
// ValidateReference for path and version // ValidateReference for path and version
func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, error) { func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, error) {
var tag string var tag string
registryReference, err := newReference(u.Path) registryReference, err := newReference(u.Host + u.Path)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -800,19 +860,20 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e
} }
if registryReference.Digest != "" { if registryReference.Digest != "" {
if registryReference.Tag == "" { if version == "" {
// Install by digest only // Install by digest only
return u, nil return u, nil
} }
u.Path = fmt.Sprintf("%s@%s", registryReference.Repository, registryReference.Digest)
// Validate the tag if it was specified // Validate the tag if it was specified
path := registryReference.Registry + "/" + registryReference.Repository + ":" + registryReference.Tag path := registryReference.Registry + "/" + registryReference.Repository + ":" + version
desc, err := c.Resolve(path) desc, err := c.Resolve(path)
if err != nil { if err != nil {
// The resource does not have to be tagged when digest is specified // The resource does not have to be tagged when digest is specified
return u, nil return u, nil
} }
if desc != nil && desc.Digest.String() != registryReference.Digest { if desc.Digest.String() != registryReference.Digest {
return nil, errors.Errorf("chart reference digest mismatch: %s is not %s", desc.Digest.String(), registryReference.Digest) return nil, errors.Errorf("chart reference digest mismatch: %s is not %s", desc.Digest.String(), registryReference.Digest)
} }
return u, nil return u, nil
@ -842,7 +903,7 @@ func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, e
} }
} }
u.Path = fmt.Sprintf("%s/%s:%s", registryReference.Registry, registryReference.Repository, tag) u.Path = fmt.Sprintf("%s:%s", registryReference.Repository, tag)
return u, err return u, err
} }

@ -17,12 +17,13 @@ limitations under the License.
package registry package registry
import ( import (
"errors"
"fmt" "fmt"
"os" "os"
"testing" "testing"
"github.com/containerd/containerd/errdefs"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"oras.land/oras-go/v2/content"
) )
type HTTPRegistryClientTestSuite struct { type HTTPRegistryClientTestSuite struct {
@ -42,6 +43,18 @@ func (suite *HTTPRegistryClientTestSuite) TearDownSuite() {
os.RemoveAll(suite.WorkspaceDir) os.RemoveAll(suite.WorkspaceDir)
} }
func (suite *HTTPRegistryClientTestSuite) Test_0_Login() {
err := suite.RegistryClient.Login(suite.DockerRegistryHost,
LoginOptBasicAuth("badverybad", "ohsobad"),
LoginOptPlainText(true))
suite.NotNil(err, "error logging into registry with bad credentials")
err = suite.RegistryClient.Login(suite.DockerRegistryHost,
LoginOptBasicAuth(testUsername, testPassword),
LoginOptPlainText(true))
suite.Nil(err, "no error logging into registry with good credentials")
}
func (suite *HTTPRegistryClientTestSuite) Test_1_Push() { func (suite *HTTPRegistryClientTestSuite) Test_1_Push() {
testPush(&suite.TestSuite) testPush(&suite.TestSuite)
} }
@ -60,7 +73,7 @@ func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() {
// returns content that does not match the expected digest // returns content that does not match the expected digest
_, err := suite.RegistryClient.Pull(ref) _, err := suite.RegistryClient.Pull(ref)
suite.NotNil(err) suite.NotNil(err)
suite.True(errdefs.IsFailedPrecondition(err)) suite.True(errors.Is(err, content.ErrMismatchedDigest))
} }
func TestHTTPRegistryClientTestSuite(t *testing.T) { func TestHTTPRegistryClientTestSuite(t *testing.T) {

@ -66,7 +66,10 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_3_Tags() {
func (suite *InsecureTLSRegistryClientTestSuite) Test_4_Logout() { func (suite *InsecureTLSRegistryClientTestSuite) Test_4_Logout() {
err := suite.RegistryClient.Logout("this-host-aint-real:5000") err := suite.RegistryClient.Logout("this-host-aint-real:5000")
suite.NotNil(err, "error logging out of registry that has no entry") if err != nil {
// credential backend for mac generates an error
suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000")
}
err = suite.RegistryClient.Logout(suite.DockerRegistryHost) err = suite.RegistryClient.Logout(suite.DockerRegistryHost)
suite.Nil(err, "no error logging out of registry") suite.Nil(err, "no error logging out of registry")

@ -0,0 +1,33 @@
/*
Copyright The Helm Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package registry
import (
"testing"
"github.com/containerd/containerd/remotes"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestNewClientResolverNotSupported(t *testing.T) {
var r remotes.Resolver
client, err := NewClient(ClientOptResolver(r))
require.Equal(t, err, errDeprecatedRemote)
assert.Nil(t, client)
}

@ -66,7 +66,10 @@ func (suite *TLSRegistryClientTestSuite) Test_3_Tags() {
func (suite *TLSRegistryClientTestSuite) Test_4_Logout() { func (suite *TLSRegistryClientTestSuite) Test_4_Logout() {
err := suite.RegistryClient.Logout("this-host-aint-real:5000") err := suite.RegistryClient.Logout("this-host-aint-real:5000")
suite.NotNil(err, "error logging out of registry that has no entry") if err != nil {
// credential backend for mac generates an error
suite.NotNil(err, "failed to delete the credential for this-host-aint-real:5000")
}
err = suite.RegistryClient.Logout(suite.DockerRegistryHost) err = suite.RegistryClient.Logout(suite.DockerRegistryHost)
suite.Nil(err, "no error logging out of registry") suite.Nil(err, "no error logging out of registry")

@ -19,11 +19,11 @@ package registry
import ( import (
"strings" "strings"
orasregistry "oras.land/oras-go/pkg/registry" "oras.land/oras-go/v2/registry"
) )
type reference struct { type reference struct {
orasReference orasregistry.Reference orasReference registry.Reference
Registry string Registry string
Repository string Repository string
Tag string Tag string
@ -60,7 +60,7 @@ func newReference(raw string) (result reference, err error) {
} }
} }
result.orasReference, err = orasregistry.ParseReference(raw) result.orasReference, err = registry.ParseReference(raw)
if err != nil { if err != nil {
return result, err return result, err
} }

@ -18,24 +18,20 @@ package registry // import "helm.sh/helm/v4/pkg/registry"
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"strings" "strings"
"time" "time"
"helm.sh/helm/v4/internal/tlsutil"
"helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chart/loader"
helmtime "helm.sh/helm/v4/pkg/time" helmtime "helm.sh/helm/v4/pkg/time"
"github.com/Masterminds/semver/v3" "github.com/Masterminds/semver/v3"
ocispec "github.com/opencontainers/image-spec/specs-go/v1" ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus"
orascontext "oras.land/oras-go/pkg/context"
"helm.sh/helm/v4/internal/tlsutil"
"helm.sh/helm/v4/pkg/chart"
"helm.sh/helm/v4/pkg/chart/loader"
) )
var immutableOciAnnotations = []string{ var immutableOciAnnotations = []string{
@ -43,7 +39,7 @@ var immutableOciAnnotations = []string{
ocispec.AnnotationTitle, ocispec.AnnotationTitle,
} }
// IsOCI determines whether or not a URL is to be treated as an OCI URL // IsOCI determines whether a URL is to be treated as an OCI URL
func IsOCI(url string) bool { func IsOCI(url string) bool {
return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme)) return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme))
} }
@ -103,17 +99,6 @@ func extractChartMeta(chartData []byte) (*chart.Metadata, error) {
return ch.Metadata, nil return ch.Metadata, nil
} }
// ctx retrieves a fresh context.
// disable verbose logging coming from ORAS (unless debug is enabled)
func ctx(out io.Writer, debug bool) context.Context {
if !debug {
return orascontext.Background()
}
ctx := orascontext.WithLoggerFromWriter(context.Background(), out)
orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel)
return ctx
}
// NewRegistryClientWithTLS is a helper function to create a new registry client with TLS enabled. // NewRegistryClientWithTLS is a helper function to create a new registry client with TLS enabled.
func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSverify bool, registryConfig string, debug bool) (*Client, error) { func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSverify bool, registryConfig string, debug bool) (*Client, error) {
tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify) tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify)

@ -88,7 +88,6 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
ClientOptEnableCache(true), ClientOptEnableCache(true),
ClientOptWriter(suite.Out), ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile), ClientOptCredentialsFile(credentialsFile),
ClientOptResolver(nil),
ClientOptBasicAuth(testUsername, testPassword), ClientOptBasicAuth(testUsername, testPassword),
} }
@ -141,15 +140,12 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.HTTP.DrainTimeout = time.Duration(10) * time.Second
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
// Basic auth is not possible if we are serving HTTP.
if tlsEnabled {
config.Auth = configuration.Auth{ config.Auth = configuration.Auth{
"htpasswd": configuration.Parameters{ "htpasswd": configuration.Parameters{
"realm": "localhost", "realm": "localhost",
"path": htpasswdPath, "path": htpasswdPath,
}, },
} }
}
// config tls // config tls
if tlsEnabled { if tlsEnabled {
@ -277,7 +273,7 @@ func testPush(suite *TestSuite) {
result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptCreationTime(testingChartCreationTime)) result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptCreationTime(testingChartCreationTime))
suite.Nil(err, "no error pushing good ref with prov") suite.Nil(err, "no error pushing good ref with prov")
_, err = suite.RegistryClient.Pull(ref) _, err = suite.RegistryClient.Pull(ref, PullOptWithProv(true))
suite.Nil(err, "no error pulling a simple chart") suite.Nil(err, "no error pulling a simple chart")
// Validate the output // Validate the output

@ -163,9 +163,10 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) {
err = registryClient.Login( err = registryClient.Login(
srv.RegistryURL, srv.RegistryURL,
ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword), ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword),
ociRegistry.LoginOptInsecure(false)) ociRegistry.LoginOptInsecure(true),
ociRegistry.LoginOptPlainText(true))
if err != nil { if err != nil {
t.Fatalf("error logging into registry with good credentials") t.Fatalf("error logging into registry with good credentials: %v", err)
} }
ref := fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL) ref := fmt.Sprintf("%s/u/ocitestuser/oci-dependent-chart:0.1.0", srv.RegistryURL)

Loading…
Cancel
Save