Merge branch 'helm:main' into main

pull/12339/head
Ithrael 2 years ago committed by GitHub
commit e31db0b7a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,18 @@
name: Publish Release Assets to Asset Transparency Log
on:
release:
types: [published, created, edited, released]
jobs:
github_release_asset_transparency_log_publish_job:
runs-on: ubuntu-latest
name: Publish GitHub release asset digests to https://beta-asset.transparencylog.net
steps:
- name: Gather URLs from GitHub release and publish
id: asset-transparency
uses: transparencylog/github-releases-asset-transparency-verify-action@v10
- name: List verified and published URLs
run: echo "Verified URLs ${{ steps.asset-transparency.outputs.verified }}"
- name: List failed URLs
run: echo "Failed URLs ${{ steps.asset-transparency.outputs.failed }}"

@ -15,7 +15,7 @@ jobs:
- name: Checkout source code - name: Checkout source code
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # pin@4.0.1 uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with: with:
go-version: '1.20' go-version: '1.20'
- name: Install golangci-lint - name: Install golangci-lint

@ -39,7 +39,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@0ba4244466797eb048eb91a6cd43d5c03ca8bd05 # pinv2.21.2 uses: github/codeql-action/init@a09933a12a80f87b87005513f0abb1494c27a716 # pinv2.21.4
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file. # If you wish to specify custom queries, you can do so here or in a config file.
@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below) # If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild - name: Autobuild
uses: github/codeql-action/autobuild@0ba4244466797eb048eb91a6cd43d5c03ca8bd05 # pinv2.21.2 uses: github/codeql-action/autobuild@a09933a12a80f87b87005513f0abb1494c27a716 # pinv2.21.4
# Command-line programs to run using the OS shell. # Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl # 📚 https://git.io/JvXDl
@ -64,4 +64,4 @@ jobs:
# make release # make release
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@0ba4244466797eb048eb91a6cd43d5c03ca8bd05 # pinv2.21.2 uses: github/codeql-action/analyze@a09933a12a80f87b87005513f0abb1494c27a716 # pinv2.21.4

@ -21,7 +21,7 @@ jobs:
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # pin@4.0.1 uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with: with:
go-version: '1.20' go-version: '1.20'
@ -52,7 +52,7 @@ jobs:
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3 uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # pin@v3.5.3
- name: Setup Go - name: Setup Go
uses: actions/setup-go@fac708d6674e30b6ba41289acaab6d4b75aa0753 # pin@4.0.1 uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # pin@4.1.0
with: with:
go-version: '1.20' go-version: '1.20'

@ -11,6 +11,7 @@ triage:
- yxxhero - yxxhero
- zonggen - zonggen
- gjenkins8 - gjenkins8
- z4ce
emeritus: emeritus:
- adamreese - adamreese
- bacongobbler - bacongobbler

@ -30,7 +30,6 @@ Think of it like apt/yum/homebrew for Kubernetes.
## Install ## Install
Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest). Binary downloads of the Helm client can be found on [the Releases page](https://github.com/helm/helm/releases/latest).
Unpack the `helm` binary and add it to your PATH and you are good to go! Unpack the `helm` binary and add it to your PATH and you are good to go!
@ -68,6 +67,10 @@ You can reach the Helm community and developers via the following channels:
- [Helm Mailing List](https://lists.cncf.io/g/cncf-helm) - [Helm Mailing List](https://lists.cncf.io/g/cncf-helm)
- Developer Call: Thursdays at 9:30-10:00 Pacific ([meeting details](https://github.com/helm/community/blob/master/communication.md#meetings)) - Developer Call: Thursdays at 9:30-10:00 Pacific ([meeting details](https://github.com/helm/community/blob/master/communication.md#meetings))
### Contribution
If you're interested in contributing, please refer to the [Contributing Guide](CONTRIBUTING.md) **before submitting a pull request**.
### Code of conduct ### Code of conduct
Participation in the Helm community is governed by the [Code of Conduct](code-of-conduct.md). Participation in the Helm community is governed by the [Code of Conduct](code-of-conduct.md).

@ -189,6 +189,7 @@ func addInstallFlags(cmd *cobra.Command, f *pflag.FlagSet, client *action.Instal
f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used") f.BoolVar(&client.Atomic, "atomic", false, "if set, the installation process deletes the installation on failure. The --wait flag will be set automatically if --atomic is used")
f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed. By default, CRDs are installed if not already present")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be divided by comma.")
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")
addValueOptionsFlags(f, valueOpts) addValueOptionsFlags(f, valueOpts)
addChartPathOptionsFlags(f, &client.ChartPathOptions) addChartPathOptionsFlags(f, &client.ChartPathOptions)

@ -59,9 +59,9 @@ func newReleaseTestCmd(cfg *action.Configuration, out io.Writer) *cobra.Command
notName := regexp.MustCompile(`^!\s?name=`) notName := regexp.MustCompile(`^!\s?name=`)
for _, f := range filter { for _, f := range filter {
if strings.HasPrefix(f, "name=") { if strings.HasPrefix(f, "name=") {
client.Filters["name"] = append(client.Filters["name"], strings.TrimPrefix(f, "name=")) client.Filters[action.IncludeNameFilter] = append(client.Filters[action.IncludeNameFilter], strings.TrimPrefix(f, "name="))
} else if notName.MatchString(f) { } else if notName.MatchString(f) {
client.Filters["!name"] = append(client.Filters["!name"], notName.ReplaceAllLiteralString(f, "")) client.Filters[action.ExcludeNameFilter] = append(client.Filters[action.ExcludeNameFilter], notName.ReplaceAllLiteralString(f, ""))
} }
} }
rel, runErr := client.Run(args[0]) rel, runErr := client.Run(args[0])

@ -64,6 +64,12 @@ func TestRollbackCmd(t *testing.T) {
cmd: "rollback funny-honey", cmd: "rollback funny-honey",
golden: "output/rollback-no-revision.txt", golden: "output/rollback-no-revision.txt",
rels: rels, rels: rels,
}, {
name: "rollback a release with non-existent version",
cmd: "rollback funny-honey 3",
golden: "output/rollback-non-existent-version.txt",
rels: rels,
wantError: true,
}, { }, {
name: "rollback a release without release name", name: "rollback a release without release name",
cmd: "rollback", cmd: "rollback",

@ -0,0 +1 @@
Error: release has no 3 version

@ -74,6 +74,7 @@ func newUninstallCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f := cmd.Flags() f := cmd.Flags()
f.BoolVar(&client.DryRun, "dry-run", false, "simulate a uninstall") 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.DisableHooks, "no-hooks", false, "prevent hooks from running during uninstallation")
f.BoolVar(&client.IgnoreNotFound, "ignore-not-found", false, `Treat "release not found" as a successful uninstall`)
f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history") f.BoolVar(&client.KeepHistory, "keep-history", false, "remove all associated resources and mark the release as deleted, but retain the release history")
f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all the resources are deleted before returning. It will wait for as long as --timeout")
f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.") f.StringVar(&client.DeletionPropagation, "cascade", "background", "Must be \"background\", \"orphan\", or \"foreground\". Selects the deletion cascading strategy for the dependents. Defaults to background.")

@ -65,6 +65,13 @@ last (right-most) set specified. For example, if both 'bar' and 'newbar' values
set for a key called 'foo', the 'newbar' value would take precedence: set for a key called 'foo', the 'newbar' value would take precedence:
$ helm upgrade --set foo=bar --set foo=newbar redis ./redis $ helm upgrade --set foo=bar --set foo=newbar redis ./redis
You can update the values for an existing release with this command as well via the
'--reuse-values' flag. The 'RELEASE' and 'CHART' arguments should be set to the original
parameters, and existing values will be merged with any values set via '--values'/'-f'
or '--set' flags. Priority is given to new values.
$ helm upgrade --reuse-values --set foo=bar --set foo=newbar redis ./redis
` `
func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
@ -133,6 +140,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
instClient.SubNotes = client.SubNotes instClient.SubNotes = client.SubNotes
instClient.Description = client.Description instClient.Description = client.Description
instClient.DependencyUpdate = client.DependencyUpdate instClient.DependencyUpdate = client.DependencyUpdate
instClient.Labels = client.Labels
instClient.EnableDNS = client.EnableDNS instClient.EnableDNS = client.EnableDNS
rel, err := runInstall(args, instClient, valueOpts, out) rel, err := runInstall(args, instClient, valueOpts, out)
@ -250,6 +258,7 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit") f.IntVar(&client.MaxHistory, "history-max", settings.MaxHistory, "limit the maximum number of revisions saved per release. Use 0 for no limit")
f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails") f.BoolVar(&client.CleanupOnFail, "cleanup-on-fail", false, "allow deletion of new resources created in this upgrade when upgrade fails")
f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent") f.BoolVar(&client.SubNotes, "render-subchart-notes", false, "if set, render subchart notes along with the parent")
f.StringToStringVarP(&client.Labels, "labels", "l", nil, "Labels that would be added to release metadata. Should be separated by comma. Original release labels will be merged with upgrade labels. You can unset label using null.")
f.StringVar(&client.Description, "description", "", "add a custom description") f.StringVar(&client.Description, "description", "", "add a custom description")
f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart") f.BoolVar(&client.DependencyUpdate, "dependency-update", false, "update dependencies if they are missing before installing the chart")
f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates") f.BoolVar(&client.EnableDNS, "enable-dns", false, "enable DNS lookups when rendering templates")

@ -20,6 +20,7 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"reflect"
"strings" "strings"
"testing" "testing"
@ -430,3 +431,31 @@ func TestUpgradeFileCompletion(t *testing.T) {
checkFileCompletion(t, "upgrade myrelease", true) checkFileCompletion(t, "upgrade myrelease", true)
checkFileCompletion(t, "upgrade myrelease repo/chart", false) checkFileCompletion(t, "upgrade myrelease repo/chart", false)
} }
func TestUpgradeInstallWithLabels(t *testing.T) {
releaseName := "funny-bunny-labels"
_, _, chartPath := prepareMockRelease(releaseName, t)
defer resetEnv()()
store := storageFixture()
expectedLabels := map[string]string{
"key1": "val1",
"key2": "val2",
}
cmd := fmt.Sprintf("upgrade %s --install --labels key1=val1,key2=val2 '%s'", releaseName, chartPath)
_, _, err := executeActionCommandC(store, cmd)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
updatedRel, err := store.Get(releaseName, 1)
if err != nil {
t.Errorf("unexpected error, got '%v'", err)
}
if !reflect.DeepEqual(updatedRel.Labels, expectedLabels) {
t.Errorf("Expected {%v}, got {%v}", expectedLabels, updatedRel.Labels)
}
}

@ -3,14 +3,14 @@ module helm.sh/helm/v3
go 1.19 go 1.19
require ( require (
github.com/BurntSushi/toml v1.2.1 github.com/BurntSushi/toml v1.3.2
github.com/DATA-DOG/go-sqlmock v1.5.0 github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/Masterminds/semver/v3 v3.2.1 github.com/Masterminds/semver/v3 v3.2.1
github.com/Masterminds/sprig/v3 v3.2.3 github.com/Masterminds/sprig/v3 v3.2.3
github.com/Masterminds/squirrel v1.5.4 github.com/Masterminds/squirrel v1.5.4
github.com/Masterminds/vcs v1.13.3 github.com/Masterminds/vcs v1.13.3
github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535 github.com/asaskevich/govalidator v0.0.0-20200428143746-21a406dcc535
github.com/containerd/containerd v1.7.0 github.com/containerd/containerd v1.7.3
github.com/cyphar/filepath-securejoin v0.2.3 github.com/cyphar/filepath-securejoin v0.2.3
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2
github.com/evanphx/json-patch v5.6.0+incompatible github.com/evanphx/json-patch v5.6.0+incompatible
@ -23,11 +23,11 @@ require (
github.com/lib/pq v1.10.9 github.com/lib/pq v1.10.9
github.com/mattn/go-shellwords v1.0.12 github.com/mattn/go-shellwords v1.0.12
github.com/mitchellh/copystructure v1.2.0 github.com/mitchellh/copystructure v1.2.0
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 github.com/moby/term v0.5.0
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b github.com/opencontainers/image-spec v1.1.0-rc4
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/rubenv/sql-migrate v1.5.1 github.com/rubenv/sql-migrate v1.5.2
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
github.com/spf13/cobra v1.7.0 github.com/spf13/cobra v1.7.0
github.com/spf13/pflag v1.0.5 github.com/spf13/pflag v1.0.5
@ -63,7 +63,7 @@ require (
github.com/chai2010/gettext-go v1.0.2 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v23.0.1+incompatible // indirect github.com/docker/cli v23.0.3+incompatible // indirect
github.com/docker/distribution v2.8.2+incompatible // indirect github.com/docker/distribution v2.8.2+incompatible // indirect
github.com/docker/docker v23.0.3+incompatible // indirect github.com/docker/docker v23.0.3+incompatible // indirect
github.com/docker/docker-credential-helpers v0.7.0 // indirect github.com/docker/docker-credential-helpers v0.7.0 // indirect
@ -149,7 +149,7 @@ require (
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect google.golang.org/genproto v0.0.0-20230306155012-7f2fa6fef1f4 // indirect
google.golang.org/grpc v1.53.0 // indirect google.golang.org/grpc v1.53.0 // indirect
google.golang.org/protobuf v1.28.1 // indirect google.golang.org/protobuf v1.29.1 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

@ -36,8 +36,8 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230106234847-43070de90fa1/go.mod h
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8=
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
@ -54,8 +54,8 @@ 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/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 h1:IIA2aBdXvfbIM+yl/eTnL4hb1XwdpvuQLglAix1gweE=
github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8= github.com/Masterminds/vcs v1.13.3/go.mod h1:TiE7xuEjl1N4j016moRd6vezp6e6Lz23gypeXfzXeW8=
github.com/Microsoft/go-winio v0.6.0 h1:slsWYD/zyx7lCXoZVlvQrj0hPTM1HI4+v1sIda2yDvg= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/hcsshim v0.10.0-rc.7 h1:HBytQPxcv8Oy4244zbQbe6hnOnx544eL5QPUqhJldz8= github.com/Microsoft/hcsshim v0.10.0-rc.8 h1:YSZVvlIIDD1UxQpJp0h+dnpLUw+TrY0cx8obKsp3bek=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
@ -92,9 +92,9 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM=
github.com/containerd/containerd v1.7.0 h1:G/ZQr3gMZs6ZT0qPUZ15znx5QSdQdASW11nXTLTM2Pg= github.com/containerd/containerd v1.7.3 h1:cKwYKkP1eTj54bP3wCdXXBymmKRQMrWjkLSWZZJDa8o=
github.com/containerd/containerd v1.7.0/go.mod h1:QfR7Efgb/6X2BDpTPJRvPTYDE9rsF0FsXX9J8sIs/sc= github.com/containerd/containerd v1.7.3/go.mod h1:32FOM4/O0RkNg7AjQj3hDzN9cUGtu+HMvaKUNiqCZB8=
github.com/containerd/continuity v0.3.0 h1:nisirsYROK15TAMVukJOUyGJjz4BNQJBVsNvAXZJ/eg= github.com/containerd/continuity v0.4.1 h1:wQnVrjIyQ8vhU2sgOiL5T07jo+ouqc2bnKsv5/EqGhU=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -106,8 +106,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2 h1:aBfCb7iqHmDEIp6fBvC/hQUddQfg+3qdYjwzaiP9Hnc=
github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI= github.com/distribution/distribution/v3 v3.0.0-20221208165359-362910506bc2/go.mod h1:WHNsWjnIn2V1LYOrME7e8KxSeKunYHsxEm4am0BUtcI=
github.com/docker/cli v23.0.1+incompatible h1:LRyWITpGzl2C9e9uGxzisptnxAn1zfZKXy13Ul2Q5oM= github.com/docker/cli v23.0.3+incompatible h1:Zcse1DuDqBdgI7OQDV8Go7b83xLgfhW1eza4HfEdxpY=
github.com/docker/cli v23.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/cli v23.0.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho= github.com/docker/docker v23.0.3+incompatible h1:9GhVsShNWz1hO//9BNg/dpMnZW25KydO4wtVxWAIbho=
@ -361,8 +361,8 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ
github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8= github.com/moby/spdystream v0.2.0 h1:cjW1zVyyoiM0T7b6UoySUFqzXMoqRckQtXwGPiBhOM8=
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c= github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587 h1:HfkjXDfhgVaN5rmueG8cL8KKeFNecRCXFhaJ2qZ5SKA= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.0.0-20221205130635-1aeaba878587/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@ -382,8 +382,8 @@ github.com/onsi/ginkgo/v2 v2.9.1 h1:zie5Ly042PD3bsCvsSOPvRnFwyo3rKe64TJlD6nu0mk=
github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E= github.com/onsi/gomega v1.27.4 h1:Z2AnStgsdSayCMDiCU42qIz+HLqEPcgiOCXjAU/w+8E=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= github.com/opencontainers/image-spec v1.1.0-rc4 h1:oOxKUJWnFC4YGHCCMNql1x4YaDfYBTS5Y4x/Cgeo1E0=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= github.com/opencontainers/image-spec v1.1.0-rc4/go.mod h1:X4pATf0uXsnn3g5aiGIsVnJBR4mxhKzfwmvK/B2NTm8=
github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI= github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5 h1:Ii+DKncOVM8Cu1Hc+ETb5K+23HdAMvESYE3ZJ5b5cMI=
@ -426,8 +426,8 @@ github.com/prometheus/procfs v0.8.0 h1:ODq8ZFEaYeCaZOJlZZdJA2AbQR98dSHSM1KW/You5
github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4= github.com/prometheus/procfs v0.8.0/go.mod h1:z7EfXMXOkbkqb9IINtpCn86r/to3BnA0uaxHdg830/4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rubenv/sql-migrate v1.5.1 h1:WsZo4jPQfjmddDTh/suANP2aKPA7/ekN0LzuuajgQEo= github.com/rubenv/sql-migrate v1.5.2 h1:bMDqOnrJVV/6JQgQ/MxOpU+AdO8uzYYA/TxFUBzFtS0=
github.com/rubenv/sql-migrate v1.5.1/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is= github.com/rubenv/sql-migrate v1.5.2/go.mod h1:H38GW8Vqf8F0Su5XignRyaRcbXbJunSWxs+kmzlg0Is=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
@ -801,8 +801,8 @@ google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGj
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w= google.golang.org/protobuf v1.29.1 h1:7QBf+IK2gx70Ap/hDsOmam3GE0v9HicjfEdAxE62UoM=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -92,6 +92,7 @@ type Install struct {
SubNotes bool SubNotes bool
DisableOpenAPIValidation bool DisableOpenAPIValidation bool
IncludeCRDs bool IncludeCRDs bool
Labels map[string]string
// KubeVersion allows specifying a custom kubernetes version to use and // KubeVersion allows specifying a custom kubernetes version to use and
// APIVersions allows a manual set of supported API Versions to be passed // APIVersions allows a manual set of supported API Versions to be passed
// (for things like templating). These are ignored if ClientOnly is false // (for things like templating). These are ignored if ClientOnly is false
@ -290,7 +291,11 @@ func (i *Install) RunWithContext(ctx context.Context, chrt *chart.Chart, vals ma
return nil, err return nil, err
} }
rel := i.createRelease(chrt, vals) if driver.ContainsSystemLabels(i.Labels) {
return nil, fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels())
}
rel := i.createRelease(chrt, vals, i.Labels)
var manifestDoc *bytes.Buffer 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) 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)
@ -534,7 +539,7 @@ func (i *Install) availableName() error {
} }
// createRelease creates a new release object // createRelease creates a new release object
func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}) *release.Release { func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{}, labels map[string]string) *release.Release {
ts := i.cfg.Now() ts := i.cfg.Now()
return &release.Release{ return &release.Release{
Name: i.ReleaseName, Name: i.ReleaseName,
@ -547,6 +552,7 @@ func (i *Install) createRelease(chrt *chart.Chart, rawVals map[string]interface{
Status: release.StatusUnknown, Status: release.StatusUnknown,
}, },
Version: 1, Version: 1,
Labels: labels,
} }
} }

@ -717,3 +717,33 @@ func TestNameAndChartGenerateName(t *testing.T) {
}) })
} }
} }
func TestInstallWithLabels(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.Labels = map[string]string{
"key1": "val1",
"key2": "val2",
}
res, err := instAction.Run(buildChart(), nil)
if err != nil {
t.Fatalf("Failed install: %s", err)
}
is.Equal(instAction.Labels, res.Labels)
}
func TestInstallWithSystemLabels(t *testing.T) {
is := assert.New(t)
instAction := installAction(t)
instAction.Labels = map[string]string{
"owner": "val1",
"key2": "val2",
}
_, err := instAction.Run(buildChart(), nil)
if err == nil {
t.Fatal("expected an error")
}
is.Equal(fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err)
}

@ -20,6 +20,7 @@ import (
"context" "context"
"fmt" "fmt"
"io" "io"
"sort"
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -29,6 +30,11 @@ import (
"helm.sh/helm/v3/pkg/release" "helm.sh/helm/v3/pkg/release"
) )
const (
ExcludeNameFilter = "!name"
IncludeNameFilter = "name"
)
// ReleaseTesting is the action for testing a release. // ReleaseTesting is the action for testing a release.
// //
// It provides the implementation of 'helm test'. // It provides the implementation of 'helm test'.
@ -66,9 +72,9 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
skippedHooks := []*release.Hook{} skippedHooks := []*release.Hook{}
executingHooks := []*release.Hook{} executingHooks := []*release.Hook{}
if len(r.Filters["!name"]) != 0 { if len(r.Filters[ExcludeNameFilter]) != 0 {
for _, h := range rel.Hooks { for _, h := range rel.Hooks {
if contains(r.Filters["!name"], h.Name) { if contains(r.Filters[ExcludeNameFilter], h.Name) {
skippedHooks = append(skippedHooks, h) skippedHooks = append(skippedHooks, h)
} else { } else {
executingHooks = append(executingHooks, h) executingHooks = append(executingHooks, h)
@ -76,10 +82,10 @@ func (r *ReleaseTesting) Run(name string) (*release.Release, error) {
} }
rel.Hooks = executingHooks rel.Hooks = executingHooks
} }
if len(r.Filters["name"]) != 0 { if len(r.Filters[IncludeNameFilter]) != 0 {
executingHooks = nil executingHooks = nil
for _, h := range rel.Hooks { for _, h := range rel.Hooks {
if contains(r.Filters["name"], h.Name) { if contains(r.Filters[IncludeNameFilter], h.Name) {
executingHooks = append(executingHooks, h) executingHooks = append(executingHooks, h)
} else { } else {
skippedHooks = append(skippedHooks, h) skippedHooks = append(skippedHooks, h)
@ -107,9 +113,17 @@ func (r *ReleaseTesting) GetPodLogs(out io.Writer, rel *release.Release) error {
return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs") return errors.Wrap(err, "unable to get kubernetes client to fetch pod logs")
} }
for _, h := range rel.Hooks { hooksByWight := append([]*release.Hook{}, rel.Hooks...)
sort.Stable(hookByWeight(hooksByWight))
for _, h := range hooksByWight {
for _, e := range h.Events { for _, e := range h.Events {
if e == release.HookTest { if e == release.HookTest {
if contains(r.Filters[ExcludeNameFilter], h.Name) {
continue
}
if len(r.Filters[IncludeNameFilter]) > 0 && !contains(r.Filters[IncludeNameFilter], h.Name) {
continue
}
req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{}) req := client.CoreV1().Pods(r.Namespace).GetLogs(h.Name, &v1.PodLogOptions{})
logReader, err := req.Stream(context.Background()) logReader, err := req.Stream(context.Background())
if err != nil { if err != nil {

@ -110,6 +110,24 @@ func (r *Rollback) prepareRollback(name string) (*release.Release, *release.Rele
previousVersion = currentRelease.Version - 1 previousVersion = currentRelease.Version - 1
} }
historyReleases, err := r.cfg.Releases.History(name)
if err != nil {
return nil, nil, err
}
// Check if the history version to be rolled back exists
previousVersionExist := false
for _, historyRelease := range historyReleases {
version := historyRelease.Version
if previousVersion == version {
previousVersionExist = true
break
}
}
if !previousVersionExist {
return nil, nil, errors.Errorf("release has no %d version", previousVersion)
}
r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion) r.cfg.Log("rolling back %s (current: v%d, target: v%d)", name, currentRelease.Version, previousVersion)
previousRelease, err := r.cfg.Releases.Get(name, previousVersion) previousRelease, err := r.cfg.Releases.Get(name, previousVersion)

@ -153,6 +153,9 @@ func (s *Show) Run(chartpath string) (string, error) {
func findReadme(files []*chart.File) (file *chart.File) { func findReadme(files []*chart.File) (file *chart.File) {
for _, file := range files { for _, file := range files {
for _, n := range readmeFileNames { for _, n := range readmeFileNames {
if file == nil {
continue
}
if strings.EqualFold(file.Name, n) { if strings.EqualFold(file.Name, n) {
return file return file
} }

@ -21,6 +21,7 @@ import (
"time" "time"
"github.com/pkg/errors" "github.com/pkg/errors"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/chartutil"
@ -38,6 +39,7 @@ type Uninstall struct {
DisableHooks bool DisableHooks bool
DryRun bool DryRun bool
IgnoreNotFound bool
KeepHistory bool KeepHistory bool
Wait bool Wait bool
DeletionPropagation string DeletionPropagation string
@ -73,6 +75,9 @@ func (u *Uninstall) Run(name string) (*release.UninstallReleaseResponse, error)
rels, err := u.cfg.Releases.History(name) rels, err := u.cfg.Releases.History(name)
if err != nil { if err != nil {
if u.IgnoreNotFound {
return nil, nil
}
return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name) return nil, errors.Wrapf(err, "uninstall: Release not loaded: %s", name)
} }
if len(rels) < 1 { if len(rels) < 1 {

@ -32,6 +32,17 @@ func uninstallAction(t *testing.T) *Uninstall {
return unAction return unAction
} }
func TestUninstallRelease_ignoreNotFound(t *testing.T) {
unAction := uninstallAction(t)
unAction.DryRun = false
unAction.IgnoreNotFound = true
is := assert.New(t)
res, err := unAction.Run("release-non-exist")
is.Nil(res)
is.NoError(err)
}
func TestUninstallRelease_deleteRelease(t *testing.T) { func TestUninstallRelease_deleteRelease(t *testing.T) {
is := assert.New(t) is := assert.New(t)

@ -94,6 +94,7 @@ type Upgrade struct {
SubNotes bool SubNotes bool
// Description is the description of this operation // Description is the description of this operation
Description string Description string
Labels map[string]string
// PostRender is an optional post-renderer // PostRender is an optional post-renderer
// //
// If this is non-nil, then after templates are rendered, they will be sent to the // If this is non-nil, then after templates are rendered, they will be sent to the
@ -261,6 +262,11 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
return nil, nil, err return nil, nil, err
} }
fmt.Println(driver.ContainsSystemLabels(u.Labels))
if driver.ContainsSystemLabels(u.Labels) {
return nil, nil, fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels())
}
// Store an upgraded release. // Store an upgraded release.
upgradedRelease := &release.Release{ upgradedRelease := &release.Release{
Name: name, Name: name,
@ -276,6 +282,7 @@ func (u *Upgrade) prepareUpgrade(name string, chart *chart.Chart, vals map[strin
Version: revision, Version: revision,
Manifest: manifestDoc.String(), Manifest: manifestDoc.String(),
Hooks: hooks, Hooks: hooks,
Labels: mergeCustomLabels(lastRelease.Labels, u.Labels),
} }
if len(notesTxt) > 0 { if len(notesTxt) > 0 {
@ -598,3 +605,13 @@ func objectKey(r *resource.Info) string {
gvk := r.Object.GetObjectKind().GroupVersionKind() gvk := r.Object.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name) return fmt.Sprintf("%s/%s/%s/%s", gvk.GroupVersion().String(), gvk.Kind, r.Namespace, r.Name)
} }
func mergeCustomLabels(current, desired map[string]string) map[string]string {
labels := mergeStrStrMaps(current, desired)
for k, v := range labels {
if v == "null" {
delete(labels, k)
}
}
return labels
}

@ -19,10 +19,12 @@ package action
import ( import (
"context" "context"
"fmt" "fmt"
"reflect"
"testing" "testing"
"time" "time"
"helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/storage/driver"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -386,5 +388,97 @@ func TestUpgradeRelease_Interrupted_Atomic(t *testing.T) {
is.NoError(err) is.NoError(err)
// Should have rolled back to the previous // Should have rolled back to the previous
is.Equal(updatedRes.Info.Status, release.StatusDeployed) is.Equal(updatedRes.Info.Status, release.StatusDeployed)
}
func TestMergeCustomLabels(t *testing.T) {
var tests = [][3]map[string]string{
{nil, nil, map[string]string{}},
{map[string]string{}, map[string]string{}, map[string]string{}},
{map[string]string{"k1": "v1", "k2": "v2"}, nil, map[string]string{"k1": "v1", "k2": "v2"}},
{nil, map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "v1", "k2": "v2"}},
{map[string]string{"k1": "v1", "k2": "v2"}, map[string]string{"k1": "null", "k2": "v3"}, map[string]string{"k2": "v3"}},
}
for _, test := range tests {
if output := mergeCustomLabels(test[0], test[1]); !reflect.DeepEqual(test[2], output) {
t.Errorf("Expected {%v}, got {%v}", test[2], output)
}
}
}
func TestUpgradeRelease_Labels(t *testing.T) {
is := assert.New(t)
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "labels"
// It's needed to check that suppressed release would keep original labels
rel.Labels = map[string]string{
"key1": "val1",
"key2": "val2.1",
}
rel.Info.Status = release.StatusDeployed
err := upAction.cfg.Releases.Create(rel)
is.NoError(err)
upAction.Labels = map[string]string{
"key1": "null",
"key2": "val2.2",
"key3": "val3",
}
// setting newValues and upgrading
res, err := upAction.Run(rel.Name, buildChart(), nil)
is.NoError(err)
// Now make sure it is actually upgraded and labels were merged
updatedRes, err := upAction.cfg.Releases.Get(res.Name, 2)
is.NoError(err)
if updatedRes == nil {
is.Fail("Updated Release is nil")
return
}
is.Equal(release.StatusDeployed, updatedRes.Info.Status)
is.Equal(mergeCustomLabels(rel.Labels, upAction.Labels), updatedRes.Labels)
// Now make sure it is suppressed release still contains original labels
initialRes, err := upAction.cfg.Releases.Get(res.Name, 1)
is.NoError(err)
if initialRes == nil {
is.Fail("Updated Release is nil")
return
}
is.Equal(initialRes.Info.Status, release.StatusSuperseded)
is.Equal(initialRes.Labels, rel.Labels)
}
func TestUpgradeRelease_SystemLabels(t *testing.T) {
is := assert.New(t)
upAction := upgradeAction(t)
rel := releaseStub()
rel.Name = "labels"
// It's needed to check that suppressed release would keep original labels
rel.Labels = map[string]string{
"key1": "val1",
"key2": "val2.1",
}
rel.Info.Status = release.StatusDeployed
err := upAction.cfg.Releases.Create(rel)
is.NoError(err)
upAction.Labels = map[string]string{
"key1": "null",
"key2": "val2.2",
"owner": "val3",
}
// setting newValues and upgrading
_, err = upAction.Run(rel.Name, buildChart(), nil)
if err == nil {
t.Fatal("expected an error")
}
is.Equal(fmt.Errorf("user suplied labels contains system reserved label name. System labels: %+v", driver.GetSystemLabels()), err)
} }

@ -85,7 +85,10 @@ func ensureArchive(name string, raw *os.File) error {
if err != nil && err != io.EOF { if err != nil && err != io.EOF {
return fmt.Errorf("file '%s' cannot be read: %s", name, err) return fmt.Errorf("file '%s' cannot be read: %s", name, err)
} }
if contentType := http.DetectContentType(buffer); contentType != "application/x-gzip" {
// Helm may identify achieve of the application/x-gzip as application/vnd.ms-fontobject.
// Fix for: https://github.com/helm/helm/issues/12261
if contentType := http.DetectContentType(buffer); contentType != "application/x-gzip" && !isGZipApplication(buffer) {
// TODO: Is there a way to reliably test if a file content is YAML? ghodss/yaml accepts a wide // TODO: Is there a way to reliably test if a file content is YAML? ghodss/yaml accepts a wide
// variety of content (Makefile, .zshrc) as valid YAML without errors. // variety of content (Makefile, .zshrc) as valid YAML without errors.
@ -98,6 +101,12 @@ func ensureArchive(name string, raw *os.File) error {
return nil return nil
} }
// isGZipApplication checks whether the achieve is of the application/x-gzip type.
func isGZipApplication(data []byte) bool {
sig := []byte("\x1F\x8B\x08")
return bytes.HasPrefix(data, sig)
}
// LoadArchiveFiles reads in files out of an archive into memory. This function // LoadArchiveFiles reads in files out of an archive into memory. This function
// performs important path security checks and should always be used before // performs important path security checks and should always be used before
// expanding a tarball // expanding a tarball

@ -121,6 +121,8 @@ fullnameOverride: ""
serviceAccount: serviceAccount:
# Specifies whether a service account should be created # Specifies whether a service account should be created
create: true create: true
# Automatically mount a ServiceAccount's API credentials?
automount: true
# Annotations to add to the service account # Annotations to add to the service account
annotations: {} annotations: {}
# The name of the service account to use. # The name of the service account to use.
@ -128,6 +130,7 @@ serviceAccount:
name: "" name: ""
podAnnotations: {} podAnnotations: {}
podLabels: {}
podSecurityContext: {} podSecurityContext: {}
# fsGroup: 2000 # fsGroup: 2000
@ -308,6 +311,9 @@ spec:
{{- end }} {{- end }}
labels: labels:
{{- include "<CHARTNAME>.selectorLabels" . | nindent 8 }} {{- include "<CHARTNAME>.selectorLabels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
spec: spec:
{{- with .Values.imagePullSecrets }} {{- with .Values.imagePullSecrets }}
imagePullSecrets: imagePullSecrets:
@ -386,6 +392,7 @@ metadata:
annotations: annotations:
{{- toYaml . | nindent 4 }} {{- toYaml . | nindent 4 }}
{{- end }} {{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount }}
{{- end }} {{- end }}
` `

@ -151,6 +151,9 @@ Loop:
} }
for _, req := range c.Metadata.Dependencies { for _, req := range c.Metadata.Dependencies {
if req == nil {
continue
}
if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil { if chartDependency := getAliasDependency(c.Dependencies(), req); chartDependency != nil {
chartDependencies = append(chartDependencies, chartDependency) chartDependencies = append(chartDependencies, chartDependency)
} }

@ -8,7 +8,7 @@ Consists of the following charts:
- App Chart (Uses Library Chart as dependecy, 2x: app1/app2) - App Chart (Uses Library Chart as dependecy, 2x: app1/app2)
- Umbrella Chart (Has all the app charts as dependencies) - Umbrella Chart (Has all the app charts as dependencies)
The precendence is as follows: `library < app < umbrella` The precedence is as follows: `library < app < umbrella`
Catches two use-cases: Catches two use-cases:

@ -391,6 +391,9 @@ func recAllTpls(c *chart.Chart, templates map[string]renderable, vals chartutil.
newParentID := c.ChartFullPath() newParentID := c.ChartFullPath()
for _, t := range c.Templates { for _, t := range c.Templates {
if t == nil {
continue
}
if !isTemplateValid(c, t.Name) { if !isTemplateValid(c, t.Name) {
continue continue
} }

@ -179,9 +179,21 @@ func (p Providers) ByScheme(scheme string) (Getter, error) {
return nil, errors.Errorf("scheme %q not supported", scheme) return nil, errors.Errorf("scheme %q not supported", scheme)
} }
const (
// The cost timeout references curl's default connection timeout.
// https://github.com/curl/curl/blob/master/lib/connect.h#L40C21-L40C21
// The helm commands are usually executed manually. Considering the acceptable waiting time, we reduced the entire request time to 120s.
DefaultHTTPTimeout = 120
)
var defaultOptions = []Option{WithTimeout(time.Second * DefaultHTTPTimeout)}
var httpProvider = Provider{ var httpProvider = Provider{
Schemes: []string{"http", "https"}, Schemes: []string{"http", "https"},
New: NewHTTPGetter, New: func(options ...Option) (Getter, error) {
options = append(options, defaultOptions...)
return NewHTTPGetter(options...)
},
} }
var ociProvider = Provider{ var ociProvider = Provider{

@ -87,7 +87,16 @@ func NewClient(options ...ClientOption) (*Client, error) {
} }
client.authorizer = authClient client.authorizer = authClient
} }
resolverFn := client.resolver // copy for avoiding recursive call
client.resolver = func(ref registry.Reference) (remotes.Resolver, error) { 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 := http.Header{}
headers.Set("User-Agent", version.GetUserAgent()) headers.Set("User-Agent", version.GetUserAgent())
dockerClient, ok := client.authorizer.(*dockerauth.Client) dockerClient, ok := client.authorizer.(*dockerauth.Client)
@ -117,6 +126,7 @@ func NewClient(options ...ClientOption) (*Client, error) {
} }
return resolver, nil return resolver, nil
} }
// allocate a cache if option is set // allocate a cache if option is set
var cache registryauth.Cache var cache registryauth.Cache
if client.enableCache { if client.enableCache {
@ -199,6 +209,15 @@ 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(ref registry.Reference) (remotes.Resolver, error) {
return resolver, nil
}
}
}
type ( type (
// LoginOption allows specifying various settings on login // LoginOption allows specifying various settings on login
LoginOption func(*loginOperation) LoginOption func(*loginOperation)
@ -287,21 +306,21 @@ type (
// PullResult is the result returned upon successful pull. // PullResult is the result returned upon successful pull.
PullResult struct { PullResult struct {
Manifest *descriptorPullSummary `json:"manifest"` Manifest *DescriptorPullSummary `json:"manifest"`
Config *descriptorPullSummary `json:"config"` Config *DescriptorPullSummary `json:"config"`
Chart *descriptorPullSummaryWithMeta `json:"chart"` Chart *DescriptorPullSummaryWithMeta `json:"chart"`
Prov *descriptorPullSummary `json:"prov"` Prov *DescriptorPullSummary `json:"prov"`
Ref string `json:"ref"` Ref string `json:"ref"`
} }
descriptorPullSummary struct { DescriptorPullSummary struct {
Data []byte `json:"-"` Data []byte `json:"-"`
Digest string `json:"digest"` Digest string `json:"digest"`
Size int64 `json:"size"` Size int64 `json:"size"`
} }
descriptorPullSummaryWithMeta struct { DescriptorPullSummaryWithMeta struct {
descriptorPullSummary DescriptorPullSummary
Meta *chart.Metadata `json:"meta"` Meta *chart.Metadata `json:"meta"`
} }
@ -404,16 +423,16 @@ func (c *Client) Pull(ref string, options ...PullOption) (*PullResult, error) {
} }
} }
result := &PullResult{ result := &PullResult{
Manifest: &descriptorPullSummary{ Manifest: &DescriptorPullSummary{
Digest: manifest.Digest.String(), Digest: manifest.Digest.String(),
Size: manifest.Size, Size: manifest.Size,
}, },
Config: &descriptorPullSummary{ Config: &DescriptorPullSummary{
Digest: configDescriptor.Digest.String(), Digest: configDescriptor.Digest.String(),
Size: configDescriptor.Size, Size: configDescriptor.Size,
}, },
Chart: &descriptorPullSummaryWithMeta{}, Chart: &DescriptorPullSummaryWithMeta{},
Prov: &descriptorPullSummary{}, Prov: &DescriptorPullSummary{},
Ref: parsedRef.String(), Ref: parsedRef.String(),
} }
var getManifestErr error var getManifestErr error

@ -88,6 +88,7 @@ func setup(suite *TestSuite, tlsEnabled, insecure bool) *registry.Registry {
ClientOptEnableCache(true), ClientOptEnableCache(true),
ClientOptWriter(suite.Out), ClientOptWriter(suite.Out),
ClientOptCredentialsFile(credentialsFile), ClientOptCredentialsFile(credentialsFile),
ClientOptResolver(nil),
} }
if tlsEnabled { if tlsEnabled {

@ -29,6 +29,7 @@ type KindSortOrder []string
// //
// Those occurring earlier in the list get installed before those occurring later in the list. // Those occurring earlier in the list get installed before those occurring later in the list.
var InstallOrder KindSortOrder = []string{ var InstallOrder KindSortOrder = []string{
"PriorityClass",
"Namespace", "Namespace",
"NetworkPolicy", "NetworkPolicy",
"ResourceQuota", "ResourceQuota",
@ -105,6 +106,7 @@ var UninstallOrder KindSortOrder = []string{
"ResourceQuota", "ResourceQuota",
"NetworkPolicy", "NetworkPolicy",
"Namespace", "Namespace",
"PriorityClass",
} }
// sort manifests by kind. // sort manifests by kind.

@ -169,6 +169,10 @@ func TestKindSorter(t *testing.T) {
Name: "x", Name: "x",
Head: &SimpleHead{Kind: "HorizontalPodAutoscaler"}, Head: &SimpleHead{Kind: "HorizontalPodAutoscaler"},
}, },
{
Name: "F",
Head: &SimpleHead{Kind: "PriorityClass"},
},
} }
for _, test := range []struct { for _, test := range []struct {
@ -176,8 +180,8 @@ func TestKindSorter(t *testing.T) {
order KindSortOrder order KindSortOrder
expected string expected string
}{ }{
{"install", InstallOrder, "aAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvw!"}, {"install", InstallOrder, "FaAbcC3deEf1gh2iIjJkKlLmnopqrxstuUvw!"},
{"uninstall", UninstallOrder, "wvUmutsxrqponLlKkJjIi2hg1fEed3CcbAa!"}, {"uninstall", UninstallOrder, "wvUmutsxrqponLlKkJjIi2hg1fEed3CcbAaF!"},
} { } {
var buf bytes.Buffer var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) { t.Run(test.description, func(t *testing.T) {

@ -78,6 +78,7 @@ func (cfgmaps *ConfigMaps) Get(key string) (*rspb.Release, error) {
cfgmaps.Log("get: failed to decode data %q: %s", key, err) cfgmaps.Log("get: failed to decode data %q: %s", key, err)
return nil, err return nil, err
} }
r.Labels = filterSystemLabels(obj.ObjectMeta.Labels)
// return the release object // return the release object
return r, nil return r, nil
} }
@ -106,7 +107,7 @@ func (cfgmaps *ConfigMaps) List(filter func(*rspb.Release) bool) ([]*rspb.Releas
continue continue
} }
rls.Labels = item.ObjectMeta.Labels rls.Labels = filterSystemLabels(item.ObjectMeta.Labels)
if filter(rls) { if filter(rls) {
results = append(results, rls) results = append(results, rls)
@ -145,6 +146,7 @@ func (cfgmaps *ConfigMaps) Query(labels map[string]string) ([]*rspb.Release, err
cfgmaps.Log("query: failed to decode release: %s", err) cfgmaps.Log("query: failed to decode release: %s", err)
continue continue
} }
rls.Labels = filterSystemLabels(item.ObjectMeta.Labels)
results = append(results, rls) results = append(results, rls)
} }
return results, nil return results, nil
@ -157,6 +159,7 @@ func (cfgmaps *ConfigMaps) Create(key string, rls *rspb.Release) error {
var lbs labels var lbs labels
lbs.init() lbs.init()
lbs.fromMap(rls.Labels)
lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix()))) lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix())))
// create a new configmap to hold the release // create a new configmap to hold the release
@ -184,6 +187,7 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
var lbs labels var lbs labels
lbs.init() lbs.init()
lbs.fromMap(rls.Labels)
lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix()))) lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix())))
// create a new configmap object to hold the release // create a new configmap object to hold the release
@ -239,6 +243,9 @@ func newConfigMapsObject(key string, rls *rspb.Release, lbs labels) (*v1.ConfigM
lbs.init() lbs.init()
} }
// apply custom labels
lbs.fromMap(rls.Labels)
// apply labels // apply labels
lbs.set("name", rls.Name) lbs.set("name", rls.Name)
lbs.set("owner", owner) lbs.set("owner", owner)

@ -40,6 +40,10 @@ func releaseStub(name string, vers int, namespace string, status rspb.Status) *r
Version: vers, Version: vers,
Namespace: namespace, Namespace: namespace,
Info: &rspb.Info{Status: status}, Info: &rspb.Info{Status: status},
Labels: map[string]string{
"key1": "val1",
"key2": "val2",
},
} }
} }

@ -72,6 +72,7 @@ func (secrets *Secrets) Get(key string) (*rspb.Release, error) {
} }
// found the secret, decode the base64 data string // found the secret, decode the base64 data string
r, err := decodeRelease(string(obj.Data["release"])) r, err := decodeRelease(string(obj.Data["release"]))
r.Labels = filterSystemLabels(obj.ObjectMeta.Labels)
return r, errors.Wrapf(err, "get: failed to decode data %q", key) return r, errors.Wrapf(err, "get: failed to decode data %q", key)
} }
@ -98,7 +99,7 @@ func (secrets *Secrets) List(filter func(*rspb.Release) bool) ([]*rspb.Release,
continue continue
} }
rls.Labels = item.ObjectMeta.Labels rls.Labels = filterSystemLabels(item.ObjectMeta.Labels)
if filter(rls) { if filter(rls) {
results = append(results, rls) results = append(results, rls)
@ -136,6 +137,7 @@ func (secrets *Secrets) Query(labels map[string]string) ([]*rspb.Release, error)
secrets.Log("query: failed to decode release: %s", err) secrets.Log("query: failed to decode release: %s", err)
continue continue
} }
rls.Labels = filterSystemLabels(item.ObjectMeta.Labels)
results = append(results, rls) results = append(results, rls)
} }
return results, nil return results, nil
@ -148,6 +150,7 @@ func (secrets *Secrets) Create(key string, rls *rspb.Release) error {
var lbs labels var lbs labels
lbs.init() lbs.init()
lbs.fromMap(rls.Labels)
lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix()))) lbs.set("createdAt", strconv.Itoa(int(time.Now().Unix())))
// create a new secret to hold the release // create a new secret to hold the release
@ -173,6 +176,7 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
var lbs labels var lbs labels
lbs.init() lbs.init()
lbs.fromMap(rls.Labels)
lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix()))) lbs.set("modifiedAt", strconv.Itoa(int(time.Now().Unix())))
// create a new secret object to hold the release // create a new secret object to hold the release
@ -221,6 +225,9 @@ func newSecretsObject(key string, rls *rspb.Release, lbs labels) (*v1.Secret, er
lbs.init() lbs.init()
} }
// apply custom labels
lbs.fromMap(rls.Labels)
// apply labels // apply labels
lbs.set("name", rls.Name) lbs.set("name", rls.Name)
lbs.set("owner", owner) lbs.set("owner", owner)

@ -49,6 +49,7 @@ const postgreSQLDialect = "postgres"
const SQLDriverName = "SQL" const SQLDriverName = "SQL"
const sqlReleaseTableName = "releases_v1" const sqlReleaseTableName = "releases_v1"
const sqlCustomLabelsTableName = "custom_labels_v1"
const ( const (
sqlReleaseTableKeyColumn = "key" sqlReleaseTableKeyColumn = "key"
@ -61,6 +62,17 @@ const (
sqlReleaseTableOwnerColumn = "owner" sqlReleaseTableOwnerColumn = "owner"
sqlReleaseTableCreatedAtColumn = "createdAt" sqlReleaseTableCreatedAtColumn = "createdAt"
sqlReleaseTableModifiedAtColumn = "modifiedAt" sqlReleaseTableModifiedAtColumn = "modifiedAt"
sqlCustomLabelsTableReleaseKeyColumn = "releaseKey"
sqlCustomLabelsTableReleaseNamespaceColumn = "releaseNamespace"
sqlCustomLabelsTableKeyColumn = "key"
sqlCustomLabelsTableValueColumn = "value"
)
// Following limits based on k8s labels limits - https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/#syntax-and-character-set
const (
sqlCustomLabelsTableKeyMaxLenght = 253 + 1 + 63
sqlCustomLabelsTableValueMaxLenght = 63
) )
const ( const (
@ -150,6 +162,41 @@ func (s *SQL) ensureDBSetup() error {
`, sqlReleaseTableName), `, sqlReleaseTableName),
}, },
}, },
{
Id: "custom_labels",
Up: []string{
fmt.Sprintf(`
CREATE TABLE %s (
%s VARCHAR(64),
%s VARCHAR(67),
%s VARCHAR(%d),
%s VARCHAR(%d)
);
CREATE INDEX ON %s (%s, %s);
GRANT ALL ON %s TO PUBLIC;
ALTER TABLE %s ENABLE ROW LEVEL SECURITY;
`,
sqlCustomLabelsTableName,
sqlCustomLabelsTableReleaseKeyColumn,
sqlCustomLabelsTableReleaseNamespaceColumn,
sqlCustomLabelsTableKeyColumn,
sqlCustomLabelsTableKeyMaxLenght,
sqlCustomLabelsTableValueColumn,
sqlCustomLabelsTableValueMaxLenght,
sqlCustomLabelsTableName,
sqlCustomLabelsTableReleaseKeyColumn,
sqlCustomLabelsTableReleaseNamespaceColumn,
sqlCustomLabelsTableName,
sqlCustomLabelsTableName,
),
},
Down: []string{
fmt.Sprintf(`
DELETE TABLE %s;
`, sqlCustomLabelsTableName),
},
},
}, },
} }
@ -180,6 +227,13 @@ type SQLReleaseWrapper struct {
ModifiedAt int `db:"modifiedAt"` ModifiedAt int `db:"modifiedAt"`
} }
type SQLReleaseCustomLabelWrapper struct {
ReleaseKey string `db:"release_key"`
ReleaseNamespace string `db:"release_namespace"`
Key string `db:"key"`
Value string `db:"value"`
}
// NewSQL initializes a new sql driver. // NewSQL initializes a new sql driver.
func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) { func NewSQL(connectionString string, logger func(string, ...interface{}), namespace string) (*SQL, error) {
db, err := sqlx.Connect(postgreSQLDialect, connectionString) db, err := sqlx.Connect(postgreSQLDialect, connectionString)
@ -230,13 +284,18 @@ func (s *SQL) Get(key string) (*rspb.Release, error) {
return nil, err return nil, err
} }
if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil {
s.Log("failed to get release %s/%s custom labels: %v", s.namespace, key, err)
return nil, err
}
return release, nil return release, nil
} }
// List returns the list of all releases such that filter(release) == true // List returns the list of all releases such that filter(release) == true
func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) { func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
sb := s.statementBuilder. sb := s.statementBuilder.
Select(sqlReleaseTableBodyColumn). Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn).
From(sqlReleaseTableName). From(sqlReleaseTableName).
Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner}) Where(sq.Eq{sqlReleaseTableOwnerColumn: sqlReleaseDefaultOwner})
@ -264,6 +323,12 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
s.Log("list: failed to decode release: %v: %v", record, err) s.Log("list: failed to decode release: %v: %v", record, err)
continue continue
} }
if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil {
s.Log("failed to get release %s/%s custom labels: %v", record.Namespace, record.Key, err)
return nil, err
}
if filter(release) { if filter(release) {
releases = append(releases, release) releases = append(releases, release)
} }
@ -275,7 +340,7 @@ func (s *SQL) List(filter func(*rspb.Release) bool) ([]*rspb.Release, error) {
// Query returns the set of releases that match the provided set of labels. // Query returns the set of releases that match the provided set of labels.
func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) { func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
sb := s.statementBuilder. sb := s.statementBuilder.
Select(sqlReleaseTableBodyColumn). Select(sqlReleaseTableKeyColumn, sqlReleaseTableNamespaceColumn, sqlReleaseTableBodyColumn).
From(sqlReleaseTableName) From(sqlReleaseTableName)
keys := make([]string, 0, len(labels)) keys := make([]string, 0, len(labels))
@ -321,6 +386,12 @@ func (s *SQL) Query(labels map[string]string) ([]*rspb.Release, error) {
s.Log("list: failed to decode release: %v: %v", record, err) s.Log("list: failed to decode release: %v: %v", record, err)
continue continue
} }
if release.Labels, err = s.getReleaseCustomLabels(record.Key, record.Namespace); err != nil {
s.Log("failed to get release %s/%s custom labels: %v", record.Namespace, record.Key, err)
return nil, err
}
releases = append(releases, release) releases = append(releases, release)
} }
@ -403,6 +474,36 @@ func (s *SQL) Create(key string, rls *rspb.Release) error {
s.Log("failed to store release %s in SQL database: %v", key, err) s.Log("failed to store release %s in SQL database: %v", key, err)
return err return err
} }
// Filtering labels before insert cause in SQL storage driver system releases are stored in separate columns of release table
for k, v := range filterSystemLabels(rls.Labels) {
insertLabelsQuery, args, err := s.statementBuilder.
Insert(sqlCustomLabelsTableName).
Columns(
sqlCustomLabelsTableReleaseKeyColumn,
sqlCustomLabelsTableReleaseNamespaceColumn,
sqlCustomLabelsTableKeyColumn,
sqlCustomLabelsTableValueColumn,
).
Values(
key,
namespace,
k,
v,
).ToSql()
if err != nil {
defer transaction.Rollback()
s.Log("failed to build insert query: %v", err)
return err
}
if _, err := transaction.Exec(insertLabelsQuery, args...); err != nil {
defer transaction.Rollback()
s.Log("failed to write Labels: %v", err)
return err
}
}
defer transaction.Commit() defer transaction.Commit()
return nil return nil
@ -487,10 +588,56 @@ func (s *SQL) Delete(key string) (*rspb.Release, error) {
Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}). Where(sq.Eq{sqlReleaseTableNamespaceColumn: s.namespace}).
ToSql() ToSql()
if err != nil { if err != nil {
s.Log("failed to build select query: %v", err) s.Log("failed to build delete query: %v", err)
return nil, err return nil, err
} }
_, err = transaction.Exec(deleteQuery, args...) _, err = transaction.Exec(deleteQuery, args...)
if err != nil {
s.Log("failed perform delete query: %v", err)
return release, err
}
if release.Labels, err = s.getReleaseCustomLabels(key, s.namespace); err != nil {
s.Log("failed to get release %s/%s custom labels: %v", s.namespace, key, err)
return nil, err
}
deleteCustomLabelsQuery, args, err := s.statementBuilder.
Delete(sqlCustomLabelsTableName).
Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key}).
Where(sq.Eq{sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}).
ToSql()
if err != nil {
s.Log("failed to build delete Labels query: %v", err)
return nil, err
}
_, err = transaction.Exec(deleteCustomLabelsQuery, args...)
return release, err return release, err
} }
// Get release custom labels from database
func (s *SQL) getReleaseCustomLabels(key string, namespace string) (map[string]string, error) {
query, args, err := s.statementBuilder.
Select(sqlCustomLabelsTableKeyColumn, sqlCustomLabelsTableValueColumn).
From(sqlCustomLabelsTableName).
Where(sq.Eq{sqlCustomLabelsTableReleaseKeyColumn: key,
sqlCustomLabelsTableReleaseNamespaceColumn: s.namespace}).
ToSql()
if err != nil {
return nil, err
}
var labelsList = []SQLReleaseCustomLabelWrapper{}
if err := s.db.Select(&labelsList, query, args...); err != nil {
return nil, err
}
labelsMap := make(map[string]string)
for _, i := range labelsList {
labelsMap[i.Key] = i.Value
}
return filterSystemLabels(labelsMap), nil
}

@ -62,6 +62,8 @@ func TestSQLGet(t *testing.T) {
), ),
).RowsWillBeClosed() ).RowsWillBeClosed()
mockGetReleaseCustomLabels(mock, key, namespace, rel.Labels)
got, err := sqlDriver.Get(key) got, err := sqlDriver.Get(key)
if err != nil { if err != nil {
t.Fatalf("Failed to get release: %v", err) t.Fatalf("Failed to get release: %v", err)
@ -77,38 +79,42 @@ func TestSQLGet(t *testing.T) {
} }
func TestSQLList(t *testing.T) { func TestSQLList(t *testing.T) {
body1, _ := encodeRelease(releaseStub("key-1", 1, "default", rspb.StatusUninstalled)) releases := []*rspb.Release{}
body2, _ := encodeRelease(releaseStub("key-2", 1, "default", rspb.StatusUninstalled)) releases = append(releases, releaseStub("key-1", 1, "default", rspb.StatusUninstalled))
body3, _ := encodeRelease(releaseStub("key-3", 1, "default", rspb.StatusDeployed)) releases = append(releases, releaseStub("key-2", 1, "default", rspb.StatusUninstalled))
body4, _ := encodeRelease(releaseStub("key-4", 1, "default", rspb.StatusDeployed)) releases = append(releases, releaseStub("key-3", 1, "default", rspb.StatusDeployed))
body5, _ := encodeRelease(releaseStub("key-5", 1, "default", rspb.StatusSuperseded)) releases = append(releases, releaseStub("key-4", 1, "default", rspb.StatusDeployed))
body6, _ := encodeRelease(releaseStub("key-6", 1, "default", rspb.StatusSuperseded)) releases = append(releases, releaseStub("key-5", 1, "default", rspb.StatusSuperseded))
releases = append(releases, releaseStub("key-6", 1, "default", rspb.StatusSuperseded))
sqlDriver, mock := newTestFixtureSQL(t) sqlDriver, mock := newTestFixtureSQL(t)
for i := 0; i < 3; i++ { for i := 0; i < 3; i++ {
query := fmt.Sprintf( query := fmt.Sprintf(
"SELECT %s FROM %s WHERE %s = $1 AND %s = $2", "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2",
sqlReleaseTableKeyColumn,
sqlReleaseTableNamespaceColumn,
sqlReleaseTableBodyColumn, sqlReleaseTableBodyColumn,
sqlReleaseTableName, sqlReleaseTableName,
sqlReleaseTableOwnerColumn, sqlReleaseTableOwnerColumn,
sqlReleaseTableNamespaceColumn, sqlReleaseTableNamespaceColumn,
) )
rows := mock.NewRows([]string{
sqlReleaseTableBodyColumn,
})
for _, r := range releases {
body, _ := encodeRelease(r)
rows.AddRow(body)
}
mock. mock.
ExpectQuery(regexp.QuoteMeta(query)). ExpectQuery(regexp.QuoteMeta(query)).
WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace). WithArgs(sqlReleaseDefaultOwner, sqlDriver.namespace).
WillReturnRows( WillReturnRows(rows).RowsWillBeClosed()
mock.NewRows([]string{
sqlReleaseTableBodyColumn, for _, r := range releases {
}). mockGetReleaseCustomLabels(mock, "", r.Namespace, r.Labels)
AddRow(body1). }
AddRow(body2).
AddRow(body3).
AddRow(body4).
AddRow(body5).
AddRow(body6),
).RowsWillBeClosed()
} }
// list all deleted releases // list all deleted releases
@ -181,6 +187,23 @@ func TestSqlCreate(t *testing.T) {
ExpectExec(regexp.QuoteMeta(query)). ExpectExec(regexp.QuoteMeta(query)).
WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())). WithArgs(key, sqlReleaseDefaultType, body, rel.Name, rel.Namespace, int(rel.Version), rel.Info.Status.String(), sqlReleaseDefaultOwner, int(time.Now().Unix())).
WillReturnResult(sqlmock.NewResult(1, 1)) WillReturnResult(sqlmock.NewResult(1, 1))
labelsQuery := fmt.Sprintf(
"INSERT INTO %s (%s,%s,%s,%s) VALUES ($1,$2,$3,$4)",
sqlCustomLabelsTableName,
sqlCustomLabelsTableReleaseKeyColumn,
sqlCustomLabelsTableReleaseNamespaceColumn,
sqlCustomLabelsTableKeyColumn,
sqlCustomLabelsTableValueColumn,
)
mock.MatchExpectationsInOrder(false)
for k, v := range filterSystemLabels(rel.Labels) {
mock.
ExpectExec(regexp.QuoteMeta(labelsQuery)).
WithArgs(key, rel.Namespace, k, v).
WillReturnResult(sqlmock.NewResult(1, 1))
}
mock.ExpectCommit() mock.ExpectCommit()
if err := sqlDriver.Create(key, rel); err != nil { if err := sqlDriver.Create(key, rel); err != nil {
@ -316,7 +339,9 @@ func TestSqlQuery(t *testing.T) {
sqlDriver, mock := newTestFixtureSQL(t) sqlDriver, mock := newTestFixtureSQL(t)
query := fmt.Sprintf( query := fmt.Sprintf(
"SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4", "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3 AND %s = $4",
sqlReleaseTableKeyColumn,
sqlReleaseTableNamespaceColumn,
sqlReleaseTableBodyColumn, sqlReleaseTableBodyColumn,
sqlReleaseTableName, sqlReleaseTableName,
sqlReleaseTableNameColumn, sqlReleaseTableNameColumn,
@ -345,8 +370,12 @@ func TestSqlQuery(t *testing.T) {
), ),
).RowsWillBeClosed() ).RowsWillBeClosed()
mockGetReleaseCustomLabels(mock, "", deployedRelease.Namespace, deployedRelease.Labels)
query = fmt.Sprintf( query = fmt.Sprintf(
"SELECT %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3", "SELECT %s, %s, %s FROM %s WHERE %s = $1 AND %s = $2 AND %s = $3",
sqlReleaseTableKeyColumn,
sqlReleaseTableNamespaceColumn,
sqlReleaseTableBodyColumn, sqlReleaseTableBodyColumn,
sqlReleaseTableName, sqlReleaseTableName,
sqlReleaseTableNameColumn, sqlReleaseTableNameColumn,
@ -367,6 +396,9 @@ func TestSqlQuery(t *testing.T) {
), ),
).RowsWillBeClosed() ).RowsWillBeClosed()
mockGetReleaseCustomLabels(mock, "", supersededRelease.Namespace, supersededRelease.Labels)
mockGetReleaseCustomLabels(mock, "", deployedRelease.Namespace, deployedRelease.Labels)
_, err := sqlDriver.Query(labelSetUnknown) _, err := sqlDriver.Query(labelSetUnknown)
if err == nil { if err == nil {
t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound) t.Errorf("Expected error {%v}, got nil", ErrReleaseNotFound)
@ -447,6 +479,20 @@ func TestSqlDelete(t *testing.T) {
ExpectExec(regexp.QuoteMeta(deleteQuery)). ExpectExec(regexp.QuoteMeta(deleteQuery)).
WithArgs(key, namespace). WithArgs(key, namespace).
WillReturnResult(sqlmock.NewResult(0, 1)) WillReturnResult(sqlmock.NewResult(0, 1))
mockGetReleaseCustomLabels(mock, key, namespace, rel.Labels)
deleteLabelsQuery := fmt.Sprintf(
"DELETE FROM %s WHERE %s = $1 AND %s = $2",
sqlCustomLabelsTableName,
sqlCustomLabelsTableReleaseKeyColumn,
sqlCustomLabelsTableReleaseNamespaceColumn,
)
mock.
ExpectExec(regexp.QuoteMeta(deleteLabelsQuery)).
WithArgs(key, namespace).
WillReturnResult(sqlmock.NewResult(0, 1))
mock.ExpectCommit() mock.ExpectCommit()
deletedRelease, err := sqlDriver.Delete(key) deletedRelease, err := sqlDriver.Delete(key)
@ -461,3 +507,26 @@ func TestSqlDelete(t *testing.T) {
t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease) t.Errorf("Expected release {%v}, got {%v}", rel, deletedRelease)
} }
} }
func mockGetReleaseCustomLabels(mock sqlmock.Sqlmock, key string, namespace string, labels map[string]string) {
query := fmt.Sprintf(
regexp.QuoteMeta("SELECT %s, %s FROM %s WHERE %s = $1 AND %s = $2"),
sqlCustomLabelsTableKeyColumn,
sqlCustomLabelsTableValueColumn,
sqlCustomLabelsTableName,
sqlCustomLabelsTableReleaseKeyColumn,
sqlCustomLabelsTableReleaseNamespaceColumn,
)
eq := mock.ExpectQuery(query).
WithArgs(key, namespace)
returnRows := mock.NewRows([]string{
sqlCustomLabelsTableKeyColumn,
sqlCustomLabelsTableValueColumn,
})
for k, v := range labels {
returnRows.AddRow(k, v)
}
eq.WillReturnRows(returnRows).RowsWillBeClosed()
}

@ -30,6 +30,8 @@ var b64 = base64.StdEncoding
var magicGzip = []byte{0x1f, 0x8b, 0x08} var magicGzip = []byte{0x1f, 0x8b, 0x08}
var systemLabels = []string{"name", "owner", "status", "version", "createdAt", "modifiedAt"}
// encodeRelease encodes a release returning a base64 encoded // encodeRelease encodes a release returning a base64 encoded
// gzipped string representation, or error. // gzipped string representation, or error.
func encodeRelease(rls *rspb.Release) (string, error) { func encodeRelease(rls *rspb.Release) (string, error) {
@ -83,3 +85,38 @@ func decodeRelease(data string) (*rspb.Release, error) {
} }
return &rls, nil return &rls, nil
} }
// Checks if label is system
func isSystemLabel(key string) bool {
for _, v := range GetSystemLabels() {
if key == v {
return true
}
}
return false
}
// Removes system labels from labels map
func filterSystemLabels(lbs map[string]string) map[string]string {
result := make(map[string]string)
for k, v := range lbs {
if !isSystemLabel(k) {
result[k] = v
}
}
return result
}
// Checks if labels array contains system labels
func ContainsSystemLabels(lbs map[string]string) bool {
for k := range lbs {
if isSystemLabel(k) {
return true
}
}
return false
}
func GetSystemLabels() []string {
return systemLabels
}

@ -0,0 +1,108 @@
/*
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 driver
import (
"reflect"
"testing"
)
func TestGetSystemLabel(t *testing.T) {
if output := GetSystemLabels(); !reflect.DeepEqual(systemLabels, output) {
t.Errorf("Expected {%v}, got {%v}", systemLabels, output)
}
}
func TestIsSystemLabel(t *testing.T) {
tests := map[string]bool{
"name": true,
"owner": true,
"test": false,
"NaMe": false,
}
for label, result := range tests {
if output := isSystemLabel(label); output != result {
t.Errorf("Output %t not equal to expected %t", output, result)
}
}
}
func TestFilterSystemLabels(t *testing.T) {
var tests = [][2]map[string]string{
{nil, map[string]string{}},
{map[string]string{}, map[string]string{}},
{map[string]string{
"name": "name",
"owner": "owner",
"status": "status",
"version": "version",
"createdAt": "createdAt",
"modifiedAt": "modifiedAt",
}, map[string]string{}},
{map[string]string{
"StaTus": "status",
"name": "name",
"owner": "owner",
"key": "value",
}, map[string]string{
"StaTus": "status",
"key": "value",
}},
{map[string]string{
"key1": "value1",
"key2": "value2",
}, map[string]string{
"key1": "value1",
"key2": "value2",
}},
}
for _, test := range tests {
if output := filterSystemLabels(test[0]); !reflect.DeepEqual(test[1], output) {
t.Errorf("Expected {%v}, got {%v}", test[1], output)
}
}
}
func TestContainsSystemLabels(t *testing.T) {
var tests = []struct {
input map[string]string
output bool
}{
{nil, false},
{map[string]string{}, false},
{map[string]string{
"name": "name",
"owner": "owner",
"status": "status",
"version": "version",
"createdAt": "createdAt",
"modifiedAt": "modifiedAt",
}, true},
{map[string]string{
"StaTus": "status",
"name": "name",
"owner": "owner",
"key": "value",
}, true},
{map[string]string{
"key1": "value1",
"key2": "value2",
}, false},
}
for _, test := range tests {
if output := ContainsSystemLabels(test.input); !reflect.DeepEqual(test.output, output) {
t.Errorf("Expected {%v}, got {%v}", test.output, output)
}
}
}

@ -108,11 +108,17 @@ verifySupported() {
checkDesiredVersion() { checkDesiredVersion() {
if [ "x$DESIRED_VERSION" == "x" ]; then if [ "x$DESIRED_VERSION" == "x" ]; then
# Get tag from release URL # Get tag from release URL
local latest_release_url="https://github.com/helm/helm/releases" local latest_release_url="https://api.github.com/repos/helm/helm/releases/latest"
local latest_release_response=""
if [ "${HAS_CURL}" == "true" ]; then if [ "${HAS_CURL}" == "true" ]; then
TAG=$(curl -Ls $latest_release_url | grep 'href="/helm/helm/releases/tag/v3.[0-9]*.[0-9]*\"' | sed -E 's/.*\/helm\/helm\/releases\/tag\/(v[0-9\.]+)".*/\1/g' | head -1) latest_release_response=$( curl -L --silent --show-error --fail "$latest_release_url" 2>&1 || true )
elif [ "${HAS_WGET}" == "true" ]; then elif [ "${HAS_WGET}" == "true" ]; then
TAG=$(wget $latest_release_url -O - 2>&1 | grep 'href="/helm/helm/releases/tag/v3.[0-9]*.[0-9]*\"' | sed -E 's/.*\/helm\/helm\/releases\/tag\/(v[0-9\.]+)".*/\1/g' | head -1) latest_release_response=$( wget "$latest_release_url" -O - 2>&1 || true )
fi
TAG=$( echo "$latest_release_response" | grep '"tag_name"' | sed -E 's/.*"(v[0-9\.]+)".*/\1/g' )
if [ "x$TAG" == "x" ]; then
printf "Could not retrieve the latest release tag information from %s: %s\n" "${latest_release_url}" "${latest_release_response}"
exit 1
fi fi
else else
TAG=$DESIRED_VERSION TAG=$DESIRED_VERSION

Loading…
Cancel
Save