mirror of
https://github.com/helm/helm.git
synced 2025-02-06 11:00:38 +00:00
Compare commits
22 Commits
55f2844475
...
475318e96d
Author | SHA1 | Date | |
---|---|---|---|
|
475318e96d | ||
|
3253059438 | ||
|
2d0091c278 | ||
|
547f49abf6 | ||
|
ee67cf0608 | ||
|
52384a5d12 | ||
|
56b40ad40f | ||
|
be7ab45c93 | ||
|
54753d64dc | ||
|
5f64fe6663 | ||
|
814ede5427 | ||
|
07c5c50223 | ||
|
d637595735 | ||
|
6254b987fa | ||
|
5a7046b9bf | ||
|
119ae85988 | ||
|
c68e345663 | ||
|
0ce267d907 | ||
|
7ea1d1df66 | ||
|
e3e84343d2 | ||
|
b6d4f2b52f | ||
|
452242d36e |
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
@ -21,6 +21,6 @@ jobs:
|
||||
go-version: '1.23'
|
||||
check-latest: true
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@ec5d18412c0aeab7936cb16880d708ba2a64e1ae #pin@6.2.0
|
||||
uses: golangci/golangci-lint-action@e60da84bfae8c7920a47be973d75e15710aa8bd7 #pin@6.3.0
|
||||
with:
|
||||
version: v1.62
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -297,7 +297,12 @@ func newDefaultRegistryClient(plainHTTP bool, username, password string) (*regis
|
||||
func newRegistryClientWithTLS(
|
||||
certFile, keyFile, caFile string, insecureSkipTLSverify bool, username, password string,
|
||||
) (*registry.Client, error) {
|
||||
tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
|
||||
tlsConf, err := tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
tlsutil.WithCertKeyPairFiles(certFile, keyFile),
|
||||
tlsutil.WithCAFile(caFile),
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create TLS config for client: %w", err)
|
||||
}
|
||||
|
@ -201,6 +201,7 @@ func newTemplateCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
|
||||
f.StringVar(&kubeVersion, "kube-version", "", "Kubernetes version used for Capabilities.KubeVersion")
|
||||
f.StringSliceVarP(&extraAPIs, "api-versions", "a", []string{}, "Kubernetes api versions used for Capabilities.APIVersions")
|
||||
f.BoolVar(&client.UseReleaseName, "release-name", false, "use release name in the output-dir path.")
|
||||
f.BoolVar(&client.LintMode, "lint-mode", false, "ignore missing 'required' template values.")
|
||||
bindPostRenderFlag(cmd, &client.PostRenderer)
|
||||
|
||||
return cmd
|
||||
|
19
go.mod
19
go.mod
@ -12,7 +12,7 @@ require (
|
||||
github.com/Masterminds/vcs v1.13.3
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2
|
||||
github.com/containerd/containerd v1.7.25
|
||||
github.com/cyphar/filepath-securejoin v0.4.0
|
||||
github.com/cyphar/filepath-securejoin v0.4.1
|
||||
github.com/distribution/distribution/v3 v3.0.0-rc.2
|
||||
github.com/evanphx/json-patch v5.9.11+incompatible
|
||||
github.com/foxcpp/go-mockdns v1.1.0
|
||||
@ -29,14 +29,13 @@ 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
|
||||
github.com/xeipuuv/gojsonschema v1.2.0
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/term v0.28.0
|
||||
golang.org/x/text v0.21.0
|
||||
golang.org/x/term v0.29.0
|
||||
golang.org/x/text v0.22.0
|
||||
k8s.io/api v0.32.1
|
||||
k8s.io/apiextensions-apiserver v0.32.1
|
||||
k8s.io/apimachinery v0.32.1
|
||||
@ -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
|
||||
@ -167,8 +162,8 @@ require (
|
||||
golang.org/x/mod v0.21.0 // indirect
|
||||
golang.org/x/net v0.33.0 // indirect
|
||||
golang.org/x/oauth2 v0.23.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
golang.org/x/sync v0.11.0 // indirect
|
||||
golang.org/x/sys v0.30.0 // indirect
|
||||
golang.org/x/time v0.7.0 // indirect
|
||||
golang.org/x/tools v0.26.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241104194629-dd2ea8efbc28 // indirect
|
||||
|
54
go.sum
54
go.sum
@ -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=
|
||||
@ -71,8 +63,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||
github.com/cyphar/filepath-securejoin v0.4.0 h1:PioTG9TBRSApBpYGnDU8HC+miIsX8vitBH9LGNNMoLQ=
|
||||
github.com/cyphar/filepath-securejoin v0.4.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@ -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=
|
||||
@ -466,8 +438,8 @@ golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
|
||||
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w=
|
||||
golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
@ -489,8 +461,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
@ -498,8 +470,8 @@ golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||
golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
|
||||
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
|
||||
golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww=
|
||||
golang.org/x/term v0.28.0 h1:/Ts8HFuMR2E6IP/jlo7QVLZHggjKQbhu/7H0LJFr3Gg=
|
||||
golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek=
|
||||
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
@ -507,8 +479,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||
golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
|
||||
golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
@ -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=
|
||||
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
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 tlsutil
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Options represents configurable options used to create client and server TLS configurations.
|
||||
type Options struct {
|
||||
CaCertFile string
|
||||
// If either the KeyFile or CertFile is empty, ClientConfig() will not load them.
|
||||
KeyFile string
|
||||
CertFile string
|
||||
// Client-only options
|
||||
InsecureSkipVerify bool
|
||||
}
|
||||
|
||||
// ClientConfig returns a TLS configuration for use by a Helm client.
|
||||
func ClientConfig(opts Options) (cfg *tls.Config, err error) {
|
||||
var cert *tls.Certificate
|
||||
var pool *x509.CertPool
|
||||
|
||||
if opts.CertFile != "" || opts.KeyFile != "" {
|
||||
if cert, err = CertFromFilePair(opts.CertFile, opts.KeyFile); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil, errors.Wrapf(err, "could not load x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
|
||||
}
|
||||
return nil, errors.Wrapf(err, "could not read x509 key pair (cert: %q, key: %q)", opts.CertFile, opts.KeyFile)
|
||||
}
|
||||
}
|
||||
if !opts.InsecureSkipVerify && opts.CaCertFile != "" {
|
||||
if pool, err = CertPoolFromFile(opts.CaCertFile); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
cfg = &tls.Config{InsecureSkipVerify: opts.InsecureSkipVerify, Certificates: []tls.Certificate{*cert}, RootCAs: pool}
|
||||
return cfg, nil
|
||||
}
|
@ -19,60 +19,104 @@ package tlsutil
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// NewClientTLS returns tls.Config appropriate for client auth.
|
||||
func NewClientTLS(certFile, keyFile, caFile string, insecureSkipTLSverify bool) (*tls.Config, error) {
|
||||
type TLSConfigOptions struct {
|
||||
insecureSkipTLSverify bool
|
||||
certPEMBlock, keyPEMBlock []byte
|
||||
caPEMBlock []byte
|
||||
}
|
||||
|
||||
type TLSConfigOption func(options *TLSConfigOptions) error
|
||||
|
||||
func WithInsecureSkipVerify(insecureSkipTLSverify bool) TLSConfigOption {
|
||||
return func(options *TLSConfigOptions) error {
|
||||
options.insecureSkipTLSverify = insecureSkipTLSverify
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCertKeyPairFiles(certFile, keyFile string) TLSConfigOption {
|
||||
return func(options *TLSConfigOptions) error {
|
||||
if certFile == "" && keyFile == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
certPEMBlock, err := os.ReadFile(certFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read cert file: %q: %w", certFile, err)
|
||||
}
|
||||
|
||||
keyPEMBlock, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to read key file: %q: %w", keyFile, err)
|
||||
}
|
||||
|
||||
options.certPEMBlock = certPEMBlock
|
||||
options.keyPEMBlock = keyPEMBlock
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCAFile(caFile string) TLSConfigOption {
|
||||
return func(options *TLSConfigOptions) error {
|
||||
if caFile == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
caPEMBlock, err := os.ReadFile(caFile)
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't read CA file: %q: %w", caFile, err)
|
||||
}
|
||||
|
||||
options.caPEMBlock = caPEMBlock
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func NewTLSConfig(options ...TLSConfigOption) (*tls.Config, error) {
|
||||
to := TLSConfigOptions{}
|
||||
|
||||
errs := []error{}
|
||||
for _, option := range options {
|
||||
err := option(&to)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, errors.Join(errs...)
|
||||
}
|
||||
|
||||
config := tls.Config{
|
||||
InsecureSkipVerify: insecureSkipTLSverify,
|
||||
InsecureSkipVerify: to.insecureSkipTLSverify,
|
||||
}
|
||||
|
||||
if certFile != "" && keyFile != "" {
|
||||
cert, err := CertFromFilePair(certFile, keyFile)
|
||||
if len(to.certPEMBlock) > 0 && len(to.keyPEMBlock) > 0 {
|
||||
cert, err := tls.X509KeyPair(to.certPEMBlock, to.keyPEMBlock)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("unable to load cert from key pair: %w", err)
|
||||
}
|
||||
config.Certificates = []tls.Certificate{*cert}
|
||||
|
||||
config.Certificates = []tls.Certificate{cert}
|
||||
}
|
||||
|
||||
if caFile != "" {
|
||||
cp, err := CertPoolFromFile(caFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if len(to.caPEMBlock) > 0 {
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(to.caPEMBlock) {
|
||||
return nil, fmt.Errorf("failed to append certificates from pem block")
|
||||
}
|
||||
|
||||
config.RootCAs = cp
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
|
||||
// CertPoolFromFile returns an x509.CertPool containing the certificates
|
||||
// in the given PEM-encoded file.
|
||||
// Returns an error if the file could not be read, a certificate could not
|
||||
// be parsed, or if the file does not contain any certificates
|
||||
func CertPoolFromFile(filename string) (*x509.CertPool, error) {
|
||||
b, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("can't read CA file: %v", filename)
|
||||
}
|
||||
cp := x509.NewCertPool()
|
||||
if !cp.AppendCertsFromPEM(b) {
|
||||
return nil, errors.Errorf("failed to append certificates from file: %s", filename)
|
||||
}
|
||||
return cp, nil
|
||||
}
|
||||
|
||||
// CertFromFilePair returns a tls.Certificate containing the
|
||||
// certificates public/private key pair from a pair of given PEM-encoded files.
|
||||
// Returns an error if the file could not be read, a certificate could not
|
||||
// be parsed, or if the file does not contain any certificates
|
||||
func CertFromFilePair(certFile, keyFile string) (*tls.Certificate, error) {
|
||||
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "can't load key pair from cert %s and key %s", certFile, keyFile)
|
||||
}
|
||||
return &cert, err
|
||||
}
|
||||
|
105
internal/tlsutil/tls_test.go
Normal file
105
internal/tlsutil/tls_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
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 tlsutil
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const tlsTestDir = "../../testdata"
|
||||
|
||||
const (
|
||||
testCaCertFile = "rootca.crt"
|
||||
testCertFile = "crt.pem"
|
||||
testKeyFile = "key.pem"
|
||||
)
|
||||
|
||||
func testfile(t *testing.T, file string) (path string) {
|
||||
var err error
|
||||
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
|
||||
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func TestNewTLSConfig(t *testing.T) {
|
||||
certFile := testfile(t, testCertFile)
|
||||
keyFile := testfile(t, testKeyFile)
|
||||
caCertFile := testfile(t, testCaCertFile)
|
||||
insecureSkipTLSverify := false
|
||||
|
||||
{
|
||||
cfg, err := NewTLSConfig(
|
||||
WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
WithCertKeyPairFiles(certFile, keyFile),
|
||||
WithCAFile(caCertFile),
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
}
|
||||
{
|
||||
cfg, err := NewTLSConfig(
|
||||
WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
WithCAFile(caCertFile),
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 0 {
|
||||
t.Fatalf("expecting 0 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
cfg, err := NewTLSConfig(
|
||||
WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
WithCertKeyPairFiles(certFile, keyFile),
|
||||
)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs != nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting nil")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
/*
|
||||
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 tlsutil
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const tlsTestDir = "../../testdata"
|
||||
|
||||
const (
|
||||
testCaCertFile = "rootca.crt"
|
||||
testCertFile = "crt.pem"
|
||||
testKeyFile = "key.pem"
|
||||
)
|
||||
|
||||
func TestClientConfig(t *testing.T) {
|
||||
opts := Options{
|
||||
CaCertFile: testfile(t, testCaCertFile),
|
||||
CertFile: testfile(t, testCertFile),
|
||||
KeyFile: testfile(t, testKeyFile),
|
||||
InsecureSkipVerify: false,
|
||||
}
|
||||
|
||||
cfg, err := ClientConfig(opts)
|
||||
if err != nil {
|
||||
t.Fatalf("error building tls client config: %v", err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
}
|
||||
|
||||
func testfile(t *testing.T, file string) (path string) {
|
||||
var err error
|
||||
if path, err = filepath.Abs(filepath.Join(tlsTestDir, file)); err != nil {
|
||||
t.Fatalf("error getting absolute path to test file %q: %v", file, err)
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
||||
func TestNewClientTLS(t *testing.T) {
|
||||
certFile := testfile(t, testCertFile)
|
||||
keyFile := testfile(t, testKeyFile)
|
||||
caCertFile := testfile(t, testCaCertFile)
|
||||
insecureSkipTLSverify := false
|
||||
|
||||
cfg, err := NewClientTLS(certFile, keyFile, caCertFile, insecureSkipTLSverify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
|
||||
cfg, err = NewClientTLS("", "", caCertFile, insecureSkipTLSverify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 0 {
|
||||
t.Fatalf("expecting 0 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs == nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting non-nil")
|
||||
}
|
||||
|
||||
cfg, err = NewClientTLS(certFile, keyFile, "", insecureSkipTLSverify)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if got := len(cfg.Certificates); got != 1 {
|
||||
t.Fatalf("expecting 1 client certificates, got %d", got)
|
||||
}
|
||||
if cfg.InsecureSkipVerify {
|
||||
t.Fatalf("insecure skip verify mismatch, expecting false")
|
||||
}
|
||||
if cfg.RootCAs != nil {
|
||||
t.Fatalf("mismatch tls RootCAs, expecting nil")
|
||||
}
|
||||
}
|
@ -103,7 +103,7 @@ type Configuration struct {
|
||||
// TODO: As part of the refactor the duplicate code in cmd/helm/template.go should be removed
|
||||
//
|
||||
// This code has to do with writing files to disk.
|
||||
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, interactWithRemote, enableDNS, hideSecret bool) ([]*release.Hook, *bytes.Buffer, string, error) {
|
||||
func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Values, releaseName, outputDir string, subNotes, useReleaseName, includeCrds bool, pr postrender.PostRenderer, interactWithRemote, enableDNS, hideSecret bool, lintMode bool) ([]*release.Hook, *bytes.Buffer, string, error) {
|
||||
hs := []*release.Hook{}
|
||||
b := bytes.NewBuffer(nil)
|
||||
|
||||
@ -135,6 +135,7 @@ func (cfg *Configuration) renderResources(ch *chart.Chart, values chartutil.Valu
|
||||
} else {
|
||||
var e engine.Engine
|
||||
e.EnableDNS = enableDNS
|
||||
e.LintMode = lintMode
|
||||
files, err2 = e.Render(ch, values)
|
||||
}
|
||||
|
||||
|
@ -115,6 +115,8 @@ type Install struct {
|
||||
PostRenderer postrender.PostRenderer
|
||||
// Lock to control raceconditions when the process receives a SIGTERM
|
||||
Lock sync.Mutex
|
||||
// In LintMode, some 'required' template values may be missing, so don't fail
|
||||
LintMode bool
|
||||
}
|
||||
|
||||
// ChartPathOptions captures common options used for controlling chart paths
|
||||
@ -317,7 +319,7 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
|
||||
rel := i.createRelease(chrt, vals, i.Labels)
|
||||
|
||||
var manifestDoc *bytes.Buffer
|
||||
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithRemote, i.EnableDNS, i.HideSecret)
|
||||
rel.Hooks, manifestDoc, rel.Info.Notes, err = i.cfg.renderResources(chrt, valuesToRender, i.ReleaseName, i.OutputDir, i.SubNotes, i.UseReleaseName, i.IncludeCRDs, i.PostRenderer, interactWithRemote, i.EnableDNS, i.HideSecret, i.LintMode)
|
||||
// Even for errors, attach this if available
|
||||
if manifestDoc != nil {
|
||||
rel.Manifest = manifestDoc.String()
|
||||
@ -789,8 +791,16 @@ func (c *ChartPathOptions) LocateChart(name string, settings *cli.EnvSettings) (
|
||||
dl.Verify = downloader.VerifyAlways
|
||||
}
|
||||
if c.RepoURL != "" {
|
||||
chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(c.RepoURL, c.Username, c.Password, name, version,
|
||||
c.CertFile, c.KeyFile, c.CaFile, c.InsecureSkipTLSverify, c.PassCredentialsAll, getter.All(settings))
|
||||
chartURL, err := repo.FindChartInRepoURL(
|
||||
c.RepoURL,
|
||||
name,
|
||||
getter.All(settings),
|
||||
repo.WithChartVersion(version),
|
||||
repo.WithClientTLS(c.CertFile, c.KeyFile, c.CaFile),
|
||||
repo.WithUsernamePassword(c.Username, c.Password),
|
||||
repo.WithInsecureSkipTLSverify(c.InsecureSkipTLSverify),
|
||||
repo.WithPassCredentialsAll(c.PassCredentialsAll),
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -117,7 +117,16 @@ func (p *Pull) Run(chartRef string) (string, error) {
|
||||
}
|
||||
|
||||
if p.RepoURL != "" {
|
||||
chartURL, err := repo.FindChartInAuthAndTLSAndPassRepoURL(p.RepoURL, p.Username, p.Password, chartRef, p.Version, p.CertFile, p.KeyFile, p.CaFile, p.InsecureSkipTLSverify, p.PassCredentialsAll, getter.All(p.Settings))
|
||||
chartURL, err := repo.FindChartInRepoURL(
|
||||
p.RepoURL,
|
||||
chartRef,
|
||||
getter.All(p.Settings),
|
||||
repo.WithChartVersion(p.Version),
|
||||
repo.WithClientTLS(p.CertFile, p.KeyFile, p.CaFile),
|
||||
repo.WithUsernamePassword(p.Username, p.Password),
|
||||
repo.WithInsecureSkipTLSverify(p.InsecureSkipTLSverify),
|
||||
repo.WithPassCredentialsAll(p.PassCredentialsAll),
|
||||
)
|
||||
if err != nil {
|
||||
return out.String(), err
|
||||
}
|
||||
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
@ -273,7 +273,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
|
||||
interactWithRemote = true
|
||||
}
|
||||
|
||||
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote, u.EnableDNS, u.HideSecret)
|
||||
hooks, manifestDoc, notesTxt, err := u.cfg.renderResources(chart, valuesToRender, "", "", u.SubNotes, false, false, u.PostRenderer, interactWithRemote, u.EnableDNS, u.HideSecret, false)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
@ -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},
|
||||
}
|
||||
|
||||
|
@ -742,7 +742,7 @@ func (m *Manager) findChartURL(name, version, repoURL string, repos map[string]*
|
||||
return
|
||||
}
|
||||
}
|
||||
url, err = repo.FindChartInRepoURL(repoURL, name, version, certFile, keyFile, caFile, m.Getters)
|
||||
url, err = repo.FindChartInRepoURL(repoURL, name, m.Getters, repo.WithChartVersion(version), repo.WithClientTLS(certFile, keyFile, caFile))
|
||||
if err == nil {
|
||||
return url, username, password, false, false, "", "", "", err
|
||||
}
|
||||
|
@ -128,7 +128,11 @@ func (g *HTTPGetter) httpClient() (*http.Client, error) {
|
||||
})
|
||||
|
||||
if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS {
|
||||
tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS)
|
||||
tlsConf, err := tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS),
|
||||
tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile),
|
||||
tlsutil.WithCAFile(g.opts.caFile),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't create TLS config for client")
|
||||
}
|
||||
|
@ -311,7 +311,11 @@ func TestDownloadTLS(t *testing.T) {
|
||||
insecureSkipTLSverify := false
|
||||
|
||||
tlsSrv := httptest.NewUnstartedServer(http.HandlerFunc(func(_ http.ResponseWriter, _ *http.Request) {}))
|
||||
tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecureSkipTLSverify)
|
||||
tlsConf, err := tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
tlsutil.WithCertKeyPairFiles(pub, priv),
|
||||
tlsutil.WithCAFile(ca),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(errors.Wrap(err, "can't create TLS config for client"))
|
||||
}
|
||||
|
@ -128,7 +128,11 @@ func (g *OCIGetter) newRegistryClient() (*registry.Client, error) {
|
||||
})
|
||||
|
||||
if (g.opts.certFile != "" && g.opts.keyFile != "") || g.opts.caFile != "" || g.opts.insecureSkipVerifyTLS {
|
||||
tlsConf, err := tlsutil.NewClientTLS(g.opts.certFile, g.opts.keyFile, g.opts.caFile, g.opts.insecureSkipVerifyTLS)
|
||||
tlsConf, err := tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(g.opts.insecureSkipVerifyTLS),
|
||||
tlsutil.WithCertKeyPairFiles(g.opts.certFile, g.opts.keyFile),
|
||||
tlsutil.WithCAFile(g.opts.caFile),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create TLS config for client: %w", err)
|
||||
}
|
||||
|
@ -824,35 +824,3 @@ func scrubValidationError(err error) error {
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
|
||||
// and returns said phase (PodSucceeded or PodFailed qualify).
|
||||
func (c *Client) WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error) {
|
||||
client, err := c.getKubeClient()
|
||||
if err != nil {
|
||||
return v1.PodUnknown, err
|
||||
}
|
||||
to := int64(timeout)
|
||||
watcher, err := client.CoreV1().Pods(c.namespace()).Watch(context.Background(), metav1.ListOptions{
|
||||
FieldSelector: fmt.Sprintf("metadata.name=%s", name),
|
||||
TimeoutSeconds: &to,
|
||||
})
|
||||
if err != nil {
|
||||
return v1.PodUnknown, err
|
||||
}
|
||||
|
||||
for event := range watcher.ResultChan() {
|
||||
p, ok := event.Object.(*v1.Pod)
|
||||
if !ok {
|
||||
return v1.PodUnknown, fmt.Errorf("%s not a pod", name)
|
||||
}
|
||||
switch p.Status.Phase {
|
||||
case v1.PodFailed:
|
||||
return v1.PodFailed, nil
|
||||
case v1.PodSucceeded:
|
||||
return v1.PodSucceeded, nil
|
||||
}
|
||||
}
|
||||
|
||||
return v1.PodUnknown, err
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
@ -34,19 +33,18 @@ import (
|
||||
// delegates all its calls to `PrintingKubeClient`
|
||||
type FailingKubeClient struct {
|
||||
PrintingKubeClient
|
||||
CreateError error
|
||||
GetError error
|
||||
WaitError error
|
||||
DeleteError error
|
||||
DeleteWithPropagationError error
|
||||
WatchUntilReadyError error
|
||||
UpdateError error
|
||||
BuildError error
|
||||
BuildTableError error
|
||||
BuildDummy bool
|
||||
BuildUnstructuredError error
|
||||
WaitAndGetCompletedPodPhaseError error
|
||||
WaitDuration time.Duration
|
||||
CreateError error
|
||||
GetError error
|
||||
WaitError error
|
||||
DeleteError error
|
||||
DeleteWithPropagationError error
|
||||
WatchUntilReadyError error
|
||||
UpdateError error
|
||||
BuildError error
|
||||
BuildTableError error
|
||||
BuildDummy bool
|
||||
BuildUnstructuredError error
|
||||
WaitDuration time.Duration
|
||||
}
|
||||
|
||||
// Create returns the configured error if set or prints
|
||||
@ -133,14 +131,6 @@ func (f *FailingKubeClient) BuildTable(r io.Reader, _ bool) (kube.ResourceList,
|
||||
return f.PrintingKubeClient.BuildTable(r, false)
|
||||
}
|
||||
|
||||
// WaitAndGetCompletedPodPhase returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duration) (v1.PodPhase, error) {
|
||||
if f.WaitAndGetCompletedPodPhaseError != nil {
|
||||
return v1.PodSucceeded, f.WaitAndGetCompletedPodPhaseError
|
||||
}
|
||||
return f.PrintingKubeClient.WaitAndGetCompletedPodPhase(s, d)
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy returns the configured error if set or prints
|
||||
func (f *FailingKubeClient) DeleteWithPropagationPolicy(resources kube.ResourceList, policy metav1.DeletionPropagation) (*kube.Result, []error) {
|
||||
if f.DeleteWithPropagationError != nil {
|
||||
|
@ -21,7 +21,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
@ -111,11 +110,6 @@ func (p *PrintingKubeClient) BuildTable(_ io.Reader, _ bool) (kube.ResourceList,
|
||||
return []*resource.Info{}, nil
|
||||
}
|
||||
|
||||
// WaitAndGetCompletedPodPhase implements KubeClient WaitAndGetCompletedPodPhase.
|
||||
func (p *PrintingKubeClient) WaitAndGetCompletedPodPhase(_ string, _ time.Duration) (v1.PodPhase, error) {
|
||||
return v1.PodSucceeded, nil
|
||||
}
|
||||
|
||||
// DeleteWithPropagationPolicy implements KubeClient delete.
|
||||
//
|
||||
// It only prints out the content to be deleted.
|
||||
|
@ -20,7 +20,6 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
@ -64,10 +63,6 @@ type Interface interface {
|
||||
// Validates against OpenAPI schema if validate is true.
|
||||
Build(reader io.Reader, validate bool) (ResourceList, error)
|
||||
|
||||
// WaitAndGetCompletedPodPhase waits up to a timeout until a pod enters a completed phase
|
||||
// and returns said phase (PodSucceeded or PodFailed qualify).
|
||||
WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error)
|
||||
|
||||
// IsReachable checks whether the client is able to connect to the cluster.
|
||||
IsReachable() error
|
||||
}
|
||||
|
@ -111,7 +111,11 @@ func NewOCIPusher(ops ...Option) (Pusher, error) {
|
||||
|
||||
func (pusher *OCIPusher) newRegistryClient() (*registry.Client, error) {
|
||||
if (pusher.opts.certFile != "" && pusher.opts.keyFile != "") || pusher.opts.caFile != "" || pusher.opts.insecureSkipTLSverify {
|
||||
tlsConf, err := tlsutil.NewClientTLS(pusher.opts.certFile, pusher.opts.keyFile, pusher.opts.caFile, pusher.opts.insecureSkipTLSverify)
|
||||
tlsConf, err := tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(pusher.opts.insecureSkipTLSverify),
|
||||
tlsutil.WithCertKeyPairFiles(pusher.opts.certFile, pusher.opts.keyFile),
|
||||
tlsutil.WithCAFile(pusher.opts.caFile),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "can't create TLS config for client")
|
||||
}
|
||||
|
@ -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.httpClient == nil {
|
||||
type cloner[T any] interface {
|
||||
Clone() T
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
client.httpClient = &http.Client{
|
||||
Transport: retry.NewTransport(transport),
|
||||
}
|
||||
}
|
||||
|
||||
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.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 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},
|
||||
},
|
||||
))
|
||||
}
|
||||
|
||||
resolver, err := client.authorizer.ResolverWithOpts(opts...)
|
||||
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 = ®istryauth.Client{
|
||||
authorizer := auth.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
|
||||
}
|
||||
}
|
||||
authorizer.SetUserAgent(version.GetUserAgent())
|
||||
|
||||
dockerClient, ok := client.authorizer.(*dockerauth.Client)
|
||||
if !ok {
|
||||
return registryauth.EmptyCredential, errors.New("unable to obtain docker client")
|
||||
}
|
||||
authorizer.Credential = credentials.Credential(client.credentialsStore)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return registryauth.Credential{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}, nil
|
||||
|
||||
},
|
||||
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})
|
||||
}
|
||||
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),
|
||||
}
|
||||
if operation.insecure {
|
||||
authorizerLoginOpts = append(authorizerLoginOpts, auth.WithLoginInsecure())
|
||||
}
|
||||
if err := c.authorizer.LoginWithOpts(authorizerLoginOpts...); err != nil {
|
||||
|
||||
reg, err := remote.NewRegistry(host)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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 err := reg.Ping(ctx); err != nil {
|
||||
return fmt.Errorf("authenticating to %q: %w", host, err)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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 {
|
||||
return nil, err
|
||||
}
|
||||
result.Chart.Meta = meta
|
||||
}
|
||||
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) {
|
||||
func (c *Client) Resolve(ref string) (desc ocispec.Descriptor, err error) {
|
||||
remoteRepository, err := remote.NewRepository(ref)
|
||||
if err != nil {
|
||||
return desc, err
|
||||
}
|
||||
remoteRepository.PlainHTTP = c.plainHTTP
|
||||
|
||||
parsedReference, err := newReference(ref)
|
||||
if err != nil {
|
||||
return desc, err
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
parsedRef, err := newReference(ref)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if parsedRef.Registry == "" {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
remotesResolver, err := c.resolver(parsedRef.orasReference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, desc, err := remotesResolver.Resolve(ctx, ref)
|
||||
return &desc, err
|
||||
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")
|
||||
|
33
pkg/registry/client_test.go
Normal file
33
pkg/registry/client_test.go
Normal file
@ -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,20 +99,13 @@ 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)
|
||||
tlsConf, err := tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(insecureSkipTLSverify),
|
||||
tlsutil.WithCertKeyPairFiles(certFile, keyFile),
|
||||
tlsutil.WithCAFile(caFile),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't create TLS config for client: %s", err)
|
||||
}
|
||||
|
@ -88,16 +88,20 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
|
||||
ClientOptEnableCache(true),
|
||||
ClientOptWriter(suite.Out),
|
||||
ClientOptCredentialsFile(credentialsFile),
|
||||
ClientOptResolver(nil),
|
||||
ClientOptBasicAuth(testUsername, testPassword),
|
||||
}
|
||||
|
||||
if tlsEnabled {
|
||||
var tlsConf *tls.Config
|
||||
if insecure {
|
||||
tlsConf, err = tlsutil.NewClientTLS("", "", "", true)
|
||||
tlsConf, err = tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(true),
|
||||
)
|
||||
} else {
|
||||
tlsConf, err = tlsutil.NewClientTLS(tlsCert, tlsKey, tlsCA, false)
|
||||
tlsConf, err = tlsutil.NewTLSConfig(
|
||||
tlsutil.WithCertKeyPairFiles(tlsCert, tlsKey),
|
||||
tlsutil.WithCAFile(tlsCA),
|
||||
)
|
||||
}
|
||||
httpClient := &http.Client{
|
||||
Transport: &http.Transport{
|
||||
@ -141,14 +145,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 +278,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
|
||||
|
@ -196,33 +196,65 @@ func (r *ChartRepository) generateIndex() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type findChartInRepoURLOptions struct {
|
||||
Username string
|
||||
Password string
|
||||
PassCredentialsAll bool
|
||||
InsecureSkipTLSverify bool
|
||||
CertFile string
|
||||
KeyFile string
|
||||
CAFile string
|
||||
ChartVersion string
|
||||
}
|
||||
|
||||
type FindChartInRepoURLOption func(*findChartInRepoURLOptions)
|
||||
|
||||
// WithChartVersion specifies the chart version to find
|
||||
func WithChartVersion(chartVersion string) FindChartInRepoURLOption {
|
||||
return func(options *findChartInRepoURLOptions) {
|
||||
options.ChartVersion = chartVersion
|
||||
}
|
||||
}
|
||||
|
||||
// WithUsernamePassword specifies the username/password credntials for the repository
|
||||
func WithUsernamePassword(username, password string) FindChartInRepoURLOption {
|
||||
return func(options *findChartInRepoURLOptions) {
|
||||
options.Username = username
|
||||
options.Password = password
|
||||
}
|
||||
}
|
||||
|
||||
// WithPassCredentialsAll flags whether credentials should be passed on to other domains
|
||||
func WithPassCredentialsAll(passCredentialsAll bool) FindChartInRepoURLOption {
|
||||
return func(options *findChartInRepoURLOptions) {
|
||||
options.PassCredentialsAll = passCredentialsAll
|
||||
}
|
||||
}
|
||||
|
||||
// WithClientTLS species the cert, key, and CA files for client mTLS
|
||||
func WithClientTLS(certFile, keyFile, caFile string) FindChartInRepoURLOption {
|
||||
return func(options *findChartInRepoURLOptions) {
|
||||
options.CertFile = certFile
|
||||
options.KeyFile = keyFile
|
||||
options.CAFile = caFile
|
||||
}
|
||||
}
|
||||
|
||||
// WithInsecureSkipTLSverify skips TLS verification for repostory communication
|
||||
func WithInsecureSkipTLSverify(insecureSkipTLSverify bool) FindChartInRepoURLOption {
|
||||
return func(options *findChartInRepoURLOptions) {
|
||||
options.InsecureSkipTLSverify = insecureSkipTLSverify
|
||||
}
|
||||
}
|
||||
|
||||
// FindChartInRepoURL finds chart in chart repository pointed by repoURL
|
||||
// without adding repo to repositories
|
||||
func FindChartInRepoURL(repoURL, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
|
||||
return FindChartInAuthRepoURL(repoURL, "", "", chartName, chartVersion, certFile, keyFile, caFile, getters)
|
||||
}
|
||||
func FindChartInRepoURL(repoURL string, chartName string, getters getter.Providers, options ...FindChartInRepoURLOption) (string, error) {
|
||||
|
||||
// FindChartInAuthRepoURL finds chart in chart repository pointed by repoURL
|
||||
// without adding repo to repositories, like FindChartInRepoURL,
|
||||
// but it also receives credentials for the chart repository.
|
||||
func FindChartInAuthRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, getters getter.Providers) (string, error) {
|
||||
return FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, false, getters)
|
||||
}
|
||||
|
||||
// FindChartInAuthAndTLSRepoURL finds chart in chart repository pointed by repoURL
|
||||
// without adding repo to repositories, like FindChartInRepoURL,
|
||||
// but it also receives credentials and TLS verify flag for the chart repository.
|
||||
// TODO Helm 4, FindChartInAuthAndTLSRepoURL should be integrated into FindChartInAuthRepoURL.
|
||||
func FindChartInAuthAndTLSRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify bool, getters getter.Providers) (string, error) {
|
||||
return FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile, insecureSkipTLSverify, false, getters)
|
||||
}
|
||||
|
||||
// FindChartInAuthAndTLSAndPassRepoURL finds chart in chart repository pointed by repoURL
|
||||
// without adding repo to repositories, like FindChartInRepoURL,
|
||||
// but it also receives credentials, TLS verify flag, and if credentials should
|
||||
// be passed on to other domains.
|
||||
// TODO Helm 4, FindChartInAuthAndTLSAndPassRepoURL should be integrated into FindChartInAuthRepoURL.
|
||||
func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName, chartVersion, certFile, keyFile, caFile string, insecureSkipTLSverify, passCredentialsAll bool, getters getter.Providers) (string, error) {
|
||||
opts := findChartInRepoURLOptions{}
|
||||
for _, option := range options {
|
||||
option(&opts)
|
||||
}
|
||||
|
||||
// Download and write the index file to a temporary location
|
||||
buf := make([]byte, 20)
|
||||
@ -231,14 +263,14 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName,
|
||||
|
||||
c := Entry{
|
||||
URL: repoURL,
|
||||
Username: username,
|
||||
Password: password,
|
||||
PassCredentialsAll: passCredentialsAll,
|
||||
CertFile: certFile,
|
||||
KeyFile: keyFile,
|
||||
CAFile: caFile,
|
||||
Username: opts.Username,
|
||||
Password: opts.Password,
|
||||
PassCredentialsAll: opts.PassCredentialsAll,
|
||||
CertFile: opts.CertFile,
|
||||
KeyFile: opts.KeyFile,
|
||||
CAFile: opts.CAFile,
|
||||
Name: name,
|
||||
InsecureSkipTLSverify: insecureSkipTLSverify,
|
||||
InsecureSkipTLSverify: opts.InsecureSkipTLSverify,
|
||||
}
|
||||
r, err := NewChartRepository(&c, getters)
|
||||
if err != nil {
|
||||
@ -260,10 +292,10 @@ func FindChartInAuthAndTLSAndPassRepoURL(repoURL, username, password, chartName,
|
||||
}
|
||||
|
||||
errMsg := fmt.Sprintf("chart %q", chartName)
|
||||
if chartVersion != "" {
|
||||
errMsg = fmt.Sprintf("%s version %q", errMsg, chartVersion)
|
||||
if opts.ChartVersion != "" {
|
||||
errMsg = fmt.Sprintf("%s version %q", errMsg, opts.ChartVersion)
|
||||
}
|
||||
cv, err := repoIndex.Get(chartName, chartVersion)
|
||||
cv, err := repoIndex.Get(chartName, opts.ChartVersion)
|
||||
if err != nil {
|
||||
return "", errors.Errorf("%s not found in %s repository", errMsg, repoURL)
|
||||
}
|
||||
|
@ -297,7 +297,12 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) {
|
||||
}
|
||||
defer srv.Close()
|
||||
|
||||
chartURL, err := FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "", "", "", "", true, false, getter.All(&cli.EnvSettings{}))
|
||||
chartURL, err := FindChartInRepoURL(
|
||||
srv.URL,
|
||||
"nginx",
|
||||
getter.All(&cli.EnvSettings{}),
|
||||
WithInsecureSkipTLSverify(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
@ -306,7 +311,7 @@ func TestFindChartInAuthAndTLSAndPassRepoURL(t *testing.T) {
|
||||
}
|
||||
|
||||
// If the insecureSkipTLSVerify is false, it will return an error that contains "x509: certificate signed by unknown authority".
|
||||
_, err = FindChartInAuthAndTLSAndPassRepoURL(srv.URL, "", "", "nginx", "0.1.0", "", "", "", false, false, getter.All(&cli.EnvSettings{}))
|
||||
_, err = FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{}), WithChartVersion("0.1.0"))
|
||||
// Go communicates with the platform and different platforms return different messages. Go itself tests darwin
|
||||
// differently for its message. On newer versions of Darwin the message includes the "Acme Co" portion while older
|
||||
// versions of Darwin do not. As there are people developing Helm using both old and new versions of Darwin we test
|
||||
@ -327,7 +332,7 @@ func TestFindChartInRepoURL(t *testing.T) {
|
||||
}
|
||||
defer srv.Close()
|
||||
|
||||
chartURL, err := FindChartInRepoURL(srv.URL, "nginx", "", "", "", "", getter.All(&cli.EnvSettings{}))
|
||||
chartURL, err := FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{}))
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
@ -335,7 +340,7 @@ func TestFindChartInRepoURL(t *testing.T) {
|
||||
t.Errorf("%s is not the valid URL", chartURL)
|
||||
}
|
||||
|
||||
chartURL, err = FindChartInRepoURL(srv.URL, "nginx", "0.1.0", "", "", "", getter.All(&cli.EnvSettings{}))
|
||||
chartURL, err = FindChartInRepoURL(srv.URL, "nginx", getter.All(&cli.EnvSettings{}), WithChartVersion("0.1.0"))
|
||||
if err != nil {
|
||||
t.Errorf("%s", err)
|
||||
}
|
||||
@ -350,7 +355,7 @@ func TestErrorFindChartInRepoURL(t *testing.T) {
|
||||
RepositoryCache: t.TempDir(),
|
||||
})
|
||||
|
||||
if _, err := FindChartInRepoURL("http://someserver/something", "nginx", "", "", "", "", g); err == nil {
|
||||
if _, err := FindChartInRepoURL("http://someserver/something", "nginx", g); err == nil {
|
||||
t.Errorf("Expected error for bad chart URL, but did not get any errors")
|
||||
} else if !strings.Contains(err.Error(), `looks like "http://someserver/something" is not a valid chart repository or cannot be reached`) {
|
||||
t.Errorf("Expected error for bad chart URL, but got a different error (%v)", err)
|
||||
@ -362,19 +367,19 @@ func TestErrorFindChartInRepoURL(t *testing.T) {
|
||||
}
|
||||
defer srv.Close()
|
||||
|
||||
if _, err = FindChartInRepoURL(srv.URL, "nginx1", "", "", "", "", g); err == nil {
|
||||
if _, err = FindChartInRepoURL(srv.URL, "nginx1", g); err == nil {
|
||||
t.Errorf("Expected error for chart not found, but did not get any errors")
|
||||
} else if err.Error() != `chart "nginx1" not found in `+srv.URL+` repository` {
|
||||
t.Errorf("Expected error for chart not found, but got a different error (%v)", err)
|
||||
}
|
||||
|
||||
if _, err = FindChartInRepoURL(srv.URL, "nginx1", "0.1.0", "", "", "", g); err == nil {
|
||||
if _, err = FindChartInRepoURL(srv.URL, "nginx1", g, WithChartVersion("0.1.0")); err == nil {
|
||||
t.Errorf("Expected error for chart not found, but did not get any errors")
|
||||
} else if err.Error() != `chart "nginx1" version "0.1.0" not found in `+srv.URL+` repository` {
|
||||
t.Errorf("Expected error for chart not found, but got a different error (%v)", err)
|
||||
}
|
||||
|
||||
if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", "", "", "", "", g); err == nil {
|
||||
if _, err = FindChartInRepoURL(srv.URL, "chartWithNoURL", g); err == nil {
|
||||
t.Errorf("Expected error for no chart URLs available, but did not get any errors")
|
||||
} else if err.Error() != `chart "chartWithNoURL" has no downloadable URLs` {
|
||||
t.Errorf("Expected error for chart not found, but got a different error (%v)", err)
|
||||
|
@ -113,7 +113,7 @@ func NewOCIServer(t *testing.T, dir string) (*OCIServer, error) {
|
||||
t.Fatalf("error finding free port for test registry")
|
||||
}
|
||||
|
||||
config.HTTP.Addr = fmt.Sprintf(":%d", port)
|
||||
config.HTTP.Addr = fmt.Sprintf("127.0.0.1:%d", port)
|
||||
config.HTTP.DrainTimeout = time.Duration(10) * time.Second
|
||||
config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}}
|
||||
config.Auth = configuration.Auth{
|
||||
@ -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)
|
||||
@ -367,7 +368,11 @@ func (s *Server) StartTLS() {
|
||||
}
|
||||
http.FileServer(http.Dir(s.Root())).ServeHTTP(w, r)
|
||||
}))
|
||||
tlsConf, err := tlsutil.NewClientTLS(pub, priv, ca, insecure)
|
||||
tlsConf, err := tlsutil.NewTLSConfig(
|
||||
tlsutil.WithInsecureSkipVerify(insecure),
|
||||
tlsutil.WithCertKeyPairFiles(pub, priv),
|
||||
tlsutil.WithCAFile(ca),
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user