chore(oci): migrate to ORAS Golang library v2

Updates from `oras.land/oras-go` to `oras.land/oras-go/v2`. The main
user facing change is that the `--plain-http` parameter is now required
even when accessing OCI registries running on `localhost`.

I'm new to this codebase and some tests needed to be adjusted to match
the new dependency, please look over the test changes to see if there
are any issues with those.

Fixes #11821

Signed-off-by: Zoran Regvart <zoran@regvart.com>
pull/12310/head
Zoran Regvart 11 months ago
parent 21568dee29
commit 156e562f38
No known key found for this signature in database
GPG Key ID: 6D9197EFE5AB7D75

@ -65,10 +65,17 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
}
registryClient, err := newDefaultRegistryClient(client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
man.RegistryClient = registryClient
if client.Verify {
man.Verify = downloader.VerifyIfPossible
}
err := man.Build()
err = man.Build()
if e, ok := err.(downloader.ErrRepoNotFound); ok {
return fmt.Errorf("%s. Please add the missing repos via 'helm repo add'", e.Error())
}
@ -80,6 +87,7 @@ func newDependencyBuildCmd(cfg *action.Configuration, out io.Writer) *cobra.Comm
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download")
return cmd
}

@ -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(),

@ -16,6 +16,7 @@ limitations under the License.
package main
import (
"fmt"
"io"
"path/filepath"
@ -68,6 +69,13 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com
RepositoryCache: settings.RepositoryCache,
Debug: settings.Debug,
}
registryClient, err := newDefaultRegistryClient(client.PlainHTTP)
if err != nil {
return fmt.Errorf("missing registry client: %w", err)
}
man.RegistryClient = registryClient
if client.Verify {
man.Verify = downloader.VerifyAlways
}
@ -79,6 +87,7 @@ func newDependencyUpdateCmd(cfg *action.Configuration, out io.Writer) *cobra.Com
f.BoolVar(&client.Verify, "verify", false, "verify the packages against signatures")
f.StringVar(&client.Keyring, "keyring", defaultKeyring(), "keyring containing public keys")
f.BoolVar(&client.SkipRefresh, "skip-refresh", false, "do not refresh the local repository cache")
f.BoolVar(&client.PlainHTTP, "plain-http", false, "use insecure HTTP connections for the chart download")
return cmd
}

@ -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")

@ -198,7 +198,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
}

@ -11,7 +11,6 @@ require (
github.com/Masterminds/squirrel v1.5.4
github.com/Masterminds/vcs v1.13.3
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
github.com/containerd/containerd v1.7.23
github.com/cyphar/filepath-securejoin v0.3.4
github.com/distribution/distribution/v3 v3.0.0-rc.1
github.com/evanphx/json-patch v5.9.0+incompatible
@ -29,7 +28,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.0
github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.8.1
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
@ -45,7 +43,7 @@ require (
k8s.io/client-go v0.31.2
k8s.io/klog/v2 v2.130.1
k8s.io/kubectl v0.31.2
oras.land/oras-go v1.2.5
oras.land/oras-go/v2 v2.5.0
sigs.k8s.io/yaml v1.4.0
)
@ -60,19 +58,12 @@ require (
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/containerd/errdefs v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/containerd/platforms v0.2.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // 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/distribution/reference v0.6.0 // indirect
github.com/docker/cli v25.0.1+incompatible // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect
github.com/docker/docker v25.0.6+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
@ -119,7 +110,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.4.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
@ -138,6 +128,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

@ -24,10 +24,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=
@ -59,18 +55,6 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
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.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ=
github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw=
github.com/containerd/continuity v0.4.2 h1:v3y/4Yz5jwnvqPKJJ+7Wf93fyWoCB3F5EclWG023MDM=
github.com/containerd/continuity v0.4.2/go.mod h1:F6PTNCKepoxEaXLQp3wDAjygEnImnZ/7o4JzpodfroQ=
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=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
@ -90,22 +74,12 @@ github.com/distribution/distribution/v3 v3.0.0-rc.1 h1:6M4ewmPBUhF7wtQ8URLOQ1W/P
github.com/distribution/distribution/v3 v3.0.0-rc.1/go.mod h1:tFjaPDeHCrLg28e4feBIy27cP+qmrc/mvkl6MFIfVi4=
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 v25.0.1+incompatible h1:mFpqnrS6Hsm3v1k7Wa/BO23oz0k121MTbTO1lpcGSkU=
github.com/docker/cli v25.0.1+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 v25.0.6+incompatible h1:5cPwbwriIcsua2REJe8HqQV+6WlWc1byg2QSXzBxBGg=
github.com/docker/docker v25.0.6+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/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@ -157,8 +131,6 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
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/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
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=
@ -278,14 +250,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.4.0 h1:Vy79D6mHeJJjiPdFEL2yku1kl0chZpJfZcPpb16BRl8=
github.com/moby/spdystream v0.4.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.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -395,8 +361,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.54.0 h1:WWL67oxtknNVMb70lJXxXruf8UyK/a9hmIE1XO3Uedg=
go.opentelemetry.io/contrib/bridges/prometheus v0.54.0/go.mod h1:LqNcnXmyULp8ertk4hUTVtSUvKXj4h1Mx7gUCSSr/q0=
go.opentelemetry.io/contrib/exporters/autoexport v0.54.0 h1:dTmcmVm4J54IRPGm5oVjLci1uYat4UDea84E2tyBaAk=
@ -602,8 +566,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=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
k8s.io/api v0.31.2 h1:3wLBbL5Uom/8Zy98GRPXpJ254nEFpl+hwndmk9RwmL0=
@ -628,8 +590,8 @@ k8s.io/kubectl v0.31.2 h1:gTxbvRkMBwvTSAlobiTVqsH6S8Aa1aGyBcu5xYLsn8M=
k8s.io/kubectl v0.31.2/go.mod h1:EyASYVU6PY+032RrTh5ahtSOMgoDRIux9V1JLKtG5xM=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
oras.land/oras-go v1.2.5 h1:XpYuAwAb0DfQsunIyMfeET92emK8km3W4yEzZvUbsTo=
oras.land/oras-go v1.2.5/go.mod h1:PuAwRShRZCsZb7g8Ar3jKKQR/2A/qN+pkYxIOd/FAoo=
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-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd/go.mod h1:B8JuhiUyNFVKdsE8h686QcCxMaH6HrOAZj4vswFpcB0=
sigs.k8s.io/kustomize/api v0.17.2 h1:E7/Fjk7V5fboiuijoZHgs4aHuexi5Y2loXlVOAVAG5g=

@ -38,6 +38,7 @@ type Dependency struct {
Keyring string
SkipRefresh bool
ColumnWidth uint
PlainHTTP bool
}
// NewDependency creates a new Dependency object with the given configuration.

@ -29,6 +29,7 @@ type RegistryLogin struct {
keyFile string
caFile string
insecure bool
plainHTTP bool
}
type RegistryLoginOpt func(*RegistryLogin) error
@ -65,6 +66,13 @@ func WithCAFile(caFile string) RegistryLoginOpt {
}
}
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 +92,6 @@ 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))
}

@ -18,24 +18,29 @@ package registry // import "helm.sh/helm/v3/pkg/registry"
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
"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/v3/internal/version"
"helm.sh/helm/v3/pkg/chart"
@ -57,9 +62,8 @@ type (
// path to repository config file e.g. ~/.docker/config.json
credentialsFile string
out io.Writer
authorizer auth.Client
registryAuthorizer *registryauth.Client
resolver func(ref registry.Reference) (remotes.Resolver, error)
authorizer *auth.Client
credentialsStore credentials.Store
httpClient *http.Client
plainHTTP bool
}
@ -80,77 +84,62 @@ func NewClient(options ...ClientOption) (*Client, error) {
if client.credentialsFile == "" {
client.credentialsFile = helmpath.ConfigPath(CredentialsFileBasename)
}
if client.authorizer == nil {
authClient, err := dockerauth.NewClientWithDockerFallback(client.credentialsFile)
if err != nil {
return nil, err
if client.httpClient == nil {
type cloner[T any] interface {
Clone() T
}
client.authorizer = authClient
// 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()
}
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
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.authorizer == nil {
authorizer := auth.Client{
Client: client.httpClient,
}
if client.plainHTTP {
opts = append(opts, auth.WithResolverPlainHTTP())
authorizer.SetUserAgent(version.GetUserAgent())
storeOptions := credentials.StoreOptions{
AllowPlaintextPut: true,
DetectDefaultNativeStore: true,
}
resolver, err := client.authorizer.ResolverWithOpts(opts...)
store, err := credentials.NewStore(client.credentialsFile, storeOptions)
if err != nil {
return nil, err
}
return resolver, nil
}
// allocate a cache if option is set
var cache registryauth.Cache
if client.enableCache {
cache = registryauth.DefaultCache
}
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) {
dockerClient, ok := client.authorizer.(*dockerauth.Client)
if !ok {
return registryauth.EmptyCredential, errors.New("unable to obtain docker client")
}
username, password, err := dockerClient.Credential(reg)
dockerStore, err := credentials.NewStoreFromDocker(storeOptions)
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
// 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)
}
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
}
@ -195,74 +184,134 @@ 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
}
}
}
type (
// LoginOption allows specifying various settings on login
LoginOption func(*loginOperation)
loginOperation struct {
username string
password string
insecure bool
certFile string
keyFile string
caFile string
}
LoginOption func(host string, client *Client) error
)
// Login logs into a registry
func (c *Client) Login(host string, options ...LoginOption) error {
operation := &loginOperation{}
for _, option := range options {
option(operation)
if err := option(host, c); err != nil {
return fmt.Errorf("configuring login option: %w", err)
}
}
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(host string, client *Client) error {
client.authorizer.Credential = auth.StaticCredential(host, auth.Credential{Username: username, Password: password})
return nil
}
}
// LoginOptBasicAuth returns a function that allows plaintext (HTTP) login
func LoginOptPlainText(isPlainText bool) LoginOption {
return func(host string, client *Client) error {
client.plainHTTP = isPlainText
return nil
}
}
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(_ string, client *Client) error {
tlsConfig, err := ensureTLSConfig(client.authorizer)
if err != nil {
return err
}
tlsConfig.InsecureSkipVerify = insecure
return nil
}
}
// 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(_ string, client *Client) error {
if (certFile == "" || keyFile == "") && caFile == "" {
return nil
}
tlsConfig, err := ensureTLSConfig(client.authorizer)
if err != nil {
return err
}
if certFile != "" && keyFile != "" {
authCert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
return err
}
tlsConfig.Certificates = []tls.Certificate{authCert}
}
if caFile != "" {
certPool := x509.NewCertPool()
ca, err := os.ReadFile(caFile)
if err != nil {
return err
}
if !certPool.AppendCertsFromPEM(ca) {
return fmt.Errorf("unable to parse CA file: %q", caFile)
}
tlsConfig.RootCAs = certPool
}
return nil
}
}
@ -279,7 +328,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)
@ -334,8 +384,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
@ -351,18 +402,35 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
}
var descriptors, layers []ocispec.Descriptor
remotesResolver, err := c.resolver(parsedRef)
descriptorsMutex := sync.Mutex{}
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
ctx := context.Background()
sort.Strings(allowedMediaTypes)
manifest, err := oras.Copy(ctx, repository, parsedRef.String(), memoryStore, "", oras.CopyOptions{
CopyGraphOptions: oras.CopyGraphOptions{
PreCopy: func(ctx 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)
}
descriptorsMutex.Lock()
defer descriptorsMutex.Unlock()
layers = append(layers, desc)
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
}))
return nil
},
},
})
if err != nil {
return nil, err
}
@ -421,55 +489,38 @@ 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
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)
}
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 {
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 {
if err := json.Unmarshal(result.Config.Data, &result.Chart.Meta); err != nil {
return nil, err
}
result.Chart.Meta = meta
}
if getConfigDescriptorErr != nil {
return nil, getConfigDescriptorErr
}
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.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 getChartDescriptorErr != nil {
return nil, getChartDescriptorErr
}
}
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.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
}
if getProvDescriptorErr != nil {
return nil, getProvDescriptorErr
}
}
fmt.Fprintf(c.out, "Pulled: %s\n", result.Ref)
fmt.Fprintf(c.out, "Digest: %s\n", result.Manifest.Digest)
@ -556,8 +607,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
}
@ -567,43 +621,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)
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.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,
}
@ -611,8 +679,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(),
@ -666,21 +734,17 @@ 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 {
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, "_", "+"))
@ -689,6 +753,12 @@ func (c *Client) Tags(ref string) ([]string, error) {
}
}
return nil
})
if err != nil {
return nil, err
}
// Sort the collection
sort.Sort(sort.Reverse(semver.Collection(tagVersions)))

@ -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,8 @@ 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")
// in oras-credentials-go removing a nonexisting credential causes no errors
suite.Nil(err, "no error logging out of registry that has no entry")
err = suite.RegistryClient.Logout(suite.DockerRegistryHost)
suite.Nil(err, "no error logging out of registry")

@ -66,7 +66,8 @@ 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")
// in oras-credentials-go removing a nonexisting credential causes no errors
suite.Nil(err, "no error logging out of registry that has no entry")
err = suite.RegistryClient.Logout(suite.DockerRegistryHost)
suite.Nil(err, "no error logging out of registry")

@ -18,7 +18,6 @@ package registry // import "helm.sh/helm/v3/pkg/registry"
import (
"bytes"
"context"
"fmt"
"io"
"net/http"
@ -30,9 +29,7 @@ import (
"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"
"oras.land/oras-go/pkg/registry"
"oras.land/oras-go/v2/registry"
"helm.sh/helm/v3/internal/tlsutil"
"helm.sh/helm/v3/pkg/chart"
@ -104,17 +101,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
}
// parseReference will parse and validate the reference, and clean tags when
// applicable tags are only cleaned when plus (+) signs are present, and are
// converted to underscores (_) before pushing

@ -88,7 +88,6 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
ClientOptEnableCache(true),
ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile),
ClientOptResolver(nil),
}
if tlsEnabled {
@ -139,15 +138,12 @@ 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 tls
if tlsEnabled {
@ -275,7 +271,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,7 +163,8 @@ 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(false),
ociRegistry.LoginOptPlainText(true))
if err != nil {
t.Fatalf("error logging into registry with good credentials")
}

Loading…
Cancel
Save