From 5ce68d350fb1dfd6b98900abfb3c93acd541a55e Mon Sep 17 00:00:00 2001 From: Jimmi Dyson Date: Tue, 15 Mar 2022 11:06:52 +0000 Subject: [PATCH] feat: Add username and password auth to helm repository config Enables passing credentials to authenticate against a Helm repository. Fixes #51. --- cmd/create/helmbundle/helm_bundle.go | 20 ++- config/helm_charts_config.go | 6 + config/helm_charts_config_test.go | 139 ++++++++++++++++++ config/images_config.go | 2 +- config/testdata/helmcharts/empty.yaml | 4 + ...ultiple_charts_with_multiple_versions.yaml | 33 +++++ ...multiple_repositories_with_tls_config.yaml | 9 ++ ...ory_with_chart_with_multiple_versions.yaml | 11 ++ ...with_chart_with_no_requested_versions.yaml | 8 + ...sitory_with_chart_with_single_version.yaml | 9 ++ ...ultiple_charts_with_multiple_versions.yaml | 19 +++ .../single_repository_with_no_charts.yaml | 6 + .../single_repository_with_tls_config.yaml | 7 + .../multiple_registries_with_tls_config.yaml | 4 +- .../single_registry_with_tls_config.yaml | 2 +- helm/client.go | 82 +++++++---- images-example.yaml | 2 +- 17 files changed, 327 insertions(+), 36 deletions(-) create mode 100644 config/helm_charts_config_test.go create mode 100644 config/testdata/helmcharts/empty.yaml create mode 100644 config/testdata/helmcharts/multiple_repositories_with_multiple_charts_with_multiple_versions.yaml create mode 100644 config/testdata/helmcharts/multiple_repositories_with_tls_config.yaml create mode 100644 config/testdata/helmcharts/single_repository_with_chart_with_multiple_versions.yaml create mode 100644 config/testdata/helmcharts/single_repository_with_chart_with_no_requested_versions.yaml create mode 100644 config/testdata/helmcharts/single_repository_with_chart_with_single_version.yaml create mode 100644 config/testdata/helmcharts/single_repository_with_multiple_charts_with_multiple_versions.yaml create mode 100644 config/testdata/helmcharts/single_repository_with_no_charts.yaml create mode 100644 config/testdata/helmcharts/single_repository_with_tls_config.yaml diff --git a/cmd/create/helmbundle/helm_bundle.go b/cmd/create/helmbundle/helm_bundle.go index fd6e1821..d06bb0dd 100644 --- a/cmd/create/helmbundle/helm_bundle.go +++ b/cmd/create/helmbundle/helm_bundle.go @@ -11,6 +11,8 @@ import ( "github.com/mesosphere/dkp-cli-runtime/core/output" "github.com/spf13/cobra" + "helm.sh/helm/v3/pkg/action" + "k8s.io/utils/pointer" "github.com/mesosphere/mindthegap/archive" "github.com/mesosphere/mindthegap/cleanup" @@ -115,9 +117,21 @@ func NewCommand(out output.Output) *cobra.Command { repoConfig.RepoURL, ), ) - if err := helmClient.GetChartFromRepo(tempDir, repoConfig.RepoURL, chartName, chartVersions...); err != nil { - out.EndOperation(false) - return fmt.Errorf("failed to create Helm chart bundle: %v", err) + var opts []action.PullOpt + if repoConfig.Username != "" { + opts = append( + opts, + helm.UsernamePasswordOpt(repoConfig.Username, repoConfig.Password), + ) + } + if !pointer.BoolDeref(repoConfig.TLSVerify, true) { + opts = append(opts, helm.InsecureSkipTLSverifyOpt()) + } + for _, chartVersion := range chartVersions { + if err := helmClient.GetChartFromRepo(tempDir, repoConfig.RepoURL, chartName, chartVersion, opts...); err != nil { + out.EndOperation(false) + return fmt.Errorf("failed to create Helm chart bundle: %v", err) + } } out.EndOperation(true) } diff --git a/config/helm_charts_config.go b/config/helm_charts_config.go index 08699ad3..64bf71cc 100644 --- a/config/helm_charts_config.go +++ b/config/helm_charts_config.go @@ -15,6 +15,12 @@ import ( type HelmRepositorySyncConfig struct { // RepoURL is the URL for the repository. RepoURL string `yaml:"repoURL"` + // Username holds the username for the repository. + Username string `yaml:"username,omitempty"` + // Password holds the password for the repository. + Password string `yaml:"password,omitempty"` + // TLS verification mode (enabled by default) + TLSVerify *bool `yaml:"tlsVerify,omitempty"` // Charts map charts name to slices with the chart versions. Charts map[string][]string `yaml:"charts,omitempty"` } diff --git a/config/helm_charts_config_test.go b/config/helm_charts_config_test.go new file mode 100644 index 00000000..da7af751 --- /dev/null +++ b/config/helm_charts_config_test.go @@ -0,0 +1,139 @@ +// Copyright 2021 D2iQ, Inc. All rights reserved. +// SPDX-License-Identifier: Apache-2.0 + +package config + +import ( + "path/filepath" + "reflect" + "strings" + "testing" + + "k8s.io/utils/pointer" +) + +func TestParseHelmChartsFile(t *testing.T) { + t.Parallel() + tests := []struct { + name string + want HelmChartsConfig + wantErr bool + }{{ + name: "empty", + want: HelmChartsConfig{}, + }, { + name: "single repository with no charts", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": {}, + }, + }, + }, { + name: "single repository with chart with no requested versions", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": { + Charts: map[string][]string{ + "test-chart": nil, + }, + }, + }, + }, + }, { + name: "single repository with chart with single version", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": { + Charts: map[string][]string{ + "test-chart": {"v1.2.3"}, + }, + }, + }, + }, + }, { + name: "single repository with chart with multiple versions", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": { + Charts: map[string][]string{ + "test-chart": {"v1.2.3", "v2.4.6", "v3.6.9"}, + }, + }, + }, + }, + }, { + name: "single repository with multiple charts with multiple versions", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": { + Charts: map[string][]string{ + "test-chart": {"v1.2.3", "v2.4.6", "v3.6.9"}, + "test-chart2": {"v3.6.9", "v4.8.12", "v5.10.15"}, + "test-chart3": {"v6.12.18", "v7.14.21", "v8.16.24"}, + }, + }, + }, + }, + }, { + name: "multiple repositories with multiple charts with multiple versions", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": { + Charts: map[string][]string{ + "test-chart": {"v1.2.3", "v2.4.6", "v3.6.9"}, + "test-chart2": {"v3.6.9", "v4.8.12", "v5.10.15"}, + "test-chart3": {"v6.12.18", "v7.14.21", "v8.16.24"}, + }, + }, "test.repository2.io": { + Charts: map[string][]string{ + "test-chart": {"v1.2.31", "v2.4.61", "v3.6.91"}, + "test-chart5": {"v3.6.92", "v4.8.122", "v5.10.152"}, + "test-chart6": {"v63.126.189", "v73.146.219", "v8.16.243"}, + }, + }, + }, + }, + }, { + name: "single repository with tls config", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": { + TLSVerify: pointer.Bool(false), + }, + }, + }, + }, { + name: "multiple repositories with tls config", + want: HelmChartsConfig{ + Repositories: map[string]HelmRepositorySyncConfig{ + "test.repository.io": { + TLSVerify: pointer.Bool(false), + }, + "test.repository2.io": { + TLSVerify: pointer.Bool(true), + }, + }, + }, + }} + for ti := range tests { + tt := tests[ti] + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + ext := "yaml" + got, err := ParseHelmChartsConfigFile( + filepath.Join( + "testdata", + "helmcharts", + strings.ReplaceAll(tt.name, " ", "_")+"."+ext, + ), + ) + if (err != nil) != tt.wantErr { + t.Errorf("ParseFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("ParseFile() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/config/images_config.go b/config/images_config.go index abbcaef4..3e01a82d 100644 --- a/config/images_config.go +++ b/config/images_config.go @@ -22,7 +22,7 @@ type RegistrySyncConfig struct { // Images map images name to slices with the images' references (tags, digests) Images map[string][]string // TLS verification mode (enabled by default) - TLSVerify *bool `yaml:"tls-verify,omitempty"` + TLSVerify *bool `yaml:"tlsVerify,omitempty"` // Username and password used to authenticate with the registry Credentials *types.DockerAuthConfig `yaml:"credentials,omitempty"` } diff --git a/config/testdata/helmcharts/empty.yaml b/config/testdata/helmcharts/empty.yaml new file mode 100644 index 00000000..c270d5a0 --- /dev/null +++ b/config/testdata/helmcharts/empty.yaml @@ -0,0 +1,4 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- diff --git a/config/testdata/helmcharts/multiple_repositories_with_multiple_charts_with_multiple_versions.yaml b/config/testdata/helmcharts/multiple_repositories_with_multiple_charts_with_multiple_versions.yaml new file mode 100644 index 00000000..859cb5c5 --- /dev/null +++ b/config/testdata/helmcharts/multiple_repositories_with_multiple_charts_with_multiple_versions.yaml @@ -0,0 +1,33 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: + charts: + test-chart: + - v1.2.3 + - v2.4.6 + - v3.6.9 + test-chart2: + - v3.6.9 + - v4.8.12 + - v5.10.15 + test-chart3: + - v6.12.18 + - v7.14.21 + - v8.16.24 + test.repository2.io: + charts: + test-chart: + - v1.2.31 + - v2.4.61 + - v3.6.91 + test-chart5: + - v3.6.92 + - v4.8.122 + - v5.10.152 + test-chart6: + - v63.126.189 + - v73.146.219 + - v8.16.243 diff --git a/config/testdata/helmcharts/multiple_repositories_with_tls_config.yaml b/config/testdata/helmcharts/multiple_repositories_with_tls_config.yaml new file mode 100644 index 00000000..4bdc280b --- /dev/null +++ b/config/testdata/helmcharts/multiple_repositories_with_tls_config.yaml @@ -0,0 +1,9 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: + tlsVerify: false + test.repository2.io: + tlsVerify: true diff --git a/config/testdata/helmcharts/single_repository_with_chart_with_multiple_versions.yaml b/config/testdata/helmcharts/single_repository_with_chart_with_multiple_versions.yaml new file mode 100644 index 00000000..d2cf7532 --- /dev/null +++ b/config/testdata/helmcharts/single_repository_with_chart_with_multiple_versions.yaml @@ -0,0 +1,11 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: + charts: + test-chart: + - v1.2.3 + - v2.4.6 + - v3.6.9 diff --git a/config/testdata/helmcharts/single_repository_with_chart_with_no_requested_versions.yaml b/config/testdata/helmcharts/single_repository_with_chart_with_no_requested_versions.yaml new file mode 100644 index 00000000..91097f8b --- /dev/null +++ b/config/testdata/helmcharts/single_repository_with_chart_with_no_requested_versions.yaml @@ -0,0 +1,8 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: + charts: + test-chart: diff --git a/config/testdata/helmcharts/single_repository_with_chart_with_single_version.yaml b/config/testdata/helmcharts/single_repository_with_chart_with_single_version.yaml new file mode 100644 index 00000000..454c5f46 --- /dev/null +++ b/config/testdata/helmcharts/single_repository_with_chart_with_single_version.yaml @@ -0,0 +1,9 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: + charts: + test-chart: + - v1.2.3 diff --git a/config/testdata/helmcharts/single_repository_with_multiple_charts_with_multiple_versions.yaml b/config/testdata/helmcharts/single_repository_with_multiple_charts_with_multiple_versions.yaml new file mode 100644 index 00000000..8089248c --- /dev/null +++ b/config/testdata/helmcharts/single_repository_with_multiple_charts_with_multiple_versions.yaml @@ -0,0 +1,19 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: + charts: + test-chart: + - v1.2.3 + - v2.4.6 + - v3.6.9 + test-chart2: + - v3.6.9 + - v4.8.12 + - v5.10.15 + test-chart3: + - v6.12.18 + - v7.14.21 + - v8.16.24 diff --git a/config/testdata/helmcharts/single_repository_with_no_charts.yaml b/config/testdata/helmcharts/single_repository_with_no_charts.yaml new file mode 100644 index 00000000..73603e3a --- /dev/null +++ b/config/testdata/helmcharts/single_repository_with_no_charts.yaml @@ -0,0 +1,6 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: diff --git a/config/testdata/helmcharts/single_repository_with_tls_config.yaml b/config/testdata/helmcharts/single_repository_with_tls_config.yaml new file mode 100644 index 00000000..e49aa3c2 --- /dev/null +++ b/config/testdata/helmcharts/single_repository_with_tls_config.yaml @@ -0,0 +1,7 @@ +# Copyright 2021 D2iQ, Inc. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +--- +repositories: + test.repository.io: + tlsVerify: false diff --git a/config/testdata/images/multiple_registries_with_tls_config.yaml b/config/testdata/images/multiple_registries_with_tls_config.yaml index 05f89940..07b3bc6b 100644 --- a/config/testdata/images/multiple_registries_with_tls_config.yaml +++ b/config/testdata/images/multiple_registries_with_tls_config.yaml @@ -3,6 +3,6 @@ --- test.registry.io: - tls-verify: false + tlsVerify: false test.registry2.io: - tls-verify: true + tlsVerify: true diff --git a/config/testdata/images/single_registry_with_tls_config.yaml b/config/testdata/images/single_registry_with_tls_config.yaml index e59b9168..b2a8bc03 100644 --- a/config/testdata/images/single_registry_with_tls_config.yaml +++ b/config/testdata/images/single_registry_with_tls_config.yaml @@ -3,4 +3,4 @@ --- test.registry.io: - tls-verify: false + tlsVerify: false diff --git a/helm/client.go b/helm/client.go index 53db4124..6289c5bd 100644 --- a/helm/client.go +++ b/helm/client.go @@ -38,60 +38,86 @@ func NewClient(out output.Output) (*Client, CleanupFunc) { } } -func (c *Client) doNotUntarOpt() action.PullOpt { +func DoNotUntarOpt() action.PullOpt { return func(p *action.Pull) { p.Untar = false } } -func (c *Client) destDirOpt(outputDir string) action.PullOpt { +func DestDirOpt(outputDir string) action.PullOpt { return func(p *action.Pull) { p.DestDir = outputDir } } -func (c *Client) tempRepositoryCacheOpt() action.PullOpt { +func TempRepositoryCacheOpt(tempDir string) action.PullOpt { return func(p *action.Pull) { if p.Settings == nil { p.Settings = &cli.EnvSettings{} } - p.Settings.RepositoryCache = c.tempDir + p.Settings.RepositoryCache = tempDir } } -func (c *Client) repoURLOpt(repoURL string) action.PullOpt { +func RepoURLOpt(repoURL string) action.PullOpt { return func(p *action.Pull) { p.RepoURL = repoURL } } +func ChartVersionOpt(chartVersion string) action.PullOpt { + return func(p *action.Pull) { + p.Version = chartVersion + } +} + +func UsernamePasswordOpt(username, password string) action.PullOpt { + return func(p *action.Pull) { + p.Username = username + p.Password = password + } +} + +func InsecureSkipTLSverifyOpt() action.PullOpt { + return func(p *action.Pull) { + p.InsecureSkipTLSverify = true + } +} + +func CAFileOpt(caFile string) action.PullOpt { + return func(p *action.Pull) { + p.CaFile = caFile + } +} + func (c *Client) GetChartFromRepo( - outputDir, repoURL, chartName string, - chartVersions ...string, + outputDir, repoURL, chartName, chartVersion string, + extraPullOpts ...action.PullOpt, ) error { pull := action.NewPullWithOpts( - action.WithConfig(&action.Configuration{Log: c.out.V(4).Infof}), - c.doNotUntarOpt(), - c.destDirOpt(outputDir), - c.tempRepositoryCacheOpt(), - c.repoURLOpt(repoURL), + append( + extraPullOpts, + action.WithConfig(&action.Configuration{Log: c.out.V(4).Infof}), + DoNotUntarOpt(), + DestDirOpt(outputDir), + TempRepositoryCacheOpt(c.tempDir), + RepoURLOpt(repoURL), + ChartVersionOpt(chartVersion), + )..., ) - for _, v := range chartVersions { - pull.Version = v - helmOutput, err := pull.Run(chartName) - if err != nil { - return fmt.Errorf( - "failed to fetch chart %s:%s from %s: %w, output:\n\n%s", - chartName, - chartVersions, - repoURL, - err, - helmOutput, - ) - } - if helmOutput != "" { - c.out.V(4).Info(helmOutput) - } + helmOutput, err := pull.Run(chartName) + if err != nil { + return fmt.Errorf( + "failed to fetch chart %s:%s from %s: %w, output:\n\n%s", + chartName, + chartVersion, + repoURL, + err, + helmOutput, + ) + } + if helmOutput != "" { + c.out.V(4).Info(helmOutput) } return nil diff --git a/images-example.yaml b/images-example.yaml index 0380176e..552a10fb 100644 --- a/images-example.yaml +++ b/images-example.yaml @@ -105,7 +105,7 @@ k8s.gcr.io: external-dns/external-dns: - v0.10.2 quay.io: - tls-verify: false + tlsVerify: false images: jetstack/cert-manager-cainjector: - v1.5.4