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())
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)
// 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)
}
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)
// 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 {
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("repositories.yaml"),
dir(),

@ -67,7 +67,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
}
_, 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 {
t.Logf("Output: %s", out)
@ -110,7 +110,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
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 {
t.Logf("Output: %s", out)
t.Fatal(err)
@ -131,7 +131,7 @@ func TestDependencyUpdateCmd(t *testing.T) {
if err := chartutil.SaveDir(c, dir()); err != nil {
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("repositories.yaml"),
dir(),
@ -169,7 +169,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
}
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 {
t.Logf("Output: %s", output)
t.Fatal(err)
@ -178,7 +178,7 @@ func TestDependencyUpdateCmd_DoNotDeleteOldChartsOnError(t *testing.T) {
// Chart repo is down
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 {
t.Logf("Output: %s", output)
t.Fatal("Expected error, got nil")

@ -203,7 +203,7 @@ func TestPullCmd(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
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,
outdir,
filepath.Join(outdir, "repositories.yaml"),

@ -43,6 +43,7 @@ type registryLoginOptions struct {
keyFile string
caFile string
insecure bool
plainHTTP bool
}
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.WithKeyFile(o.keyFile),
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.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.BoolVar(&o.plainHTTP, "plain-http", false, "use insecure HTTP connections for the chart upload")
return cmd
}

@ -29,7 +29,6 @@ require (
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.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/pflag v1.0.6
github.com/stretchr/testify v1.10.0
@ -45,7 +44,7 @@ require (
k8s.io/client-go v0.32.1
k8s.io/klog/v2 v2.130.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
)
@ -68,11 +67,7 @@ require (
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/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/go-connections v0.5.0 // indirect
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect
github.com/docker/go-metrics v0.0.1 // 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/mitchellh/go-wordwrap v1.0.1 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // 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/russross/blackfriday/v2 v2.1.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/x448/float16 v0.8.4 // 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/vcs v1.13.3 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE=
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/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
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/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/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/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/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
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/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk=
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/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/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
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/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/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
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.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
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.3.1/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/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
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/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/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
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.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
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/go.mod h1:ppciCHRLsyCio54qbzQv0E4Jyth/fLWDTJYfvWpcSVk=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
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/go.mod h1:/Yi/BqkuueW1BgpoePYBRdDYfjPF5sgTr5+YqDZra5k=
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/utils v0.0.0-20241104100929-3ea5e8cea738 h1:M3sRQVHv7vB20Xc2ybTt7ODCeFj6JSWYFzOFnYeS6Ro=
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 v1.2.6/go.mod h1:OVPc1PegSEe/K8YiLfosrlqlqTN9PUyFvOw5Y9gwrT8=
oras.land/oras-go/v2 v2.5.0 h1:o8Me9kLY74Vp5uw07QXPiitjsw7qNXi8Twd+19Zf02c=
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/go.mod h1:18nIHnGi6636UCz6m8i4DhaJ65T6EruyzmoQqI2BVDo=
sigs.k8s.io/kustomize/api v0.18.0 h1:hTzp67k+3NEVInwz5BHyzc9rGxIauoXferXyjv5lWPo=

@ -24,11 +24,12 @@ import (
// RegistryLogin performs a registry login operation.
type RegistryLogin struct {
cfg *Configuration
certFile string
keyFile string
caFile string
insecure bool
cfg *Configuration
certFile string
keyFile string
caFile string
insecure bool
plainHTTP bool
}
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 {
return func(r *RegistryLogin) error {
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.
func NewRegistryLogin(cfg *Configuration) *RegistryLogin {
return &RegistryLogin{
@ -84,5 +93,7 @@ func (a *RegistryLogin) Run(_ io.Writer, hostname string, username string, passw
hostname,
registry.LoginOptBasicAuth(username, password),
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: "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 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},
}

@ -18,26 +18,31 @@ package registry // import "helm.sh/helm/v4/pkg/registry"
import (
"context"
"encoding/base64"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"sort"
"strings"
"sync"
"github.com/Masterminds/semver/v3"
"github.com/containerd/containerd/remotes"
"github.com/opencontainers/image-spec/specs-go"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"oras.land/oras-go/pkg/auth"
dockerauth "oras.land/oras-go/pkg/auth/docker"
"oras.land/oras-go/pkg/content"
"oras.land/oras-go/pkg/oras"
"oras.land/oras-go/pkg/registry"
registryremote "oras.land/oras-go/pkg/registry/remote"
registryauth "oras.land/oras-go/pkg/registry/remote/auth"
"oras.land/oras-go/v2"
"oras.land/oras-go/v2/content"
"oras.land/oras-go/v2/content/memory"
"oras.land/oras-go/v2/registry"
"oras.land/oras-go/v2/registry/remote"
"oras.land/oras-go/v2/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/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
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 (
// RemoteClient shadows the ORAS remote.Client interface
// (hiding the ORAS type from Helm client visibility)
@ -68,11 +75,12 @@ type (
username string
password string
out io.Writer
authorizer auth.Client
authorizer *auth.Client
registryAuthorizer RemoteClient
resolver func(ref registry.Reference) (remotes.Resolver, error)
credentialsStore credentials.Store
httpClient *http.Client
plainHTTP bool
err error // pass any errors from the ClientOption functions
}
// 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 {
option(client)
if client.err != nil {
return nil, client.err
}
}
if client.credentialsFile == "" {
client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename)
}
if client.authorizer == nil {
authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile)
if err != nil {
return nil, err
}
client.authorizer = authClient
}
resolverFn := client.resolver // copy for avoiding recursive call
client.resolver = func(ref registry.Reference) (remotes.Resolver, error) {
if resolverFn != nil {
// validate if the resolverFn returns a valid resolver
if resolver, err := resolverFn(ref); resolver != nil && err == nil {
return resolver, nil
}
}
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 client.httpClient == nil {
type cloner[T any] interface {
Clone() T
}
// if username and password are set, use them for authentication
// by adding the basic auth Authorization header to the resolver
if client.username != "" && client.password != "" {
concat := client.username + ":" + client.password
encodedAuth := base64.StdEncoding.EncodeToString([]byte(concat))
opts = append(opts, auth.WithResolverHeaders(
http.Header{
"Authorization": []string{"Basic " + encodedAuth},
},
))
// try to copy (clone) the http.DefaultTransport so any mutations we
// perform on it (e.g. TLS config) are not reflected globally
// follow https://github.com/golang/go/issues/39299 for a more elegant
// solution in the future
transport := http.DefaultTransport
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()
}
resolver, err := client.authorizer.ResolverWithOpts(opts...)
if err != nil {
return nil, err
client.httpClient = &http.Client{
Transport: retry.NewTransport(transport),
}
return resolver, nil
}
// allocate a cache if option is set
var cache registryauth.Cache
if client.enableCache {
cache = registryauth.DefaultCache
storeOptions := credentials.StoreOptions{
AllowPlaintextPut: true,
DetectDefaultNativeStore: true,
}
store, err := credentials.NewStore(client.credentialsFile, storeOptions)
if err != nil {
return nil, err
}
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)
}
if client.registryAuthorizer == nil {
client.registryAuthorizer = &registryauth.Client{
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
}
dockerClient, ok := client.authorizer.(*dockerauth.Client)
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 username == "" && password != "" {
return registryauth.Credential{
RefreshToken: password,
}, nil
}
if client.authorizer == nil {
authorizer := auth.Client{
Client: client.httpClient,
}
authorizer.SetUserAgent(version.GetUserAgent())
return registryauth.Credential{
Username: username,
Password: password,
}, nil
authorizer.Credential = credentials.Credential(client.credentialsStore)
},
if client.enableCache {
authorizer.Cache = auth.NewCache()
}
client.authorizer = &authorizer
}
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.
func ClientOptAuthorizer(authorizer auth.Client) ClientOption {
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(resolver remotes.Resolver) ClientOption {
return func(client *Client) {
client.resolver = func(_ registry.Reference) (remotes.Resolver, error) {
return resolver, nil
}
func ClientOptResolver(_ remotes.Resolver) ClientOption {
return func(c *Client) {
c.err = errDeprecatedRemote
}
}
@ -268,60 +242,128 @@ type (
LoginOption func(*loginOperation)
loginOperation struct {
username string
password string
insecure bool
certFile string
keyFile string
caFile string
host string
client *Client
}
)
// Login logs into a registry
func (c *Client) Login(host string, options ...LoginOption) error {
operation := &loginOperation{}
for _, option := range options {
option(operation)
option(&loginOperation{host, c})
}
reg, err := remote.NewRegistry(host)
if err != nil {
return err
}
authorizerLoginOpts := []auth.LoginOption{
auth.WithLoginContext(ctx(c.out, c.debug)),
auth.WithLoginHostname(host),
auth.WithLoginUsername(operation.username),
auth.WithLoginSecret(operation.password),
auth.WithLoginUserAgent(version.GetUserAgent()),
auth.WithLoginTLS(operation.certFile, operation.keyFile, operation.caFile),
reg.PlainHTTP = c.plainHTTP
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
}
fmt.Fprintln(c.out, "Login Succeeded")
return nil
}
// LoginOptBasicAuth returns a function that sets the username/password settings on login
func LoginOptBasicAuth(username string, password string) LoginOption {
return func(operation *loginOperation) {
operation.username = username
operation.password = password
return func(o *loginOperation) {
o.client.username = username
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
func LoginOptInsecure(insecure bool) LoginOption {
return func(operation *loginOperation) {
operation.insecure = insecure
return func(o *loginOperation) {
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.
func LoginOptTLSClientConfig(certFile, keyFile, caFile string) LoginOption {
return func(operation *loginOperation) {
operation.certFile = certFile
operation.keyFile = keyFile
operation.caFile = caFile
return func(o *loginOperation) {
if (certFile == "" || keyFile == "") && caFile == "" {
return
}
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 {
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
}
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(
"must specify at least one layer to pull (chart/prov)")
}
memoryStore := content.NewMemory()
memoryStore := memory.New()
allowedMediaTypes := []string{
ocispec.MediaTypeImageManifest,
ConfigMediaType,
}
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
remotesResolver, err := c.resolver(parsedRef.orasReference)
repository, err := remote.NewRepository(parsedRef.String())
if err != nil {
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, "",
oras.WithPullEmptyNameAllowed(),
oras.WithAllowedMediaTypes(allowedMediaTypes),
oras.WithLayerDescriptors(func(l []ocispec.Descriptor) {
layers = l
}))
ctx := context.Background()
sort.Strings(allowedMediaTypes)
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 {
return nil, err
}
@ -480,54 +540,37 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
Prov: &DescriptorPullSummary{},
Ref: parsedRef.String(),
}
var getManifestErr error
if _, manifestData, ok := memoryStore.Get(manifest); !ok {
getManifestErr = errors.Errorf("Unable to retrieve blob with digest %s", manifest.Digest)
} else {
result.Manifest.Data = manifestData
}
if getManifestErr != nil {
return nil, getManifestErr
result.Manifest.Data, err = content.FetchAll(ctx, memoryStore, manifest)
if err != nil {
return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", manifest.Digest, err)
}
var getConfigDescriptorErr error
if _, configData, ok := memoryStore.Get(*configDescriptor); !ok {
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
}
result.Chart.Meta = meta
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)
}
if getConfigDescriptorErr != nil {
return nil, getConfigDescriptorErr
if err := json.Unmarshal(result.Config.Data, &result.Chart.Meta); err != nil {
return nil, err
}
if operation.withChart {
var getChartDescriptorErr error
if _, chartData, ok := memoryStore.Get(*chartDescriptor); !ok {
getChartDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", chartDescriptor.Digest)
} else {
result.Chart.Data = chartData
result.Chart.Digest = chartDescriptor.Digest.String()
result.Chart.Size = chartDescriptor.Size
}
if getChartDescriptorErr != nil {
return nil, getChartDescriptorErr
result.Chart.Data, err = content.FetchAll(ctx, memoryStore, *chartDescriptor)
if err != nil {
return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", chartDescriptor.Digest, err)
}
result.Chart.Digest = chartDescriptor.Digest.String()
result.Chart.Size = chartDescriptor.Size
}
if operation.withProv && !provMissing {
var getProvDescriptorErr error
if _, provData, ok := memoryStore.Get(*provDescriptor); !ok {
getProvDescriptorErr = errors.Errorf("Unable to retrieve blob with digest %s", provDescriptor.Digest)
} else {
result.Prov.Data = provData
result.Prov.Digest = provDescriptor.Digest.String()
result.Prov.Size = provDescriptor.Size
}
if getProvDescriptorErr != nil {
return nil, getProvDescriptorErr
result.Prov.Data, err = content.FetchAll(ctx, memoryStore, *provDescriptor)
if err != nil {
return nil, fmt.Errorf("unable to retrieve blob with digest %s: %w", provDescriptor.Digest, err)
}
result.Prov.Digest = provDescriptor.Digest.String()
result.Prov.Size = provDescriptor.Size
}
fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref)
@ -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")
}
}
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 {
return nil, err
}
@ -626,43 +672,57 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
return nil, err
}
configDescriptor, err := memoryStore.Add("", ConfigMediaType, configData)
configDescriptor, err := oras.PushBytes(ctx, memoryStore, ConfigMediaType, configData)
if err != nil {
return nil, err
}
descriptors := []ocispec.Descriptor{chartDescriptor}
layers := []ocispec.Descriptor{chartDescriptor}
var provDescriptor ocispec.Descriptor
if operation.provData != nil {
provDescriptor, err = memoryStore.Add("", ProvLayerMediaType, operation.provData)
provDescriptor, err = oras.PushBytes(ctx, memoryStore, ProvLayerMediaType, operation.provData)
if err != nil {
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)
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 {
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
}
remotesResolver, err := c.resolver(parsedRef.orasReference)
repository, err := remote.NewRepository(parsedRef.String())
if err != nil {
return nil, err
}
registryStore := content.Registry{Resolver: remotesResolver}
_, err = oras.Copy(ctx(c.out, c.debug), memoryStore, parsedRef.orasReference.String(), registryStore, "",
oras.WithNameValidation(nil))
repository.PlainHTTP = c.plainHTTP
repository.Client = c.authorizer
manifestDescriptor, err = oras.ExtendedCopy(ctx, memoryStore, parsedRef.String(), repository, parsedRef.String(), oras.DefaultExtendedCopyOptions)
if err != nil {
return nil, err
}
chartSummary := &descriptorPushSummaryWithMeta{
Meta: meta,
}
@ -670,8 +730,8 @@ func (c *Client) Push(data []byte, ref string, options ...PushOption) (*PushResu
chartSummary.Size = chartDescriptor.Size
result := &PushResult{
Manifest: &descriptorPushSummary{
Digest: manifest.Digest.String(),
Size: manifest.Size,
Digest: manifestDescriptor.Digest.String(),
Size: manifestDescriptor.Size,
},
Config: &descriptorPushSummary{
Digest: configDescriptor.Digest.String(),
@ -725,27 +785,29 @@ func (c *Client) Tags(ref string) ([]string, error) {
return nil, err
}
repository := registryremote.Repository{
Reference: parsedReference,
Client: c.registryAuthorizer,
PlainHTTP: c.plainHTTP,
}
var registryTags []string
registryTags, err = registry.Tags(ctx(c.out, c.debug), &repository)
ctx := context.Background()
repository, err := remote.NewRepository(parsedReference.String())
if err != nil {
return nil, err
}
repository.PlainHTTP = c.plainHTTP
repository.Client = c.authorizer
var tagVersions []*semver.Version
for _, tag := range registryTags {
// Change underscore (_) back to plus (+) for Helm
// See https://github.com/helm/helm/issues/10166
tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+"))
if err == nil {
tagVersions = append(tagVersions, tagVersion)
err = repository.Tags(ctx, "", func(tags []string) error {
for _, tag := range tags {
// Change underscore (_) back to plus (+) for Helm
// See https://github.com/helm/helm/issues/10166
tagVersion, err := semver.StrictNewVersion(strings.ReplaceAll(tag, "_", "+"))
if err == nil {
tagVersions = append(tagVersions, tagVersion)
}
}
return nil
})
if err != nil {
return nil, err
}
// Sort the collection
@ -762,30 +824,28 @@ func (c *Client) Tags(ref string) ([]string, error) {
}
// Resolve a reference to a descriptor.
func (c *Client) Resolve(ref string) (*ocispec.Descriptor, error) {
ctx := context.Background()
parsedRef, err := newReference(ref)
func (c *Client) Resolve(ref string) (desc ocispec.Descriptor, err error) {
remoteRepository, err := remote.NewRepository(ref)
if err != nil {
return nil, err
}
if parsedRef.Registry == "" {
return nil, nil
return desc, err
}
remoteRepository.PlainHTTP = c.plainHTTP
remotesResolver, err := c.resolver(parsedRef.orasReference)
parsedReference, err := newReference(ref)
if err != nil {
return nil, err
return desc, err
}
_, desc, err := remotesResolver.Resolve(ctx, ref)
return &desc, err
ctx := context.Background()
parsedString := parsedReference.String()
return remoteRepository.Resolve(ctx, parsedString)
}
// ValidateReference for path and version
func (c *Client) ValidateReference(ref, version string, u *url.URL) (*url.URL, error) {
var tag string
registryReference, err := newReference(u.Path)
registryReference, err := newReference(u.Host + u.Path)
if err != nil {
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.Tag == "" {
if version == "" {
// Install by digest only
return u, nil
}
u.Path = fmt.Sprintf("%s@%s", registryReference.Repository, registryReference.Digest)
// 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)
if err != nil {
// The resource does not have to be tagged when digest is specified
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 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
}

@ -17,12 +17,13 @@ limitations under the License.
package registry
import (
"errors"
"fmt"
"os"
"testing"
"github.com/containerd/containerd/errdefs"
"github.com/stretchr/testify/suite"
"oras.land/oras-go/v2/content"
)
type HTTPRegistryClientTestSuite struct {
@ -42,6 +43,18 @@ func (suite *HTTPRegistryClientTestSuite) TearDownSuite() {
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() {
testPush(&suite.TestSuite)
}
@ -60,7 +73,7 @@ func (suite *HTTPRegistryClientTestSuite) Test_4_ManInTheMiddle() {
// returns content that does not match the expected digest
_, err := suite.RegistryClient.Pull(ref)
suite.NotNil(err)
suite.True(errdefs.IsFailedPrecondition(err))
suite.True(errors.Is(err, content.ErrMismatchedDigest))
}
func TestHTTPRegistryClientTestSuite(t *testing.T) {

@ -66,7 +66,10 @@ func (suite *InsecureTLSRegistryClientTestSuite) Test_3_Tags() {
func (suite *InsecureTLSRegistryClientTestSuite) Test_4_Logout() {
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)
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() {
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)
suite.Nil(err, "no error logging out of registry")

@ -19,11 +19,11 @@ package registry
import (
"strings"
orasregistry "oras.land/oras-go/pkg/registry"
"oras.land/oras-go/v2/registry"
)
type reference struct {
orasReference orasregistry.Reference
orasReference registry.Reference
Registry string
Repository 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 {
return result, err
}

@ -18,24 +18,20 @@ package registry // import "helm.sh/helm/v4/pkg/registry"
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
"strings"
"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"
"github.com/Masterminds/semver/v3"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"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{
@ -43,7 +39,7 @@ var immutableOciAnnotations = []string{
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 {
return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme))
}
@ -103,17 +99,6 @@ func extractChartMeta(chartData []byte) (*chart.Metadata, error) {
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.
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)

@ -88,7 +88,6 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
ClientOptEnableCache(true),
ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile),
ClientOptResolver(nil),
ClientOptBasicAuth(testUsername, testPassword),
}
@ -141,14 +140,11 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
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{
"htpasswd": configuration.Parameters{
"realm": "localhost",
"path": htpasswdPath,
},
}
config.Auth = configuration.Auth{
"htpasswd": configuration.Parameters{
"realm": "localhost",
"path": htpasswdPath,
},
}
// config tls
@ -277,7 +273,7 @@ func testPush(suite *TestSuite) {
result, err := suite.RegistryClient.Push(chartData, ref, PushOptProvData(provData), PushOptCreationTime(testingChartCreationTime))
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")
// Validate the output

@ -163,9 +163,10 @@ func (srv *OCIServer) Run(t *testing.T, opts ...OCIServerOpt) {
err = registryClient.Login(
srv.RegistryURL,
ociRegistry.LoginOptBasicAuth(srv.TestUsername, srv.TestPassword),
ociRegistry.LoginOptInsecure(false))
ociRegistry.LoginOptInsecure(true),
ociRegistry.LoginOptPlainText(true))
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)

Loading…
Cancel
Save