From 0805a87140c76edc441151950013ddb246e76cfa Mon Sep 17 00:00:00 2001 From: Matthew Fisher Date: Fri, 8 Feb 2019 10:43:03 -0800 Subject: [PATCH 1/7] ref(uninstall): purge release history by default Signed-off-by: Matthew Fisher --- ...{uninstall-purge.txt => uninstall-keep-history.txt} | 0 cmd/helm/uninstall.go | 10 ++++++---- cmd/helm/uninstall_test.go | 6 +++--- docs/faq.md | 10 ++++++++++ pkg/action/uninstall.go | 6 +++--- scripts/completions.bash | 4 ++-- 6 files changed, 24 insertions(+), 12 deletions(-) rename cmd/helm/testdata/output/{uninstall-purge.txt => uninstall-keep-history.txt} (100%) diff --git a/cmd/helm/testdata/output/uninstall-purge.txt b/cmd/helm/testdata/output/uninstall-keep-history.txt similarity index 100% rename from cmd/helm/testdata/output/uninstall-purge.txt rename to cmd/helm/testdata/output/uninstall-keep-history.txt diff --git a/cmd/helm/uninstall.go b/cmd/helm/uninstall.go index 7d0bb09ff..d9f30ecd2 100644 --- a/cmd/helm/uninstall.go +++ b/cmd/helm/uninstall.go @@ -27,8 +27,10 @@ import ( ) const uninstallDesc = ` -This command takes a release name, and then uninstalls the release from Kubernetes. -It removes all of the resources associated with the last release of the chart. +This command takes a release name and uninstalls the release. + +It removes all of the resources associated with the last release of the chart +as well as the release history, freeing it up for future use. Use the '--dry-run' flag to see which releases will be uninstalled without actually uninstalling them. @@ -41,7 +43,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { Use: "uninstall RELEASE_NAME [...]", Aliases: []string{"del", "delete", "un"}, SuggestFor: []string{"remove", "rm"}, - Short: "given a release name, uninstall the release from Kubernetes", + Short: "uninstall a release", Long: uninstallDesc, Args: require.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { @@ -64,7 +66,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation") - f.BoolVar(&client.Purge, "purge", false, "remove the release from the store and make its name free for later use") + f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history") f.Int64Var(&client.Timeout, "timeout", 300, "time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks)") return cmd diff --git a/cmd/helm/uninstall_test.go b/cmd/helm/uninstall_test.go index 5ebb20ead..e4ce0d166 100644 --- a/cmd/helm/uninstall_test.go +++ b/cmd/helm/uninstall_test.go @@ -43,9 +43,9 @@ func TestUninstall(t *testing.T) { rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})}, }, { - name: "purge", - cmd: "uninstall aeneas --purge", - golden: "output/uninstall-purge.txt", + name: "keep history", + cmd: "uninstall aeneas --keep-history", + golden: "output/uninstall-keep-history.txt", rels: []*release.Release{release.Mock(&release.MockReleaseOptions{Name: "aeneas"})}, }, { diff --git a/docs/faq.md b/docs/faq.md index 4d37e8a24..85bfc5350 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -15,6 +15,16 @@ Here's an exhaustive list of all the major changes introduced in Helm 3. In Helm 3, Helm switched the Go import path over from `k8s.io/helm` to `helm.sh/helm`. If you intend to upgrade to the Helm 3 Go client libraries, make sure to change your import paths. +### Helm delete + +In order to better align the verbiage from other package managers, `helm delete` was re-named to +`helm uninstall`. `helm delete` is still retained as an alias to `helm uninstall`, so either form +can be used. + +In Helm 2, in order to purge the release ledger, the `--purge` flag had to be provided. This +functionality is now enabled by default. To retain the previous behaviour, use +`helm uninstall --keep-history`. + ## Installing diff --git a/pkg/action/uninstall.go b/pkg/action/uninstall.go index e44b15583..11d1b5225 100644 --- a/pkg/action/uninstall.go +++ b/pkg/action/uninstall.go @@ -38,7 +38,7 @@ type Uninstall struct { DisableHooks bool DryRun bool - Purge bool + KeepHistory bool Timeout int64 } @@ -78,7 +78,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) // TODO: Are there any cases where we want to force a delete even if it's // already marked deleted? if rel.Info.Status == release.StatusUninstalled { - if u.Purge { + if !u.KeepHistory { if err := u.purgeReleases(rels...); err != nil { return nil, errors.Wrap(err, "uninstall: Failed to purge the release") } @@ -119,7 +119,7 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error) rel.Info.Status = release.StatusUninstalled rel.Info.Description = "Uninstallation complete" - if u.Purge { + if !u.KeepHistory { u.cfg.Log("purge requested for %s", name) err := u.purgeReleases(rels...) return res, errors.Wrap(err, "uninstall: Failed to purge the release") diff --git a/scripts/completions.bash b/scripts/completions.bash index ededbb791..6c05963b4 100644 --- a/scripts/completions.bash +++ b/scripts/completions.bash @@ -287,8 +287,8 @@ _helm_delete() local_nonpersistent_flags+=("--dry-run") flags+=("--no-hooks") local_nonpersistent_flags+=("--no-hooks") - flags+=("--purge") - local_nonpersistent_flags+=("--purge") + flags+=("--keep-history") + local_nonpersistent_flags+=("--keep-history") flags+=("--timeout=") local_nonpersistent_flags+=("--timeout=") flags+=("--tls") From b600f6090e875f7782f3d6c36b5a15cdbf27f945 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Tue, 2 Apr 2019 17:22:49 +0100 Subject: [PATCH 2/7] Add app version to history table Signed-off-by: Martin Hickey --- cmd/helm/history.go | 10 +++++----- cmd/helm/testdata/output/history-limit.txt | 6 +++--- cmd/helm/testdata/output/history.json | 2 +- cmd/helm/testdata/output/history.txt | 10 +++++----- cmd/helm/testdata/output/history.yaml | 6 ++++-- pkg/action/history.go | 16 ++++++++++++++-- pkg/chart/chart.go | 8 ++++++++ pkg/release/mock.go | 5 +++-- 8 files changed, 43 insertions(+), 20 deletions(-) diff --git a/cmd/helm/history.go b/cmd/helm/history.go index 699cf38ca..09740fa22 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -35,11 +35,11 @@ configures the maximum length of the revision list returned. The historical release set is printed as a formatted table, e.g: $ helm history angry-bird --max=4 - REVISION UPDATED STATUS CHART DESCRIPTION - 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 Initial install - 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 Upgraded successfully - 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 Rolled back to 2 - 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 Upgraded successfully + REVISION UPDATED STATUS CHART APP_VERSION DESCRIPTION + 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install + 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully + 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Rolled back to 2 + 4 Mon Oct 3 10:15:13 2016 deployed alpine-0.1.0 1.0 Upgraded successfully ` func newHistoryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { diff --git a/cmd/helm/testdata/output/history-limit.txt b/cmd/helm/testdata/output/history-limit.txt index 48c900c86..b4a6e4b88 100644 --- a/cmd/helm/testdata/output/history-limit.txt +++ b/cmd/helm/testdata/output/history-limit.txt @@ -1,3 +1,3 @@ -REVISION UPDATED STATUS CHART DESCRIPTION -3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock -4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 Release mock +REVISION UPDATED STATUS CHART APP_VERSION DESCRIPTION +3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock +4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 1.0 Release mock diff --git a/cmd/helm/testdata/output/history.json b/cmd/helm/testdata/output/history.json index e16b5670e..364007f3c 100644 --- a/cmd/helm/testdata/output/history.json +++ b/cmd/helm/testdata/output/history.json @@ -1 +1 @@ -[{"revision":3,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"superseded","chart":"foo-0.1.0-beta.1","description":"Release mock"},{"revision":4,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"deployed","chart":"foo-0.1.0-beta.1","description":"Release mock"}] +[{"revision":3,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"superseded","chart":"foo-0.1.0-beta.1","app_version":"1.0","description":"Release mock"},{"revision":4,"updated":"1977-09-02 22:04:05 +0000 UTC","status":"deployed","chart":"foo-0.1.0-beta.1","app_version":"1.0","description":"Release mock"}] diff --git a/cmd/helm/testdata/output/history.txt b/cmd/helm/testdata/output/history.txt index e2379f721..a925f2080 100644 --- a/cmd/helm/testdata/output/history.txt +++ b/cmd/helm/testdata/output/history.txt @@ -1,5 +1,5 @@ -REVISION UPDATED STATUS CHART DESCRIPTION -1 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock -2 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock -3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 Release mock -4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 Release mock +REVISION UPDATED STATUS CHART APP_VERSION DESCRIPTION +1 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock +2 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock +3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock +4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 1.0 Release mock diff --git a/cmd/helm/testdata/output/history.yaml b/cmd/helm/testdata/output/history.yaml index 042b87118..fc2887068 100644 --- a/cmd/helm/testdata/output/history.yaml +++ b/cmd/helm/testdata/output/history.yaml @@ -1,9 +1,11 @@ -- chart: foo-0.1.0-beta.1 +- app_version: "1.0" + chart: foo-0.1.0-beta.1 description: Release mock revision: 3 status: superseded updated: 1977-09-02 22:04:05 +0000 UTC -- chart: foo-0.1.0-beta.1 +- app_version: "1.0" + chart: foo-0.1.0-beta.1 description: Release mock revision: 4 status: deployed diff --git a/pkg/action/history.go b/pkg/action/history.go index 290154986..51d54a5f2 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -34,6 +34,7 @@ type releaseInfo struct { Updated string `json:"updated"` Status string `json:"status"` Chart string `json:"chart"` + AppVersion string `json:"app_version"` Description string `json:"description"` } @@ -142,11 +143,13 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) { s := r.Info.Status.String() v := r.Version d := r.Info.Description + a := formatAppVersion(r.Chart) rInfo := releaseInfo{ Revision: v, Status: s, Chart: c, + AppVersion: a, Description: d, } if !r.Info.LastDeployed.IsZero() { @@ -162,10 +165,10 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) { func formatAsTable(releases releaseHistory) []byte { tbl := uitable.New() - tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "DESCRIPTION") + tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP_VERSION", "DESCRIPTION") for i := 0; i <= len(releases)-1; i++ { r := releases[i] - tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.Description) + tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion, r.Description) } return tbl.Bytes() } @@ -178,3 +181,12 @@ func formatChartname(c *chart.Chart) string { } return fmt.Sprintf("%s-%s", c.Name(), c.Metadata.Version) } + +func formatAppVersion(c *chart.Chart) string { + if c == nil || c.Metadata == nil { + // This is an edge case that has happened in prod, though we don't + // know how: https://github.com/helm/helm/issues/1347 + return "MISSING" + } + return c.AppVersion() +} diff --git a/pkg/chart/chart.go b/pkg/chart/chart.go index 06aebf37b..13e151df6 100644 --- a/pkg/chart/chart.go +++ b/pkg/chart/chart.go @@ -100,3 +100,11 @@ func (ch *Chart) ChartFullPath() string { func (ch *Chart) Validate() error { return ch.Metadata.Validate() } + +// AppVersion returns the appversion of the chart. +func (ch *Chart) AppVersion() string { + if ch.Metadata == nil { + return "" + } + return ch.Metadata.AppVersion +} diff --git a/pkg/release/mock.go b/pkg/release/mock.go index 0a3656a3b..3e8a8e361 100644 --- a/pkg/release/mock.go +++ b/pkg/release/mock.go @@ -71,8 +71,9 @@ func Mock(opts *MockReleaseOptions) *Release { if opts.Chart == nil { ch = &chart.Chart{ Metadata: &chart.Metadata{ - Name: "foo", - Version: "0.1.0-beta.1", + Name: "foo", + Version: "0.1.0-beta.1", + AppVersion: "1.0", }, Templates: []*chart.File{ {Name: "templates/foo.tpl", Data: []byte(MockManifest)}, From b72e25cfb97c0db2e1476e40823ffa4b5a0795be Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Tue, 23 Apr 2019 17:43:43 +0100 Subject: [PATCH 3/7] Change header "APP_VERSION" to "APP VERSION" Update following review comment: - https://github.com/helm/helm/pull/5538#pullrequestreview-221803355 Signed-off-by: Martin Hickey --- cmd/helm/history.go | 2 +- cmd/helm/testdata/output/history-limit.txt | 2 +- cmd/helm/testdata/output/history.txt | 2 +- pkg/action/history.go | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/helm/history.go b/cmd/helm/history.go index 09740fa22..7ba4eb7b3 100644 --- a/cmd/helm/history.go +++ b/cmd/helm/history.go @@ -35,7 +35,7 @@ configures the maximum length of the revision list returned. The historical release set is printed as a formatted table, e.g: $ helm history angry-bird --max=4 - REVISION UPDATED STATUS CHART APP_VERSION DESCRIPTION + REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION 1 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Initial install 2 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Upgraded successfully 3 Mon Oct 3 10:15:13 2016 superseded alpine-0.1.0 1.0 Rolled back to 2 diff --git a/cmd/helm/testdata/output/history-limit.txt b/cmd/helm/testdata/output/history-limit.txt index b4a6e4b88..92ce86edf 100644 --- a/cmd/helm/testdata/output/history-limit.txt +++ b/cmd/helm/testdata/output/history-limit.txt @@ -1,3 +1,3 @@ -REVISION UPDATED STATUS CHART APP_VERSION DESCRIPTION +REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION 3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock 4 1977-09-02 22:04:05 +0000 UTC deployed foo-0.1.0-beta.1 1.0 Release mock diff --git a/cmd/helm/testdata/output/history.txt b/cmd/helm/testdata/output/history.txt index a925f2080..a6161b524 100644 --- a/cmd/helm/testdata/output/history.txt +++ b/cmd/helm/testdata/output/history.txt @@ -1,4 +1,4 @@ -REVISION UPDATED STATUS CHART APP_VERSION DESCRIPTION +REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION 1 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock 2 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock 3 1977-09-02 22:04:05 +0000 UTC superseded foo-0.1.0-beta.1 1.0 Release mock diff --git a/pkg/action/history.go b/pkg/action/history.go index 51d54a5f2..36a11710e 100644 --- a/pkg/action/history.go +++ b/pkg/action/history.go @@ -165,7 +165,7 @@ func getReleaseHistory(rls []*release.Release) (history releaseHistory) { func formatAsTable(releases releaseHistory) []byte { tbl := uitable.New() - tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP_VERSION", "DESCRIPTION") + tbl.AddRow("REVISION", "UPDATED", "STATUS", "CHART", "APP VERSION", "DESCRIPTION") for i := 0; i <= len(releases)-1; i++ { r := releases[i] tbl.AddRow(r.Revision, r.Updated, r.Status, r.Chart, r.AppVersion, r.Description) From 4ad8b0cb004b6bd28aeb6c9da885a68d11bdaba0 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Mon, 29 Apr 2019 09:59:22 +0100 Subject: [PATCH 4/7] Update from source section in install doc Signed-off-by: Martin Hickey --- docs/install.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/install.md b/docs/install.md index 3bd5beeb5..cc684bf45 100755 --- a/docs/install.md +++ b/docs/install.md @@ -82,18 +82,16 @@ You must have a working Go environment with ```console $ cd $GOPATH -$ mkdir -p src/k8s.io -$ cd src/k8s.io +$ mkdir -p src/helm.sh +$ cd src/helm.sh $ git clone https://github.com/helm/helm.git $ cd helm -$ make bootstrap build +$ make ``` -The `bootstrap` target will attempt to install dependencies, rebuild the -`vendor/` tree, and validate configuration. - -The `build` target will compile `helm` and place it in `bin/helm`. - +If required, it will first install dependencies, rebuild the +`vendor/` tree, and validate configuration. It will then compile `helm` and +place it in `bin/helm`. ## Conclusion From 86c5e52ac4b67e6a853eda9b94a9dba3978a7163 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Tue, 30 Apr 2019 18:13:17 +0100 Subject: [PATCH 5/7] Fix the image field for the scaffold chart application Signed-off-by: Martin Hickey --- pkg/chartutil/create.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index e9e5e12e1..40021ad42 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -72,8 +72,9 @@ type: application version: 0.1.0 # This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. -appVersion: 0.1.0 +# incremented each time you make changes to the application. Depending on the application, +# 'stable' can signify the latest stable version of the application. +appVersion: stable ` const defaultValues = `# Default values for %s. @@ -211,7 +212,7 @@ spec: spec: containers: - name: {{ .Chart.Name }} - image: "{{ .Values.image.repository }}:{{ .Release.AppVersion }}" + image: "{{ .Values.image.repository }}:{{ .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http From f12c2a3111db0a4bae6d3f70e3fd393a2d8e9d19 Mon Sep 17 00:00:00 2001 From: Martin Hickey Date: Thu, 2 May 2019 10:00:41 +0100 Subject: [PATCH 6/7] Change the nginx app version to a set tag Want to avoid moving tags like using 'stable'. Therefore, specify the specifc nginx version/tag. Update from comment review: - https://github.com/helm/helm/pull/5662#discussion_r280122531 Signed-off-by: Martin Hickey --- pkg/chartutil/create.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pkg/chartutil/create.go b/pkg/chartutil/create.go index 40021ad42..8bf0fbf73 100644 --- a/pkg/chartutil/create.go +++ b/pkg/chartutil/create.go @@ -72,9 +72,8 @@ type: application version: 0.1.0 # This is the version number of the application being deployed. This version number should be -# incremented each time you make changes to the application. Depending on the application, -# 'stable' can signify the latest stable version of the application. -appVersion: stable +# incremented each time you make changes to the application. +appVersion: 1.16.0 ` const defaultValues = `# Default values for %s. From a12a396aabbd9fb2e67e165de44c00768c1769a0 Mon Sep 17 00:00:00 2001 From: Josh Dolitsky Date: Mon, 6 May 2019 16:15:34 -0500 Subject: [PATCH 7/7] Helm 3: registry login/logout (#5597) * login/logout placeholders Signed-off-by: Josh Dolitsky * use latest oras Signed-off-by: Josh Dolitsky * use docker auth system Signed-off-by: Josh Dolitsky * working login+push Signed-off-by: Josh Dolitsky * working on tests Signed-off-by: Josh Dolitsky * fix typo in htpasswd Signed-off-by: Josh Dolitsky * rename credsfile to config.json Signed-off-by: Josh Dolitsky * add flags for username/password Signed-off-by: Josh Dolitsky * disable logout test broken on linux Signed-off-by: Josh Dolitsky * upgrade to oras 0.4.0 Signed-off-by: Josh Dolitsky * re-enable logout test Signed-off-by: Josh Dolitsky * panic for uncaught errors Signed-off-by: Josh Dolitsky * move login/logout to new registry subcommand Signed-off-by: Josh Dolitsky --- Gopkg.lock | 124 ++++++++++++++++++++++++++++--- Gopkg.toml | 7 +- cmd/helm/chart.go | 8 +- cmd/helm/registry.go | 45 ++++++++++++ cmd/helm/registry_login.go | 134 ++++++++++++++++++++++++++++++++++ cmd/helm/registry_logout.go | 43 +++++++++++ cmd/helm/root.go | 22 +++++- pkg/action/registry_login.go | 38 ++++++++++ pkg/action/registry_logout.go | 38 ++++++++++ pkg/engine/funcs.go | 2 +- pkg/registry/authorizer.go | 28 +++++++ pkg/registry/cache.go | 2 +- pkg/registry/client.go | 41 +++++++++-- pkg/registry/client_test.go | 73 ++++++++++++++---- 14 files changed, 557 insertions(+), 48 deletions(-) create mode 100644 cmd/helm/registry.go create mode 100644 cmd/helm/registry_login.go create mode 100644 cmd/helm/registry_logout.go create mode 100644 pkg/action/registry_login.go create mode 100644 pkg/action/registry_logout.go create mode 100644 pkg/registry/authorizer.go diff --git a/Gopkg.lock b/Gopkg.lock index 7e4514968..4edbe1b21 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -91,6 +91,22 @@ revision = "b4f55832432b95a611cf1495272b5c8e24952a1a" version = "v1.13.0" +[[projects]] + digest = "1:f9ae348e1f793dcf9ed930ed47136a67343dbd6809c5c91391322267f4476892" + name = "github.com/Microsoft/go-winio" + packages = ["."] + pruneopts = "UT" + revision = "1a8911d1ed007260465c3bfbbc785ac6915a0bb8" + version = "v0.4.12" + +[[projects]] + branch = "master" + digest = "1:3721a10686511b80c052323423f0de17a8c06d417dbdd3b392b1578432a33aae" + name = "github.com/Nvveen/Gotty" + packages = ["."] + pruneopts = "UT" + revision = "cd527374f1e5bff4938207604a14f2e38a9cf512" + [[projects]] digest = "1:d1665c44bd5db19aaee18d1b6233c99b0b9a986e8bccb24ef54747547a48027f" name = "github.com/PuerkitoBio/purell" @@ -185,7 +201,7 @@ revision = "bf70f2a70fb1b1f36d90d671a72795984eab0fcb" [[projects]] - digest = "1:1872596d45cb52913fcdea90d468f3e57435959e9c0d99ccb316ee76de341313" + digest = "1:37f8940c4d3c41536ea882b1ca3498e403c04892dfc34bd0d670ed9eafccda9a" name = "github.com/containerd/containerd" packages = [ "content", @@ -198,8 +214,16 @@ "remotes/docker", ] pruneopts = "UT" - revision = "9b32062dc1f5a7c2564315c269b5059754f12b9d" - version = "v1.2.1" + revision = "894b81a4b802e4eb2a91d1ce216b8817763c29fb" + version = "v1.2.6" + +[[projects]] + branch = "master" + digest = "1:e48c63e818c67fbf3d7afe20bba33134ab1a5bf384847385384fd027652a5a96" + name = "github.com/containerd/continuity" + packages = ["pathdriver"] + pruneopts = "UT" + revision = "004b46473808b3e7a4a3049c20e4376c91eb966d" [[projects]] digest = "1:7cb4fdca4c251b3ef8027c90ea35f70c7b661a593b9eeae34753c65499098bb1" @@ -218,15 +242,17 @@ version = "v1.1.1" [[projects]] - digest = "1:8285cd51b86f5c3af447ad1db7c8572578a422cf9a50f1f07eea1d021151044f" + digest = "1:82158435e282da9b23bb1188487fe1c68b17a54ed9dcd557ab6204782ad3ff92" name = "github.com/deislabs/oras" packages = [ + "pkg/auth", + "pkg/auth/docker", "pkg/content", "pkg/oras", ] pruneopts = "UT" - revision = "e8a1fa6ff9a507b99eedd45745959e8c5b826d9f" - version = "v0.3.3" + revision = "9f7669048990b0d0c186985737e6a6c3bb3f7ecc" + version = "v0.4.0" [[projects]] digest = "1:76dc72490af7174349349838f2fe118996381b31ea83243812a97e5a0fd5ed55" @@ -237,7 +263,20 @@ version = "v3.2.0" [[projects]] - digest = "1:888aaacf886021e4a0fa6b09a61f1158063bd6c2e2ddefe14f3a7ccbc93ffe27" + digest = "1:f65090e4f60dcd4d2de69e8ebca022d59a8c6463a3a4c122e64cec91a83749ff" + name = "github.com/docker/cli" + packages = [ + "cli/config", + "cli/config/configfile", + "cli/config/credentials", + "opts", + ] + pruneopts = "UT" + revision = "c89750f836c57ce10386e71669e1b08a54c3caeb" + version = "v18.09.5" + +[[projects]] + digest = "1:feaf11ab67fe48ec2712bf9d44e2fb2d4ebdc5da8e5a47bd3ce05bae9f82825b" name = "github.com/docker/distribution" packages = [ ".", @@ -258,6 +297,7 @@ "registry/api/errcode", "registry/api/v2", "registry/auth", + "registry/auth/htpasswd", "registry/client", "registry/client/auth", "registry/client/auth/challenge", @@ -286,15 +326,61 @@ [[projects]] branch = "master" - digest = "1:8da8bb2b12c31c632e96ca6f15666a36c36cd390326b6c5e1c5e309cf4b5419a" + digest = "1:b5be0d9940d8fa3ff7df4949a8e8c47a7f93ea8251239ad074e1a6b0db55876a" name = "github.com/docker/docker" packages = [ + "api/types", + "api/types/blkiodev", + "api/types/container", + "api/types/filters", + "api/types/mount", + "api/types/network", + "api/types/registry", + "api/types/strslice", + "api/types/swarm", + "api/types/swarm/runtime", + "api/types/versions", + "errdefs", + "pkg/homedir", + "pkg/idtools", + "pkg/ioutils", + "pkg/jsonmessage", + "pkg/longpath", + "pkg/mount", + "pkg/stringid", + "pkg/system", + "pkg/tarsum", "pkg/term", "pkg/term/windows", + "registry", + "registry/resumable", ] pruneopts = "UT" revision = "2cb26cfe9cbf8a64c5046c74d65f4528b22e67f4" +[[projects]] + digest = "1:8866486038791fe65ea1abf660041423954b1f3fb99ea6a0ad8424422e943458" + name = "github.com/docker/docker-credential-helpers" + packages = [ + "client", + "credentials", + ] + pruneopts = "UT" + revision = "5241b46610f2491efdf9d1c85f1ddf5b02f6d962" + version = "v0.6.1" + +[[projects]] + digest = "1:811c86996b1ca46729bad2724d4499014c4b9effd05ef8c71b852aad90deb0ce" + name = "github.com/docker/go-connections" + packages = [ + "nat", + "sockets", + "tlsconfig", + ] + pruneopts = "UT" + revision = "7395e3f8aa162843a74ed6d48e79627d9792ac55" + version = "v0.4.0" + [[projects]] branch = "master" digest = "1:2b126e77be4ab4b92cdb3924c87894dd76bf365ba282f358a13133e848aa0059" @@ -711,6 +797,14 @@ revision = "d60099175f88c47cd379c4738d158884749ed235" version = "v1.0.1" +[[projects]] + digest = "1:38ee335aedf4626620f3cf8f605661e71abdcce7b40b38921962beb3980f0a20" + name = "github.com/opencontainers/runc" + packages = ["libcontainer/user"] + pruneopts = "UT" + revision = "baf6536d6259209c3edfa2b22237af82942d3dfa" + version = "v0.1.1" + [[projects]] branch = "master" digest = "1:3bf17a6e6eaa6ad24152148a631d18662f7212e21637c2699bff3369b7f00fa2" @@ -915,9 +1009,11 @@ [[projects]] branch = "master" - digest = "1:599ef9ff10026292c425292ab1d2bb1521cd671fe89a6034df07bf1411daa44b" + digest = "1:91e034b0c63a4c747c6e9dc8285f36dc5fe699a78d34de0a663895e52ff673dd" name = "golang.org/x/crypto" packages = [ + "bcrypt", + "blowfish", "cast5", "ed25519", "ed25519/internal/edwards25519", @@ -938,7 +1034,7 @@ [[projects]] branch = "master" - digest = "1:647b0128e9a9886335bfb6c9a1fc97758b7f846ec42f222933f6fee6730c96e2" + digest = "1:80c256dfc14840e13293d6404b7774e497187bd15a53f943f99bfaef4bbb2e42" name = "golang.org/x/net" packages = [ "bpf", @@ -950,9 +1046,11 @@ "idna", "internal/iana", "internal/socket", + "internal/socks", "internal/timeseries", "ipv4", "ipv6", + "proxy", "publicsuffix", "trace", ] @@ -1678,12 +1776,15 @@ "github.com/asaskevich/govalidator", "github.com/containerd/containerd/reference", "github.com/containerd/containerd/remotes", - "github.com/containerd/containerd/remotes/docker", + "github.com/deislabs/oras/pkg/auth", + "github.com/deislabs/oras/pkg/auth/docker", "github.com/deislabs/oras/pkg/content", "github.com/deislabs/oras/pkg/oras", "github.com/docker/distribution/configuration", "github.com/docker/distribution/registry", + "github.com/docker/distribution/registry/auth/htpasswd", "github.com/docker/distribution/registry/storage/driver/inmemory", + "github.com/docker/docker/pkg/term", "github.com/docker/go-units", "github.com/evanphx/json-patch", "github.com/ghodss/yaml", @@ -1701,6 +1802,7 @@ "github.com/stretchr/testify/assert", "github.com/stretchr/testify/suite", "github.com/xeipuuv/gojsonschema", + "golang.org/x/crypto/bcrypt", "golang.org/x/crypto/openpgp", "golang.org/x/crypto/openpgp/clearsign", "golang.org/x/crypto/openpgp/errors", diff --git a/Gopkg.toml b/Gopkg.toml index fe26c0010..94736bf3c 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -44,7 +44,7 @@ [[constraint]] name = "github.com/deislabs/oras" - version = "~0.3.3" + version = "0.4.0" [[constraint]] name = "github.com/docker/go-units" @@ -76,11 +76,6 @@ branch = "master" source = "https://github.com/dmcgowan/letsencrypt.git" -# https://github.com/bugsnag/bugsnag-go/issues/96 -[[override]] - name = "github.com/bugsnag/bugsnag-go" - version = "=1.3.2" - # gopkg.in is broken # # https://github.com/golang/dep/issues/1760 diff --git a/cmd/helm/chart.go b/cmd/helm/chart.go index dd4af9273..293ab3635 100644 --- a/cmd/helm/chart.go +++ b/cmd/helm/chart.go @@ -18,14 +18,13 @@ package main import ( "io" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" "helm.sh/helm/pkg/action" ) const chartHelp = ` -This command consists of multiple subcommands to interact with charts and registries. +This command consists of multiple subcommands to work with the chart cache. It can be used to push, pull, tag, list, or remove Helm charts. Example usage: @@ -48,8 +47,3 @@ func newChartCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { ) return cmd } - -// TODO remove once WARN lines removed from oras or containerd -func init() { - logrus.SetLevel(logrus.ErrorLevel) -} diff --git a/cmd/helm/registry.go b/cmd/helm/registry.go new file mode 100644 index 000000000..1ed885c83 --- /dev/null +++ b/cmd/helm/registry.go @@ -0,0 +1,45 @@ +/* +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 main + +import ( + "io" + + "github.com/spf13/cobra" + + "helm.sh/helm/pkg/action" +) + +const registryHelp = ` +This command consists of multiple subcommands to interact with registries. + +It can be used to login to or logout from a registry. +Example usage: + $ helm registry login [URL] +` + +func newRegistryCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { + cmd := &cobra.Command{ + Use: "registry", + Short: "login to or logout from a registry", + Long: registryHelp, + } + cmd.AddCommand( + newRegistryLoginCmd(cfg, out), + newRegistryLogoutCmd(cfg, out), + ) + return cmd +} diff --git a/cmd/helm/registry_login.go b/cmd/helm/registry_login.go new file mode 100644 index 000000000..d40b1dd44 --- /dev/null +++ b/cmd/helm/registry_login.go @@ -0,0 +1,134 @@ +/* +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 main + +import ( + "bufio" + "errors" + "fmt" + "io" + "io/ioutil" + "os" + "strings" + + "github.com/docker/docker/pkg/term" + "github.com/spf13/cobra" + + "helm.sh/helm/cmd/helm/require" + "helm.sh/helm/pkg/action" +) + +const registryLoginDesc = ` +Authenticate to a remote registry. +` + +func newRegistryLoginCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { + var usernameOpt, passwordOpt string + var passwordFromStdinOpt bool + + cmd := &cobra.Command{ + Use: "login [host]", + Short: "login to a registry", + Long: registryLoginDesc, + Args: require.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + hostname := args[0] + + username, password, err := getUsernamePassword(usernameOpt, passwordOpt, passwordFromStdinOpt) + if err != nil { + return err + } + + return action.NewRegistryLogin(cfg).Run(out, hostname, username, password) + }, + } + + f := cmd.Flags() + f.StringVarP(&usernameOpt, "username", "u", "", "registry username") + f.StringVarP(&passwordOpt, "password", "p", "", "registry password or identity token") + f.BoolVarP(&passwordFromStdinOpt, "password-stdin", "", false, "read password or identity token from stdin") + + return cmd +} + +// Adapted from https://github.com/deislabs/oras +func getUsernamePassword(usernameOpt string, passwordOpt string, passwordFromStdinOpt bool) (string, string, error) { + var err error + username := usernameOpt + password := passwordOpt + + if passwordFromStdinOpt { + passwordFromStdin, err := ioutil.ReadAll(os.Stdin) + if err != nil { + return "", "", err + } + password = strings.TrimSuffix(string(passwordFromStdin), "\n") + password = strings.TrimSuffix(password, "\r") + } else if password == "" { + if username == "" { + username, err = readLine("Username: ", false) + if err != nil { + return "", "", err + } + username = strings.TrimSpace(username) + } + if username == "" { + password, err = readLine("Token: ", true) + if err != nil { + return "", "", err + } else if password == "" { + return "", "", errors.New("token required") + } + } else { + password, err = readLine("Password: ", true) + if err != nil { + return "", "", err + } else if password == "" { + return "", "", errors.New("password required") + } + } + } else { + fmt.Fprintln(os.Stderr, "WARNING! Using --password via the CLI is insecure. Use --password-stdin.") + } + + return username, password, nil +} + +// Copied/adapted from https://github.com/deislabs/oras +func readLine(prompt string, silent bool) (string, error) { + fmt.Print(prompt) + if silent { + fd := os.Stdin.Fd() + state, err := term.SaveState(fd) + if err != nil { + return "", err + } + term.DisableEcho(fd, state) + defer term.RestoreTerminal(fd, state) + } + + reader := bufio.NewReader(os.Stdin) + line, _, err := reader.ReadLine() + if err != nil { + return "", err + } + if silent { + fmt.Println() + } + + return string(line), nil +} diff --git a/cmd/helm/registry_logout.go b/cmd/helm/registry_logout.go new file mode 100644 index 000000000..099f4ee7b --- /dev/null +++ b/cmd/helm/registry_logout.go @@ -0,0 +1,43 @@ +/* +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 main + +import ( + "io" + + "github.com/spf13/cobra" + + "helm.sh/helm/cmd/helm/require" + "helm.sh/helm/pkg/action" +) + +const registryLogoutDesc = ` +Remove credentials stored for a remote registry. +` + +func newRegistryLogoutCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { + return &cobra.Command{ + Use: "logout [host]", + Short: "logout from a registry", + Long: registryLogoutDesc, + Args: require.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + hostname := args[0] + return action.NewRegistryLogout(cfg).Run(out, hostname) + }, + } +} diff --git a/cmd/helm/root.go b/cmd/helm/root.go index 5a6f43d6e..f2124aba4 100644 --- a/cmd/helm/root.go +++ b/cmd/helm/root.go @@ -17,9 +17,11 @@ limitations under the License. package main // import "helm.sh/helm/cmd/helm" import ( + "context" "io" + "path/filepath" - "github.com/containerd/containerd/remotes/docker" + auth "github.com/deislabs/oras/pkg/auth/docker" "github.com/spf13/cobra" "helm.sh/helm/cmd/helm/require" @@ -68,10 +70,23 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string // Add the registry client based on settings // TODO: Move this elsewhere (first, settings.Init() must move) + // TODO: handle errors, dont panic + credentialsFile := filepath.Join(settings.Home.Registry(), registry.CredentialsFileBasename) + client, err := auth.NewClient(credentialsFile) + if err != nil { + panic(err) + } + resolver, err := client.Resolver(context.Background()) + if err != nil { + panic(err) + } actionConfig.RegistryClient = registry.NewClient(®istry.ClientOptions{ Out: out, + Authorizer: registry.Authorizer{ + Client: client, + }, Resolver: registry.Resolver{ - Resolver: docker.NewResolver(docker.ResolverOptions{}), + Resolver: resolver, }, CacheRootDir: settings.Home.Registry(), }) @@ -87,6 +102,9 @@ func newRootCmd(actionConfig *action.Configuration, out io.Writer, args []string newRepoCmd(out), newSearchCmd(out), newVerifyCmd(out), + + // registry/chart cache commands + newRegistryCmd(actionConfig, out), newChartCmd(actionConfig, out), // release commands diff --git a/pkg/action/registry_login.go b/pkg/action/registry_login.go new file mode 100644 index 000000000..2192d49e8 --- /dev/null +++ b/pkg/action/registry_login.go @@ -0,0 +1,38 @@ +/* +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 action + +import ( + "io" +) + +// RegistryLogin performs a registry login operation. +type RegistryLogin struct { + cfg *Configuration +} + +// NewRegistryLogin creates a new RegistryLogin object with the given configuration. +func NewRegistryLogin(cfg *Configuration) *RegistryLogin { + return &RegistryLogin{ + cfg: cfg, + } +} + +// Run executes the registry login operation +func (a *RegistryLogin) Run(out io.Writer, hostname string, username string, password string) error { + return a.cfg.RegistryClient.Login(hostname, username, password) +} diff --git a/pkg/action/registry_logout.go b/pkg/action/registry_logout.go new file mode 100644 index 000000000..69add4163 --- /dev/null +++ b/pkg/action/registry_logout.go @@ -0,0 +1,38 @@ +/* +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 action + +import ( + "io" +) + +// RegistryLogout performs a registry login operation. +type RegistryLogout struct { + cfg *Configuration +} + +// NewRegistryLogout creates a new RegistryLogout object with the given configuration. +func NewRegistryLogout(cfg *Configuration) *RegistryLogout { + return &RegistryLogout{ + cfg: cfg, + } +} + +// Run executes the registry logout operation +func (a *RegistryLogout) Run(out io.Writer, hostname string) error { + return a.cfg.RegistryClient.Logout(hostname) +} diff --git a/pkg/engine/funcs.go b/pkg/engine/funcs.go index 2b927872f..936f91d3c 100644 --- a/pkg/engine/funcs.go +++ b/pkg/engine/funcs.go @@ -25,7 +25,7 @@ import ( "github.com/BurntSushi/toml" "github.com/Masterminds/sprig" "github.com/pkg/errors" - "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v2" ) // funcMap returns a mapping of all of the functions that Engine has. diff --git a/pkg/registry/authorizer.go b/pkg/registry/authorizer.go new file mode 100644 index 000000000..c601b59d4 --- /dev/null +++ b/pkg/registry/authorizer.go @@ -0,0 +1,28 @@ +/* +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 "helm.sh/helm/pkg/registry" + +import ( + "github.com/deislabs/oras/pkg/auth" +) + +type ( + // Authorizer handles registry auth operations + Authorizer struct { + auth.Client + } +) diff --git a/pkg/registry/cache.go b/pkg/registry/cache.go index 39dec1467..ccedd1e54 100644 --- a/pkg/registry/cache.go +++ b/pkg/registry/cache.go @@ -29,7 +29,7 @@ import ( "time" orascontent "github.com/deislabs/oras/pkg/content" - "github.com/docker/go-units" + units "github.com/docker/go-units" checksum "github.com/opencontainers/go-digest" ocispec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" diff --git a/pkg/registry/client.go b/pkg/registry/client.go index a2244f816..588961d02 100644 --- a/pkg/registry/client.go +++ b/pkg/registry/client.go @@ -28,27 +28,34 @@ import ( "helm.sh/helm/pkg/chart" ) +const ( + CredentialsFileBasename = "config.json" +) + type ( // ClientOptions is used to construct a new client ClientOptions struct { Out io.Writer + Authorizer Authorizer Resolver Resolver CacheRootDir string } // Client works with OCI-compliant registries and local Helm chart cache Client struct { - out io.Writer - resolver Resolver - cache *filesystemCache // TODO: something more robust + out io.Writer + authorizer Authorizer + resolver Resolver + cache *filesystemCache // TODO: something more robust } ) // NewClient returns a new registry client with config func NewClient(options *ClientOptions) *Client { return &Client{ - out: options.Out, - resolver: options.Resolver, + out: options.Out, + resolver: options.Resolver, + authorizer: options.Authorizer, cache: &filesystemCache{ out: options.Out, rootDir: options.CacheRootDir, @@ -57,6 +64,26 @@ func NewClient(options *ClientOptions) *Client { } } +// Login logs into a registry +func (c *Client) Login(hostname string, username string, password string) error { + err := c.authorizer.Login(context.Background(), hostname, username, password) + if err != nil { + return err + } + fmt.Fprint(c.out, "Login succeeded\n") + return nil +} + +// Logout logs out of a registry +func (c *Client) Logout(hostname string) error { + err := c.authorizer.Logout(context.Background(), hostname) + if err != nil { + return err + } + fmt.Fprint(c.out, "Logout succeeded\n") + return nil +} + // PushChart uploads a chart to a registry func (c *Client) PushChart(ref *Reference) error { c.setDefaultTag(ref) @@ -65,7 +92,7 @@ func (c *Client) PushChart(ref *Reference) error { if err != nil { return err } - err = oras.Push(context.Background(), c.resolver, ref.String(), c.cache.store, layers) + _, err = oras.Push(context.Background(), c.resolver, ref.String(), c.cache.store, layers) if err != nil { return err } @@ -82,7 +109,7 @@ func (c *Client) PushChart(ref *Reference) error { func (c *Client) PullChart(ref *Reference) error { c.setDefaultTag(ref) fmt.Fprintf(c.out, "%s: Pulling from %s\n", ref.Tag, ref.Repo) - layers, err := oras.Pull(context.Background(), c.resolver, ref.String(), c.cache.store, KnownMediaTypes()...) + _, layers, err := oras.Pull(context.Background(), c.resolver, ref.String(), c.cache.store, oras.WithAllowedMediaTypes(KnownMediaTypes())) if err != nil { return err } diff --git a/pkg/registry/client_test.go b/pkg/registry/client_test.go index fd6285c15..5b2f06eb5 100644 --- a/pkg/registry/client_test.go +++ b/pkg/registry/client_test.go @@ -21,22 +21,29 @@ import ( "context" "fmt" "io" + "io/ioutil" "net" "os" + "path/filepath" "testing" "time" - "github.com/containerd/containerd/remotes/docker" + auth "github.com/deislabs/oras/pkg/auth/docker" "github.com/docker/distribution/configuration" "github.com/docker/distribution/registry" + _ "github.com/docker/distribution/registry/auth/htpasswd" _ "github.com/docker/distribution/registry/storage/driver/inmemory" "github.com/stretchr/testify/suite" + "golang.org/x/crypto/bcrypt" "helm.sh/helm/pkg/chart" ) var ( - testCacheRootDir = "helm-registry-test" + testCacheRootDir = "helm-registry-test" + testHtpasswdFileBasename = "authtest.htpasswd" + testUsername = "myuser" + testPassword = "mypass" ) type RegistryClientTestSuite struct { @@ -49,28 +56,52 @@ type RegistryClientTestSuite struct { func (suite *RegistryClientTestSuite) SetupSuite() { suite.CacheRootDir = testCacheRootDir + os.RemoveAll(suite.CacheRootDir) + os.Mkdir(suite.CacheRootDir, 0700) - // Init test client var out bytes.Buffer suite.Out = &out + credentialsFile := filepath.Join(suite.CacheRootDir, CredentialsFileBasename) + + client, err := auth.NewClient(credentialsFile) + suite.Nil(err, "no error creating auth client") + + resolver, err := client.Resolver(context.Background()) + suite.Nil(err, "no error creating resolver") + + // Init test client suite.RegistryClient = NewClient(&ClientOptions{ Out: suite.Out, + Authorizer: Authorizer{ + Client: client, + }, Resolver: Resolver{ - Resolver: docker.NewResolver(docker.ResolverOptions{}), + Resolver: resolver, }, CacheRootDir: suite.CacheRootDir, }) + // create htpasswd file (w BCrypt, which is required) + pwBytes, err := bcrypt.GenerateFromPassword([]byte(testPassword), bcrypt.DefaultCost) + suite.Nil(err, "no error generating bcrypt password for test htpasswd file") + htpasswdPath := filepath.Join(suite.CacheRootDir, testHtpasswdFileBasename) + err = ioutil.WriteFile(htpasswdPath, []byte(fmt.Sprintf("%s:%s\n", testUsername, string(pwBytes))), 0644) + suite.Nil(err, "no error creating test htpasswd file") + // Registry config config := &configuration.Configuration{} port, err := getFreePort() - if err != nil { - suite.Nil(err, "no error finding free port for test registry") - } + suite.Nil(err, "no error finding free port for test registry") suite.DockerRegistryHost = fmt.Sprintf("localhost:%d", port) config.HTTP.Addr = fmt.Sprintf(":%d", port) config.HTTP.DrainTimeout = time.Duration(10) * time.Second config.Storage = map[string]configuration.Parameters{"inmemory": map[string]interface{}{}} + config.Auth = configuration.Auth{ + "htpasswd": configuration.Parameters{ + "realm": "localhost", + "path": htpasswdPath, + }, + } dockerRegistry, err := registry.NewRegistry(context.Background(), config) suite.Nil(err, "no error creating test registry") @@ -82,7 +113,15 @@ func (suite *RegistryClientTestSuite) TearDownSuite() { os.RemoveAll(suite.CacheRootDir) } -func (suite *RegistryClientTestSuite) Test_0_SaveChart() { +func (suite *RegistryClientTestSuite) Test_0_Login() { + err := suite.RegistryClient.Login(suite.DockerRegistryHost, "badverybad", "ohsobad") + suite.NotNil(err, "error logging into registry with bad credentials") + + err = suite.RegistryClient.Login(suite.DockerRegistryHost, testUsername, testPassword) + suite.Nil(err, "no error logging into registry with good credentials") +} + +func (suite *RegistryClientTestSuite) Test_1_SaveChart() { ref, err := ParseReference(fmt.Sprintf("%s/testrepo/testchart:1.2.3", suite.DockerRegistryHost)) suite.Nil(err) @@ -101,7 +140,7 @@ func (suite *RegistryClientTestSuite) Test_0_SaveChart() { suite.Nil(err) } -func (suite *RegistryClientTestSuite) Test_1_LoadChart() { +func (suite *RegistryClientTestSuite) Test_2_LoadChart() { // non-existent ref ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) @@ -118,7 +157,7 @@ func (suite *RegistryClientTestSuite) Test_1_LoadChart() { suite.Equal("1.2.3", ch.Metadata.Version) } -func (suite *RegistryClientTestSuite) Test_2_PushChart() { +func (suite *RegistryClientTestSuite) Test_3_PushChart() { // non-existent ref ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) @@ -133,7 +172,7 @@ func (suite *RegistryClientTestSuite) Test_2_PushChart() { suite.Nil(err) } -func (suite *RegistryClientTestSuite) Test_3_PullChart() { +func (suite *RegistryClientTestSuite) Test_4_PullChart() { // non-existent ref ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) @@ -148,12 +187,12 @@ func (suite *RegistryClientTestSuite) Test_3_PullChart() { suite.Nil(err) } -func (suite *RegistryClientTestSuite) Test_4_PrintChartTable() { +func (suite *RegistryClientTestSuite) Test_5_PrintChartTable() { err := suite.RegistryClient.PrintChartTable() suite.Nil(err) } -func (suite *RegistryClientTestSuite) Test_5_RemoveChart() { +func (suite *RegistryClientTestSuite) Test_6_RemoveChart() { // non-existent ref ref, err := ParseReference(fmt.Sprintf("%s/testrepo/whodis:9.9.9", suite.DockerRegistryHost)) @@ -168,6 +207,14 @@ func (suite *RegistryClientTestSuite) Test_5_RemoveChart() { suite.Nil(err) } +func (suite *RegistryClientTestSuite) Test_7_Logout() { + err := suite.RegistryClient.Logout("this-host-aint-real:5000") + suite.NotNil(err, "error logging out of registry that has no entry") + + err = suite.RegistryClient.Logout(suite.DockerRegistryHost) + suite.Nil(err, "no error logging out of registry") +} + func TestRegistryClientTestSuite(t *testing.T) { suite.Run(t, new(RegistryClientTestSuite)) }