Merge remote-tracking branch 'helm/master'

pull/7767/head
liuming 4 years ago
commit e7adc4b5d7

@ -41,6 +41,7 @@ If you want to use a package manager:
- [Chocolatey](https://chocolatey.org/) users can use `choco install kubernetes-helm`.
- [Scoop](https://scoop.sh/) users can use `scoop install helm`.
- [GoFish](https://gofi.sh/) users can use `gofish install helm`.
- [Snapcraft](https://snapcraft.io/) users can use `snap install helm --classic`
To rapidly get Helm up and running, start with the [Quick Start Guide](https://docs.helm.sh/using_helm/#quickstart-guide).

@ -83,10 +83,29 @@ func newListCmd(cfg *action.Configuration, out io.Writer) *cobra.Command {
}
if client.Short {
names := make([]string, 0)
for _, res := range results {
fmt.Fprintln(out, res.Name)
names = append(names, res.Name)
}
outputFlag := cmd.Flag("output")
switch outputFlag.Value.String() {
case "json":
output.EncodeJSON(out, names)
return nil
case "yaml":
output.EncodeYAML(out, names)
return nil
case "table":
for _, res := range results {
fmt.Fprintln(out, res.Name)
}
return nil
default:
return outfmt.Write(out, newReleaseListWriter(results))
}
return nil
}
return outfmt.Write(out, newReleaseListWriter(results))

@ -198,6 +198,16 @@ func TestListCmd(t *testing.T) {
cmd: "list --short",
golden: "output/list-short.txt",
rels: releaseFixture,
}, {
name: "list releases in short output format",
cmd: "list --short --output yaml",
golden: "output/list-short-yaml.txt",
rels: releaseFixture,
}, {
name: "list releases in short output format",
cmd: "list --short --output json",
golden: "output/list-short-json.txt",
rels: releaseFixture,
}, {
name: "list superseded releases",
cmd: "list --superseded",

@ -77,11 +77,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowAll
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := runShow(args, client)
if err != nil {
return err
}
@ -97,11 +93,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowValues
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := runShow(args, client)
if err != nil {
return err
}
@ -117,11 +109,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowChart
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := runShow(args, client)
if err != nil {
return err
}
@ -137,11 +125,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
Args: require.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
client.OutputFormat = action.ShowReadme
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return err
}
output, err := client.Run(cp)
output, err := runShow(args, client)
if err != nil {
return err
}
@ -152,8 +136,7 @@ func newShowCmd(out io.Writer) *cobra.Command {
cmds := []*cobra.Command{all, readmeSubCmd, valuesSubCmd, chartSubCmd}
for _, subCmd := range cmds {
addChartPathOptionsFlags(subCmd.Flags(), &client.ChartPathOptions)
showCommand.AddCommand(subCmd)
addShowFlags(showCommand, subCmd, client)
// Register the completion function for each subcommand
completion.RegisterValidArgsFunc(subCmd, validArgsFunc)
@ -161,3 +144,25 @@ func newShowCmd(out io.Writer) *cobra.Command {
return showCommand
}
func addShowFlags(showCmd *cobra.Command, subCmd *cobra.Command, client *action.Show) {
f := subCmd.Flags()
f.BoolVar(&client.Devel, "devel", false, "use development versions, too. Equivalent to version '>0.0.0-0'. If --version is set, this is ignored")
addChartPathOptionsFlags(f, &client.ChartPathOptions)
showCmd.AddCommand(subCmd)
}
func runShow(args []string, client *action.Show) (string, error) {
debug("Original chart version: %q", client.Version)
if client.Version == "" && client.Devel {
debug("setting version to >0.0.0-0")
client.Version = ">0.0.0-0"
}
cp, err := client.ChartPathOptions.LocateChart(args[0], settings)
if err != nil {
return "", err
}
return client.Run(cp)
}

@ -0,0 +1,82 @@
/*
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 (
"fmt"
"path/filepath"
"strings"
"testing"
"helm.sh/helm/v3/pkg/repo/repotest"
)
func TestShowPreReleaseChart(t *testing.T) {
srv, err := repotest.NewTempServer("testdata/testcharts/*.tgz*")
if err != nil {
t.Fatal(err)
}
defer srv.Stop()
if err := srv.LinkIndices(); err != nil {
t.Fatal(err)
}
tests := []struct {
name string
args string
flags string
fail bool
expectedErr string
}{
{
name: "show pre-release chart",
args: "test/pre-release-chart",
fail: true,
expectedErr: "failed to download \"test/pre-release-chart\"",
},
{
name: "show pre-release chart with 'devel' flag",
args: "test/pre-release-chart",
flags: "--devel",
fail: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
outdir := srv.Root()
cmd := fmt.Sprintf("show all '%s' %s --repository-config %s --repository-cache %s",
tt.args,
tt.flags,
filepath.Join(outdir, "repositories.yaml"),
outdir,
)
//_, out, err := executeActionCommand(cmd)
_, _, err := executeActionCommand(cmd)
if err != nil {
if tt.fail {
if !strings.Contains(err.Error(), tt.expectedErr) {
t.Errorf("%q expected error: %s, got: %s", tt.name, tt.expectedErr, err.Error())
}
return
}
t.Errorf("%q reported error: %s", tt.name, err)
}
})
}
}

@ -0,0 +1 @@
["hummingbird","iguana","rocket","starlord"]

@ -0,0 +1,4 @@
- hummingbird
- iguana
- rocket
- starlord

@ -38,7 +38,8 @@ func (cfg *Configuration) execHook(rl *release.Release, hook release.HookEvent,
}
}
sort.Sort(hookByWeight(executingHooks))
// hooke are pre-ordered by kind, so keep order stable
sort.Stable(hookByWeight(executingHooks))
for _, h := range executingHooks {
// Set default delete policy to before-hook-creation

@ -41,6 +41,7 @@ import (
"helm.sh/helm/v3/pkg/downloader"
"helm.sh/helm/v3/pkg/engine"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/kube"
kubefake "helm.sh/helm/v3/pkg/kube/fake"
"helm.sh/helm/v3/pkg/postrender"
"helm.sh/helm/v3/pkg/release"
@ -246,11 +247,18 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
// Mark this release as in-progress
rel.SetStatus(release.StatusPendingInstall, "Initial install underway")
var toBeAdopted kube.ResourceList
resources, err := i.cfg.KubeClient.Build(bytes.NewBufferString(rel.Manifest), !i.DisableOpenAPIValidation)
if err != nil {
return nil, errors.Wrap(err, "unable to build kubernetes objects from release manifest")
}
// It is safe to use "force" here because these are resources currently rendered by the chart.
err = resources.Visit(setMetadataVisitor(rel.Name, rel.Namespace, true))
if err != nil {
return nil, err
}
// Install requires an extra validation step of checking that resources
// don't already exist before we actually create resources. If we continue
// forward and create the release object with resources that already exist,
@ -258,7 +266,8 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
// deleting the release because the manifest will be pointing at that
// resource
if !i.ClientOnly && !isUpgrade {
if err := existingResourceConflict(resources); err != nil {
toBeAdopted, err = existingResourceConflict(resources, rel.Name, rel.Namespace)
if err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with install")
}
}
@ -321,8 +330,14 @@ func (i *Install) Run(chrt *chart.Chart, vals map[string]interface{}) (*release.
// At this point, we can do the install. Note that before we were detecting whether to
// do an update, but it's not clear whether we WANT to do an update if the re-use is set
// to true, since that is basically an upgrade operation.
if _, err := i.cfg.KubeClient.Create(resources); err != nil {
return i.failRelease(rel, err)
if len(toBeAdopted) == 0 {
if _, err := i.cfg.KubeClient.Create(resources); err != nil {
return i.failRelease(rel, err)
}
} else {
if _, err := i.cfg.KubeClient.Update(toBeAdopted, resources, false); err != nil {
return i.failRelease(rel, err)
}
}
if i.Wait {

@ -51,8 +51,9 @@ func (o ShowOutputFormat) String() string {
//
// It provides the implementation of 'helm show' and its respective subcommands.
type Show struct {
OutputFormat ShowOutputFormat
ChartPathOptions
Devel bool
OutputFormat ShowOutputFormat
}
// NewShow creates a new Show object with the given configuration.

@ -209,6 +209,12 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
return upgradedRelease, errors.Wrap(err, "unable to build kubernetes objects from new release manifest")
}
// It is safe to use force only on target because these are resources currently rendered by the chart.
err = target.Visit(setMetadataVisitor(upgradedRelease.Name, upgradedRelease.Namespace, true))
if err != nil {
return upgradedRelease, err
}
// Do a basic diff using gvk + name to figure out what new resources are being created so we can validate they don't already exist
existingResources := make(map[string]bool)
for _, r := range current {
@ -222,10 +228,19 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea
}
}
if err := existingResourceConflict(toBeCreated); err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a new resource that already exists. Unable to continue with update")
toBeUpdated, err := existingResourceConflict(toBeCreated, upgradedRelease.Name, upgradedRelease.Namespace)
if err != nil {
return nil, errors.Wrap(err, "rendered manifests contain a resource that already exists. Unable to continue with update")
}
toBeUpdated.Visit(func(r *resource.Info, err error) error {
if err != nil {
return err
}
current.Append(r)
return nil
})
if u.DryRun {
u.cfg.Log("dry run for %s", upgradedRelease.Name)
if len(u.Description) > 0 {

@ -21,12 +21,25 @@ import (
"github.com/pkg/errors"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/resource"
"helm.sh/helm/v3/pkg/kube"
)
func existingResourceConflict(resources kube.ResourceList) error {
var accessor = meta.NewAccessor()
const (
appManagedByLabel = "app.kubernetes.io/managed-by"
appManagedByHelm = "Helm"
helmReleaseNameAnnotation = "meta.helm.sh/release-name"
helmReleaseNamespaceAnnotation = "meta.helm.sh/release-namespace"
)
func existingResourceConflict(resources kube.ResourceList, releaseName, releaseNamespace string) (kube.ResourceList, error) {
var requireUpdate kube.ResourceList
err := resources.Visit(func(info *resource.Info, err error) error {
if err != nil {
return err
@ -38,11 +51,134 @@ func existingResourceConflict(resources kube.ResourceList) error {
if apierrors.IsNotFound(err) {
return nil
}
return errors.Wrap(err, "could not get information about the resource")
}
return fmt.Errorf("existing resource conflict: namespace: %s, name: %s, existing_kind: %s, new_kind: %s", info.Namespace, info.Name, existing.GetObjectKind().GroupVersionKind(), info.Mapping.GroupVersionKind)
// Allow adoption of the resource if it is managed by Helm and is annotated with correct release name and namespace.
if err := checkOwnership(existing, releaseName, releaseNamespace); err != nil {
return fmt.Errorf("%s exists and cannot be imported into the current release: %s", resourceString(info), err)
}
requireUpdate.Append(info)
return nil
})
return err
return requireUpdate, err
}
func checkOwnership(obj runtime.Object, releaseName, releaseNamespace string) error {
lbls, err := accessor.Labels(obj)
if err != nil {
return err
}
annos, err := accessor.Annotations(obj)
if err != nil {
return err
}
var errs []error
if err := requireValue(lbls, appManagedByLabel, appManagedByHelm); err != nil {
errs = append(errs, fmt.Errorf("label validation error: %s", err))
}
if err := requireValue(annos, helmReleaseNameAnnotation, releaseName); err != nil {
errs = append(errs, fmt.Errorf("annotation validation error: %s", err))
}
if err := requireValue(annos, helmReleaseNamespaceAnnotation, releaseNamespace); err != nil {
errs = append(errs, fmt.Errorf("annotation validation error: %s", err))
}
if len(errs) > 0 {
err := errors.New("invalid ownership metadata")
for _, e := range errs {
err = fmt.Errorf("%w; %s", err, e)
}
return err
}
return nil
}
func requireValue(meta map[string]string, k, v string) error {
actual, ok := meta[k]
if !ok {
return fmt.Errorf("missing key %q: must be set to %q", k, v)
}
if actual != v {
return fmt.Errorf("key %q must equal %q: current value is %q", k, v, actual)
}
return nil
}
// setMetadataVisitor adds release tracking metadata to all resources. If force is enabled, existing
// ownership metadata will be overwritten. Otherwise an error will be returned if any resource has an
// existing and conflicting value for the managed by label or Helm release/namespace annotations.
func setMetadataVisitor(releaseName, releaseNamespace string, force bool) resource.VisitorFunc {
return func(info *resource.Info, err error) error {
if err != nil {
return err
}
if !force {
if err := checkOwnership(info.Object, releaseName, releaseNamespace); err != nil {
return fmt.Errorf("%s cannot be owned: %s", resourceString(info), err)
}
}
if err := mergeLabels(info.Object, map[string]string{
appManagedByLabel: appManagedByHelm,
}); err != nil {
return fmt.Errorf(
"%s labels could not be updated: %s",
resourceString(info), err,
)
}
if err := mergeAnnotations(info.Object, map[string]string{
helmReleaseNameAnnotation: releaseName,
helmReleaseNamespaceAnnotation: releaseNamespace,
}); err != nil {
return fmt.Errorf(
"%s annotations could not be updated: %s",
resourceString(info), err,
)
}
return nil
}
}
func resourceString(info *resource.Info) string {
_, k := info.Mapping.GroupVersionKind.ToAPIVersionAndKind()
return fmt.Sprintf(
"%s %q in namespace %q",
k, info.Name, info.Namespace,
)
}
func mergeLabels(obj runtime.Object, labels map[string]string) error {
current, err := accessor.Labels(obj)
if err != nil {
return err
}
return accessor.SetLabels(obj, mergeStrStrMaps(current, labels))
}
func mergeAnnotations(obj runtime.Object, annotations map[string]string) error {
current, err := accessor.Annotations(obj)
if err != nil {
return err
}
return accessor.SetAnnotations(obj, mergeStrStrMaps(current, annotations))
}
// merge two maps, always taking the value on the right
func mergeStrStrMaps(current, desired map[string]string) map[string]string {
result := make(map[string]string)
for k, v := range current {
result[k] = v
}
for k, desiredVal := range desired {
result[k] = desiredVal
}
return result
}

@ -0,0 +1,123 @@
/*
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 (
"testing"
"helm.sh/helm/v3/pkg/kube"
appsv1 "k8s.io/api/apps/v1"
"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/api/meta"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/cli-runtime/pkg/resource"
)
func newDeploymentResource(name, namespace string) *resource.Info {
return &resource.Info{
Name: name,
Mapping: &meta.RESTMapping{
Resource: schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployment"},
GroupVersionKind: schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
},
Object: &appsv1.Deployment{
ObjectMeta: v1.ObjectMeta{
Name: name,
Namespace: namespace,
},
},
}
}
func TestCheckOwnership(t *testing.T) {
deployFoo := newDeploymentResource("foo", "ns-a")
// Verify that a resource that lacks labels/annotations is not owned
err := checkOwnership(deployFoo.Object, "rel-a", "ns-a")
assert.EqualError(t, err, `invalid ownership metadata; label validation error: missing key "app.kubernetes.io/managed-by": must be set to "Helm"; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
// Set managed by label and verify annotation error message
_ = accessor.SetLabels(deployFoo.Object, map[string]string{
appManagedByLabel: appManagedByHelm,
})
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a")
assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-name": must be set to "rel-a"; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
// Set only the release name annotation and verify missing release namespace error message
_ = accessor.SetAnnotations(deployFoo.Object, map[string]string{
helmReleaseNameAnnotation: "rel-a",
})
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a")
assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: missing key "meta.helm.sh/release-namespace": must be set to "ns-a"`)
// Set both release name and namespace annotations and verify no ownership errors
_ = accessor.SetAnnotations(deployFoo.Object, map[string]string{
helmReleaseNameAnnotation: "rel-a",
helmReleaseNamespaceAnnotation: "ns-a",
})
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a")
assert.NoError(t, err)
// Verify ownership error for wrong release name
err = checkOwnership(deployFoo.Object, "rel-b", "ns-a")
assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-name" must equal "rel-b": current value is "rel-a"`)
// Verify ownership error for wrong release namespace
err = checkOwnership(deployFoo.Object, "rel-a", "ns-b")
assert.EqualError(t, err, `invalid ownership metadata; annotation validation error: key "meta.helm.sh/release-namespace" must equal "ns-b": current value is "ns-a"`)
// Verify ownership error for wrong manager label
_ = accessor.SetLabels(deployFoo.Object, map[string]string{
appManagedByLabel: "helm",
})
err = checkOwnership(deployFoo.Object, "rel-a", "ns-a")
assert.EqualError(t, err, `invalid ownership metadata; label validation error: key "app.kubernetes.io/managed-by" must equal "Helm": current value is "helm"`)
}
func TestSetMetadataVisitor(t *testing.T) {
var (
err error
deployFoo = newDeploymentResource("foo", "ns-a")
deployBar = newDeploymentResource("bar", "ns-a-system")
resources = kube.ResourceList{deployFoo, deployBar}
)
// Set release tracking metadata and verify no error
err = resources.Visit(setMetadataVisitor("rel-a", "ns-a", true))
assert.NoError(t, err)
// Verify that release "b" cannot take ownership of "a"
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false))
assert.Error(t, err)
// Force release "b" to take ownership
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", true))
assert.NoError(t, err)
// Check that there is now no ownership error when setting metadata without force
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false))
assert.NoError(t, err)
// Add a new resource that is missing ownership metadata and verify error
resources.Append(newDeploymentResource("baz", "default"))
err = resources.Visit(setMetadataVisitor("rel-b", "ns-a", false))
assert.Error(t, err)
assert.Contains(t, err.Error(), `Deployment "baz" in namespace "" cannot be owned`)
}

@ -72,6 +72,31 @@ type ChartDownloader struct {
RepositoryCache string
}
// atomicWriteFile atomically (as atomic as os.Rename allows) writes a file to a
// disk.
func atomicWriteFile(filename string, body io.Reader, mode os.FileMode) error {
tempFile, err := ioutil.TempFile(filepath.Split(filename))
if err != nil {
return err
}
tempName := tempFile.Name()
if _, err := io.Copy(tempFile, body); err != nil {
tempFile.Close() // return value is ignored as we are already on error path
return err
}
if err := tempFile.Close(); err != nil {
return err
}
if err := os.Chmod(tempName, mode); err != nil {
return err
}
return os.Rename(tempName, filename)
}
// DownloadTo retrieves a chart. Depending on the settings, it may also download a provenance file.
//
// If Verify is set to VerifyNever, the verification will be nil.
@ -101,7 +126,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
name := filepath.Base(u.Path)
destfile := filepath.Join(dest, name)
if err := ioutil.WriteFile(destfile, data.Bytes(), 0644); err != nil {
if err := atomicWriteFile(destfile, data, 0644); err != nil {
return destfile, nil, err
}
@ -117,7 +142,7 @@ func (c *ChartDownloader) DownloadTo(ref, version, dest string) (string, *proven
return destfile, ver, nil
}
provfile := destfile + ".prov"
if err := ioutil.WriteFile(provfile, body.Bytes(), 0644); err != nil {
if err := atomicWriteFile(provfile, body, 0644); err != nil {
return destfile, nil, err
}

@ -93,7 +93,7 @@ func TestBadValues(t *testing.T) {
if len(m) < 1 {
t.Fatalf("All didn't fail with expected errors, got %#v", m)
}
if !strings.Contains(m[0].Err.Error(), "cannot unmarshal") {
if !strings.Contains(m[0].Err.Error(), "unable to parse YAML") {
t.Errorf("All didn't have the error for invalid key format: %s", m[0].Err)
}
}

@ -0,0 +1,37 @@
/*
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 rules
import (
"os"
"path/filepath"
"testing"
)
var (
nonExistingValuesFilePath = filepath.Join("/fake/dir", "values.yaml")
)
func TestValidateValuesYamlNotDirectory(t *testing.T) {
_ = os.Mkdir(nonExistingValuesFilePath, os.ModePerm)
defer os.Remove(nonExistingValuesFilePath)
err := validateValuesFileExistence(nonExistingValuesFilePath)
if err == nil {
t.Errorf("validateValuesFileExistence to return a linter error, got no error")
}
}

@ -16,7 +16,11 @@ limitations under the License.
package releaseutil
import "sort"
import (
"sort"
"helm.sh/helm/v3/pkg/release"
)
// KindSortOrder is an ordering of Kinds.
type KindSortOrder []string
@ -99,46 +103,42 @@ var UninstallOrder KindSortOrder = []string{
"Namespace",
}
// sortByKind does an in-place sort of manifests by Kind.
// sort manifests by kind.
//
// Results are sorted by 'ordering', keeping order of items with equal kind/priority
func sortByKind(manifests []Manifest, ordering KindSortOrder) []Manifest {
ks := newKindSorter(manifests, ordering)
sort.Stable(ks)
return ks.manifests
}
func sortManifestsByKind(manifests []Manifest, ordering KindSortOrder) []Manifest {
sort.SliceStable(manifests, func(i, j int) bool {
return lessByKind(manifests[i], manifests[j], manifests[i].Head.Kind, manifests[j].Head.Kind, ordering)
})
type kindSorter struct {
ordering map[string]int
manifests []Manifest
return manifests
}
func newKindSorter(m []Manifest, s KindSortOrder) *kindSorter {
o := make(map[string]int, len(s))
for v, k := range s {
o[k] = v
}
// sort hooks by kind, using an out-of-place sort to preserve the input parameters.
//
// Results are sorted by 'ordering', keeping order of items with equal kind/priority
func sortHooksByKind(hooks []*release.Hook, ordering KindSortOrder) []*release.Hook {
h := hooks
sort.SliceStable(h, func(i, j int) bool {
return lessByKind(h[i], h[j], h[i].Kind, h[j].Kind, ordering)
})
return &kindSorter{
manifests: m,
ordering: o,
}
return h
}
func (k *kindSorter) Len() int { return len(k.manifests) }
func (k *kindSorter) Swap(i, j int) { k.manifests[i], k.manifests[j] = k.manifests[j], k.manifests[i] }
func lessByKind(a interface{}, b interface{}, kindA string, kindB string, o KindSortOrder) bool {
ordering := make(map[string]int, len(o))
for v, k := range o {
ordering[k] = v
}
func (k *kindSorter) Less(i, j int) bool {
a := k.manifests[i]
b := k.manifests[j]
first, aok := k.ordering[a.Head.Kind]
second, bok := k.ordering[b.Head.Kind]
first, aok := ordering[kindA]
second, bok := ordering[kindB]
if !aok && !bok {
// if both are unknown then sort alphabetically by kind, keep original order if same kind
if a.Head.Kind != b.Head.Kind {
return a.Head.Kind < b.Head.Kind
if kindA != kindB {
return kindA < kindB
}
return first < second
}

@ -19,6 +19,8 @@ package releaseutil
import (
"bytes"
"testing"
"helm.sh/helm/v3/pkg/release"
)
func TestKindSorter(t *testing.T) {
@ -175,12 +177,18 @@ func TestKindSorter(t *testing.T) {
t.Fatalf("Expected %d names in order, got %d", want, got)
}
defer buf.Reset()
for _, r := range sortByKind(manifests, test.order) {
orig := manifests
for _, r := range sortManifestsByKind(manifests, test.order) {
buf.WriteString(r.Name)
}
if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got)
}
for i, manifest := range orig {
if manifest != manifests[i] {
t.Fatal("Expected input to sortManifestsByKind to stay the same")
}
}
})
}
}
@ -236,7 +244,7 @@ func TestKindSorterKeepOriginalOrder(t *testing.T) {
var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) {
defer buf.Reset()
for _, r := range sortByKind(manifests, test.order) {
for _, r := range sortManifestsByKind(manifests, test.order) {
buf.WriteString(r.Name)
}
if got := buf.String(); got != test.expected {
@ -257,7 +265,7 @@ func TestKindSorterNamespaceAgainstUnknown(t *testing.T) {
}
manifests := []Manifest{unknown, namespace}
sortByKind(manifests, InstallOrder)
manifests = sortManifestsByKind(manifests, InstallOrder)
expectedOrder := []Manifest{namespace, unknown}
for i, manifest := range manifests {
@ -266,3 +274,54 @@ func TestKindSorterNamespaceAgainstUnknown(t *testing.T) {
}
}
}
// test hook sorting with a small subset of kinds, since it uses the same algorithm as sortManifestsByKind
func TestKindSorterForHooks(t *testing.T) {
hooks := []*release.Hook{
{
Name: "i",
Kind: "ClusterRole",
},
{
Name: "j",
Kind: "ClusterRoleBinding",
},
{
Name: "c",
Kind: "LimitRange",
},
{
Name: "a",
Kind: "Namespace",
},
}
for _, test := range []struct {
description string
order KindSortOrder
expected string
}{
{"install", InstallOrder, "acij"},
{"uninstall", UninstallOrder, "jica"},
} {
var buf bytes.Buffer
t.Run(test.description, func(t *testing.T) {
if got, want := len(test.expected), len(hooks); got != want {
t.Fatalf("Expected %d names in order, got %d", want, got)
}
defer buf.Reset()
orig := hooks
for _, r := range sortHooksByKind(hooks, test.order) {
buf.WriteString(r.Name)
}
for i, hook := range orig {
if hook != hooks[i] {
t.Fatal("Expected input to sortHooksByKind to stay the same")
}
}
if got := buf.String(); got != test.expected {
t.Errorf("Expected %q, got %q", test.expected, got)
}
})
}
}

@ -108,7 +108,7 @@ func SortManifests(files map[string]string, apis chartutil.VersionSet, ordering
}
}
return result.hooks, sortByKind(result.generic, ordering), nil
return sortHooksByKind(result.hooks, ordering), sortManifestsByKind(result.generic, ordering), nil
}
// sort takes a manifestFile object which may contain multiple resource definition

@ -219,7 +219,7 @@ metadata:
}
}
sorted = sortByKind(sorted, InstallOrder)
sorted = sortManifestsByKind(sorted, InstallOrder)
for i, m := range generic {
if m.Content != sorted[i].Content {
t.Errorf("Expected %q, got %q", m.Content, sorted[i].Content)

@ -79,3 +79,30 @@ func TestSortByRevision(t *testing.T) {
return vi < vj
})
}
func TestReverseSortByName(t *testing.T) {
Reverse(releases, SortByName)
check(t, "ByName", func(i, j int) bool {
ni := releases[i].Name
nj := releases[j].Name
return ni > nj
})
}
func TestReverseSortByDate(t *testing.T) {
Reverse(releases, SortByDate)
check(t, "ByDate", func(i, j int) bool {
ti := releases[i].Info.LastDeployed.Second()
tj := releases[j].Info.LastDeployed.Second()
return ti > tj
})
}
func TestReverseSortByRevision(t *testing.T) {
Reverse(releases, SortByRevision)
check(t, "ByRevision", func(i, j int) bool {
vi := releases[i].Version
vj := releases[j].Version
return vi > vj
})
}

@ -201,11 +201,6 @@ func (cfgmaps *ConfigMaps) Update(key string, rls *rspb.Release) error {
func (cfgmaps *ConfigMaps) Delete(key string) (rls *rspb.Release, err error) {
// fetch the release to check existence
if rls, err = cfgmaps.Get(key); err != nil {
if apierrors.IsNotFound(err) {
return nil, ErrReleaseExists
}
cfgmaps.Log("delete: failed to get release %q: %s", key, err)
return nil, err
}
// delete the release

@ -194,6 +194,12 @@ func TestConfigMapDelete(t *testing.T) {
cfgmaps := newTestFixtureCfgMaps(t, []*rspb.Release{rel}...)
// perform the delete on a non-existent release
_, err := cfgmaps.Delete("nonexistent")
if err != ErrReleaseNotFound {
t.Fatalf("Expected ErrReleaseNotFound: got {%v}", err)
}
// perform the delete
rls, err := cfgmaps.Delete(key)
if err != nil {

@ -185,11 +185,7 @@ func (secrets *Secrets) Update(key string, rls *rspb.Release) error {
func (secrets *Secrets) Delete(key string) (rls *rspb.Release, err error) {
// fetch the release to check existence
if rls, err = secrets.Get(key); err != nil {
if apierrors.IsNotFound(err) {
return nil, ErrReleaseExists
}
return nil, errors.Wrapf(err, "delete: failed to get release %q", key)
return nil, err
}
// delete the release
err = secrets.impl.Delete(key, &metav1.DeleteOptions{})

@ -194,6 +194,12 @@ func TestSecretDelete(t *testing.T) {
secrets := newTestFixtureSecrets(t, []*rspb.Release{rel}...)
// perform the delete on a non-existing release
_, err := secrets.Delete("nonexistent")
if err != ErrReleaseNotFound {
t.Fatalf("Expected ErrReleaseNotFound, got: {%v}", err)
}
// perform the delete
rls, err := secrets.Delete(key)
if err != nil {

@ -81,14 +81,14 @@ The community keeps growing, and we'd love to see you there!
Download Helm ${RELEASE}. The common platform binaries are here:
- [MacOS amd64](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256))
- [Linux amd64](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-amd64.tar.gz.sha256))
- [Linux arm](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-arm.tar.gz.sha256))
- [Linux arm64](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-arm64.tar.gz.sha256))
- [Linux i386](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-386.tar.gz.sha256))
- [Linux ppc64le](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256))
- [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256))
- [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256))
- [MacOS amd64](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-darwin-amd64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256))
- [Linux amd64](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-amd64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-amd64.tar.gz.sha256))
- [Linux arm](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-arm.tar.gz.sha256))
- [Linux arm64](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-arm64.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-arm64.tar.gz.sha256))
- [Linux i386](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-386.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-386.tar.gz.sha256))
- [Linux ppc64le](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-linux-ppc64le.tar.gz.sha256))
- [Linux s390x](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz) ([checksum](https://get.helm.sh/helm-${RELEASE}-linux-s390x.tar.gz.sha256sum) / $(cat _dist/helm-${RELEASE}-darwin-amd64.tar.gz.sha256))
- [Windows amd64](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip) ([checksum](https://get.helm.sh/helm-${RELEASE}-windows-amd64.zip.sha256sum) / $(cat _dist/helm-${RELEASE}-windows-amd64.zip.sha256))
The [Quickstart Guide](https://docs.helm.sh/using_helm/#quickstart-guide) will get you going from there. For **upgrade instructions** or detailed installation notes, check the [install guide](https://docs.helm.sh/using_helm/#installing-helm). You can also use a [script to install](https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3) on any system with \`bash\`.

Loading…
Cancel
Save