From 4fdcedc172154ba34f99fb54eedcc52b6a9ad60d Mon Sep 17 00:00:00 2001 From: Daniel Strobusch <1847260+dastrobu@users.noreply.github.com> Date: Fri, 17 Jan 2020 19:20:17 +0100 Subject: [PATCH] feat(helm): add recreate upgrade (rollback) strategy An additional optional flag --recreate can be passed on upgrade (or rollback) of a release. In combination with the --force flag the following strategies are employed when updating a resource (which can be directly compared to kubectl): helm kubectl action on 'invalid' or 'conflict' -------------------------------------------------------------------------------------------------------------- upgrade apply PATCH fail upgrade --force replace PUT fail upgrade --recreate apply --force PATCH DELETE -> GET (poll) -> POST upgrade --recreate --force replace --force DELETE -> GET (poll) -> POST fail The 'on error' column should be interpreted as follows. The server responds with 'invalid' e.g. if a certain resource contains an immutable field, which cannot be patched or updated by a PUT. In theses cases it can be helpful to delete the resource and recreate it. Examples for theses cases are: * roleRef in RoleBinding * spec.clusterIP in Service * parameters in StorageClass Closes #7082. Signed-off-by: Daniel Strobusch <1847260+dastrobu@users.noreply.github.com> --- cmd/helm/rollback.go | 3 +- cmd/helm/upgrade.go | 3 +- pkg/action/action.go | 4 + pkg/action/action_test.go | 19 +- pkg/action/rollback.go | 47 +- pkg/action/rollback_test.go | 80 ++ pkg/action/upgrade.go | 29 +- pkg/action/upgrade_test.go | 39 + pkg/kube/client.go | 92 +- pkg/kube/client_test.go | 229 +++++ pkg/kube/fake/fake.go | 10 + pkg/kube/fake/printer.go | 7 + pkg/kube/fake/spy.go | 102 +++ pkg/kube/interface.go | 23 +- scripts/completions.bash | 1636 +++++++++++++++++++++++++++++++++++ 15 files changed, 2272 insertions(+), 51 deletions(-) create mode 100644 pkg/action/rollback_test.go create mode 100644 pkg/kube/fake/spy.go create mode 100644 scripts/completions.bash diff --git a/cmd/helm/rollback.go b/cmd/helm/rollback.go index ea4b75cb1..c525ef46a 100644 --- a/cmd/helm/rollback.go +++ b/cmd/helm/rollback.go @@ -78,7 +78,8 @@ func newRollbackCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f := cmd.Flags() f.BoolVar(&client.DryRun, "dry-run", false, "simulate a rollback") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") - f.BoolVar(&client.Force, "force", false, "force resource update through delete/recreate if needed") + f.BoolVar(&client.Force, "force", false, "force resources to be replaced by rendered templates on update") + f.BoolVar(&client.RecreateResources, "recreate", false, "force resources to be replaced by rendered templates on update") f.BoolVar(&client.DisableHooks, "no-hooks", false, "prevent hooks from running during rollback") f.DurationVar(&client.Timeout, "timeout", 300*time.Second, "time to wait for any individual Kubernetes operation (like Jobs for hooks)") f.BoolVar(&client.Wait, "wait", false, "if set, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment, StatefulSet, or ReplicaSet are in a ready state before marking the release as successful. It will wait for as long as --timeout") diff --git a/cmd/helm/upgrade.go b/cmd/helm/upgrade.go index 3bd392d1d..283f371e5 100644 --- a/cmd/helm/upgrade.go +++ b/cmd/helm/upgrade.go @@ -194,7 +194,8 @@ func newUpgradeCmd(cfg *action.Configuration, out io.Writer) *cobra.Command { f.BoolVar(&client.DryRun, "dry-run", false, "simulate an upgrade") f.BoolVar(&client.Recreate, "recreate-pods", false, "performs pods restart for the resource if applicable") f.MarkDeprecated("recreate-pods", "functionality will no longer be updated. Consult the documentation for other methods to recreate pods") - f.BoolVar(&client.Force, "force", false, "force resource updates through a replacement strategy") + f.BoolVar(&client.Force, "force", false, "force resources to be replaced by rendered templates on update") + f.BoolVar(&client.RecreateResources, "recreate", false, "force resources to be replaced by rendered templates on update") f.BoolVar(&client.DisableHooks, "no-hooks", false, "disable pre/post upgrade hooks") f.BoolVar(&client.DisableOpenAPIValidation, "disable-openapi-validation", false, "if set, the upgrade process will not validate rendered templates against the Kubernetes OpenAPI Schema") f.BoolVar(&client.SkipCRDs, "skip-crds", false, "if set, no CRDs will be installed when an upgrade is performed with install flag enabled. By default, CRDs are installed if not already present, when an upgrade is performed with install flag enabled") diff --git a/pkg/action/action.go b/pkg/action/action.go index f093ed7f8..b2adc66c6 100644 --- a/pkg/action/action.go +++ b/pkg/action/action.go @@ -88,6 +88,9 @@ type Configuration struct { // KubeClient is a Kubernetes API client. KubeClient kube.Interface + // KubeClient is a Kubernetes API client (version 2) + KubeClientV2 kube.InterfaceV2 + // RegistryClient is a client for working with registries RegistryClient *registry.Client @@ -413,6 +416,7 @@ func (cfg *Configuration) Init(getter genericclioptions.RESTClientGetter, namesp cfg.RESTClientGetter = getter cfg.KubeClient = kc + cfg.KubeClientV2 = kc cfg.Releases = store cfg.Log = log diff --git a/pkg/action/action_test.go b/pkg/action/action_test.go index fedf260fb..3fdfe7a8e 100644 --- a/pkg/action/action_test.go +++ b/pkg/action/action_test.go @@ -24,7 +24,10 @@ import ( "path/filepath" "testing" + "helm.sh/helm/v3/pkg/cli" + dockerauth "github.com/deislabs/oras/pkg/auth/docker" + "github.com/stretchr/testify/assert" fakeclientset "k8s.io/client-go/kubernetes/fake" "helm.sh/helm/v3/internal/experimental/registry" @@ -80,9 +83,11 @@ func actionConfigFixture(t *testing.T) *Configuration { t.Fatal(err) } + kubeClient := kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}} return &Configuration{ Releases: storage.Init(driver.NewMemory()), - KubeClient: &kubefake.FailingKubeClient{PrintingKubeClient: kubefake.PrintingKubeClient{Out: ioutil.Discard}}, + KubeClient: &kubeClient, + KubeClientV2: &kubeClient, Capabilities: chartutil.DefaultCapabilities, RegistryClient: registryClient, Log: func(format string, v ...interface{}) { @@ -319,3 +324,15 @@ func TestGetVersionSet(t *testing.T) { t.Error("Non-existent version is reported found.") } } + +func TestKubeClientSet(t *testing.T) { + is := assert.New(t) + config := new(Configuration) + settings := cli.New() + if err := config.Init(settings.RESTClientGetter(), settings.Namespace(), os.Getenv("HELM_DRIVER"), func(_ string, v ...interface{}) {}); err != nil { + t.Error(err) + } + is.NotNil(config.KubeClient, "KubeClient not set") + is.NotNil(config.KubeClientV2, "KubeClientV2 not set") + is.Equal(config.KubeClient, config.KubeClientV2, "KubeClientV2 not set") +} diff --git a/pkg/action/rollback.go b/pkg/action/rollback.go index f3f958f3d..3dce2c02e 100644 --- a/pkg/action/rollback.go +++ b/pkg/action/rollback.go @@ -22,6 +22,8 @@ import ( "strings" "time" + "helm.sh/helm/v3/pkg/kube" + "github.com/pkg/errors" "helm.sh/helm/v3/pkg/chartutil" @@ -35,16 +37,20 @@ import ( type Rollback struct { cfg *Configuration - Version int - Timeout time.Duration - Wait bool - WaitForJobs bool - DisableHooks bool - DryRun bool - Recreate bool // will (if true) recreate pods after a rollback. - Force bool // will (if true) force resource upgrade through uninstall/recreate if needed - CleanupOnFail bool - MaxHistory int // MaxHistory limits the maximum number of revisions saved per release + Version int + Timeout time.Duration + Wait bool + WaitForJobs bool + DisableHooks bool + DryRun bool + // Recreate will (if true) recreate pods after a rollback. (not to be confused with RecreateResources) + Recreate bool + // recreate resources on update + // for compatibility reasons this field cannot be named "Recreate", since "Recreate" is referring to the "recreate-pods" flag. + RecreateResources bool + Force bool // will (if true) force resource upgrade through uninstall/recreate if needed + CleanupOnFail bool + MaxHistory int // MaxHistory limits the maximum number of revisions saved per release } // NewRollback creates a new Rollback object with the given configuration. @@ -164,9 +170,18 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.Log("rollback hooks disabled for %s", targetRelease.Name) } - results, err := r.cfg.KubeClient.Update(current, target, r.Force) - - if err != nil { + var result *kube.Result + kubeClientV2 := r.cfg.KubeClientV2 + switch { + case r.RecreateResources && kubeClientV2 != nil: + result, err = kubeClientV2.UpdateRecreate(current, target, r.Force, r.Timeout) + case r.RecreateResources: + r.cfg.Log("warning: kubeClient does not support recreate flag, ignoring it.") + fallthrough + default: + result, err = r.cfg.KubeClient.Update(current, target, r.Force) + } + if err != nil && result != nil { msg := fmt.Sprintf("Rollback %q failed: %s", targetRelease.Name, err) r.cfg.Log("warning: %s", msg) currentRelease.Info.Status = release.StatusSuperseded @@ -175,8 +190,8 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas r.cfg.recordRelease(currentRelease) r.cfg.recordRelease(targetRelease) if r.CleanupOnFail { - r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(results.Created)) - _, errs := r.cfg.KubeClient.Delete(results.Created) + r.cfg.Log("Cleanup on fail set, cleaning up %d resources", len(result.Created)) + _, errs := r.cfg.KubeClient.Delete(result.Created) if errs != nil { var errorList []string for _, e := range errs { @@ -194,7 +209,7 @@ func (r *Rollback) performRollback(currentRelease, targetRelease *release.Releas // log if an error occurs and continue onward. If we ever introduce log // levels, we should make these error level logs so users are notified // that they'll need to go do the cleanup on their own - if err := recreate(r.cfg, results.Updated); err != nil { + if err := recreate(r.cfg, result.Updated); err != nil { r.cfg.Log(err.Error()) } } diff --git a/pkg/action/rollback_test.go b/pkg/action/rollback_test.go new file mode 100644 index 000000000..8d4924b8c --- /dev/null +++ b/pkg/action/rollback_test.go @@ -0,0 +1,80 @@ +/* +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" + + "github.com/stretchr/testify/assert" + + kubefake "helm.sh/helm/v3/pkg/kube/fake" + "helm.sh/helm/v3/pkg/release" +) + +func rollbackAction(t *testing.T) *Rollback { + config := actionConfigFixture(t) + rbAction := NewRollback(config) + + return rbAction +} + +func TestRollbackRelease_RecreateResources(t *testing.T) { + is := assert.New(t) + + rbAction := rollbackAction(t) + rbAction.Timeout = 0 + rbAction.RecreateResources = true + + cfg := rbAction.cfg + kubeClient := cfg.KubeClientV2 + + prevRelease := releaseStub() + prevRelease.Info.Status = release.StatusSuperseded + prevRelease.Name = "my-release" + prevRelease.Version = 0 + err := cfg.Releases.Create(prevRelease) + is.NoError(err) + + currRelease := releaseStub() + currRelease.Info.Status = release.StatusDeployed + currRelease.Name = "my-release" + currRelease.Version = 1 + err = cfg.Releases.Create(currRelease) + is.NoError(err) + + t.Run("recreate should work when kubeClient and kubeClientV2 is set", func(t *testing.T) { + verifiableKubeClient := kubefake.NewKubeClientSpy(kubeClient) + cfg.KubeClient = verifiableKubeClient + cfg.KubeClientV2 = verifiableKubeClient + + err := rbAction.Run(currRelease.Name) + is.NoError(err) + is.Equal(verifiableKubeClient.Calls["Update"], 0) + is.Equal(verifiableKubeClient.Calls["UpdateRecreate"], 1) + }) + + t.Run("recreate should fallback to Update when only kubeClient is set", func(t *testing.T) { + kubeClientSpy := kubefake.NewKubeClientSpy(kubeClient) + cfg.KubeClient = kubeClientSpy + cfg.KubeClientV2 = nil + + err := rbAction.Run(currRelease.Name) + is.NoError(err) + is.Equal(kubeClientSpy.Calls["Update"], 1) + is.Equal(kubeClientSpy.Calls["UpdateRecreate"], 0) + }) +} diff --git a/pkg/action/upgrade.go b/pkg/action/upgrade.go index 07d9cb40e..20ce15730 100644 --- a/pkg/action/upgrade.go +++ b/pkg/action/upgrade.go @@ -75,11 +75,14 @@ type Upgrade struct { // // This should be used with caution. Force bool + // recreate resources on update + // for compatibility reasons this field cannot be named "Recreate", since "Recreate" is referring to the "recreate-pods" flag. + RecreateResources bool // ResetValues will reset the values to the chart's built-ins rather than merging with existing. ResetValues bool // ReuseValues will re-use the user's last supplied values. ReuseValues bool - // Recreate will (if true) recreate pods after a rollback. + // Recreate will (if true) recreate pods after a upgrade. (not to be confused with RecreateResources). Recreate bool // MaxHistory limits the maximum number of revisions saved per release MaxHistory int @@ -316,10 +319,20 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea u.cfg.Log("upgrade hooks disabled for %s", upgradedRelease.Name) } - results, err := u.cfg.KubeClient.Update(current, target, u.Force) - if err != nil { + var result *kube.Result + kubeClientV2 := u.cfg.KubeClientV2 + switch { + case u.RecreateResources && kubeClientV2 != nil: + result, err = kubeClientV2.UpdateRecreate(current, target, u.Force, u.Timeout) + case u.RecreateResources: + u.cfg.Log("warning: kubeClient does not support recreate flag, ignoring it.") + fallthrough + default: + result, err = u.cfg.KubeClient.Update(current, target, u.Force) + } + if err != nil && result != nil { u.cfg.recordRelease(originalRelease) - return u.failRelease(upgradedRelease, results.Created, err) + return u.failRelease(upgradedRelease, result.Created, err) } if u.Recreate { @@ -327,7 +340,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea // log if an error occurs and continue onward. If we ever introduce log // levels, we should make these error level logs so users are notified // that they'll need to go do the cleanup on their own - if err := recreate(u.cfg, results.Updated); err != nil { + if err := recreate(u.cfg, result.Updated); err != nil { u.cfg.Log(err.Error()) } } @@ -336,12 +349,12 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea if u.WaitForJobs { if err := u.cfg.KubeClient.WaitWithJobs(target, u.Timeout); err != nil { u.cfg.recordRelease(originalRelease) - return u.failRelease(upgradedRelease, results.Created, err) + return u.failRelease(upgradedRelease, result.Created, err) } } else { if err := u.cfg.KubeClient.Wait(target, u.Timeout); err != nil { u.cfg.recordRelease(originalRelease) - return u.failRelease(upgradedRelease, results.Created, err) + return u.failRelease(upgradedRelease, result.Created, err) } } } @@ -349,7 +362,7 @@ func (u *Upgrade) performUpgrade(originalRelease, upgradedRelease *release.Relea // post-upgrade hooks if !u.DisableHooks { if err := u.cfg.execHook(upgradedRelease, release.HookPostUpgrade, u.Timeout); err != nil { - return u.failRelease(upgradedRelease, results.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) + return u.failRelease(upgradedRelease, result.Created, fmt.Errorf("post-upgrade hooks failed: %s", err)) } } diff --git a/pkg/action/upgrade_test.go b/pkg/action/upgrade_test.go index 5cca7ca1a..7c65561cd 100644 --- a/pkg/action/upgrade_test.go +++ b/pkg/action/upgrade_test.go @@ -296,3 +296,42 @@ func TestUpgradeRelease_Pending(t *testing.T) { _, err := upAction.Run(rel.Name, buildChart(), vals) req.Contains(err.Error(), "progress", err) } +func TestUpgradeRelease_RecreateResources(t *testing.T) { + is := assert.New(t) + + upAction := upgradeAction(t) + upAction.Timeout = 0 + upAction.RecreateResources = true + + cfg := upAction.cfg + kubeClient := cfg.KubeClientV2 + + rel := releaseStub() + rel.Info.Status = release.StatusDeployed + rel.Name = "my-release" + err := cfg.Releases.Create(rel) + is.NoError(err) + + t.Run("recreate should work when kubeClient and kubeClientV2 is set", func(t *testing.T) { + verifiableKubeClient := kubefake.NewKubeClientSpy(kubeClient) + cfg.KubeClient = verifiableKubeClient + cfg.KubeClientV2 = verifiableKubeClient + + _, err := upAction.Run(rel.Name, buildChart(), map[string]interface{}{}) + is.NoError(err) + is.Equal(verifiableKubeClient.Calls["Update"], 0) + is.Equal(verifiableKubeClient.Calls["UpdateRecreate"], 1) + }) + + t.Run("recreate should fallback to Update when only kubeClient is set", func(t *testing.T) { + kubeClientSpy := kubefake.NewKubeClientSpy(kubeClient) + cfg.KubeClient = kubeClientSpy + cfg.KubeClientV2 = nil + + _, err := upAction.Run(rel.Name, buildChart(), map[string]interface{}{}) + is.NoError(err) + is.Equal(kubeClientSpy.Calls["Update"], 1) + is.Equal(kubeClientSpy.Calls["UpdateRecreate"], 0) + }) + +} diff --git a/pkg/kube/client.go b/pkg/kube/client.go index 2565d1832..3849505e7 100644 --- a/pkg/kube/client.go +++ b/pkg/kube/client.go @@ -40,6 +40,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/strategicpatch" + "k8s.io/apimachinery/pkg/util/wait" "k8s.io/apimachinery/pkg/watch" "k8s.io/cli-runtime/pkg/genericclioptions" "k8s.io/cli-runtime/pkg/resource" @@ -197,7 +198,16 @@ func (c *Client) Build(reader io.Reader, validate bool) (ResourceList, error) { // resource updates, creations, and deletions that were attempted. These can be // used for cleanup or other logging purposes. func (c *Client) Update(original, target ResourceList, force bool) (*Result, error) { - updateErrors := []string{} + return c.update(target, original, force, false, 0) +} + +// Like Update but using the recreate strategy on updating a resource +func (c *Client) UpdateRecreate(original, target ResourceList, force bool, timeout time.Duration) (*Result, error) { + return c.update(target, original, force, true, timeout) +} + +func (c *Client) update(target ResourceList, original ResourceList, force bool, recreate bool, timeout time.Duration) (*Result, error) { + var updateErrors []string res := &Result{} c.Log("checking %d resources for changes", len(target)) @@ -231,7 +241,7 @@ func (c *Client) Update(original, target ResourceList, force bool) (*Result, err return errors.Errorf("no %s with the name %q found", kind, info.Name) } - if err := updateResource(c, info, originalInfo.Object, force); err != nil { + if err := updateResource(c, info, originalInfo.Object, force, recreate, timeout); err != nil { c.Log("error updating the resource %q:\n\t %v", info.Name, err) updateErrors = append(updateErrors, err.Error()) } @@ -441,47 +451,83 @@ func createPatch(target *resource.Info, current runtime.Object) ([]byte, types.P return patch, types.StrategicMergePatchType, err } -func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool) error { +func updateResource(c *Client, target *resource.Info, currentObj runtime.Object, force bool, recreate bool, timeout time.Duration) error { var ( obj runtime.Object helper = resource.NewHelper(target.Client, target.Mapping) kind = target.Mapping.GroupVersionKind.Kind ) - // if --force is applied, attempt to replace the existing resource with the new object. - if force { - var err error - obj, err = helper.Replace(target.Namespace, target.Name, true, target.Object) - if err != nil { - return errors.Wrap(err, "failed to replace object") + patch, patchType, err := createPatch(target, currentObj) + if err != nil { + return errors.Wrap(err, "failed to create patch") + } + + if patch == nil || string(patch) == "{}" { + c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) + // This needs to happen to make sure that tiller has the latest info from the API + // Otherwise there will be no labels and other functions that use labels will panic + if err := target.Get(); err != nil { + return errors.Wrap(err, "failed to refresh resource information") } - c.Log("Replaced %q with kind %s for kind %s", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) - } else { - patch, patchType, err := createPatch(target, currentObj) + return nil + } + + // update strategies: + // default PATCH + // --force PUT + // --recreate PATCH, on failure DELETE, POST + // --recreate --force DELETE, POST + switch { + case recreate && force: + err = c.deleteAndCreate(helper, target, timeout) if err != nil { - return errors.Wrap(err, "failed to create patch") + return errors.Wrapf(err, "failed to recreate %q with kind %s", target.Name, kind) } - - if patch == nil || string(patch) == "{}" { - c.Log("Looks like there are no changes for %s %q", target.Mapping.GroupVersionKind.Kind, target.Name) - // This needs to happen to make sure that Helm has the latest info from the API - // Otherwise there will be no labels and other functions that use labels will panic - if err := target.Get(); err != nil { - return errors.Wrap(err, "failed to refresh resource information") + case recreate: + obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil) + if err != nil { + if apierrors.IsConflict(err) || apierrors.IsInvalid(err) { + err = c.deleteAndCreate(helper, target, timeout) } - return nil + return errors.Wrapf(err, "failed to recreate %q with kind %s", target.Name, kind) + } + case force: + obj, err = helper.Replace(target.Namespace, target.Name, true, target.Object) + if err != nil { + return errors.Wrapf(err, "failed to replace %q with kind %s", target.Name, kind) } - // send patch to server + c.Log("Replaced %q with kind %s for kind %s\n", target.Name, currentObj.GetObjectKind().GroupVersionKind().Kind, kind) + default: obj, err = helper.Patch(target.Namespace, target.Name, patchType, patch, nil) if err != nil { return errors.Wrapf(err, "cannot patch %q with kind %s", target.Name, kind) } } - target.Refresh(obj, true) + if obj != nil { + return target.Refresh(obj, true) + } return nil } +func (c *Client) deleteAndCreate(helper *resource.Helper, target *resource.Info, timeout time.Duration) error { + if err := deleteResource(target); err != nil { + return err + } + + if err := wait.PollImmediate(1*time.Second, timeout, func() (bool, error) { + if _, err := helper.Get(target.Namespace, target.Name); !apierrors.IsNotFound(err) { + return false, err + } + return true, nil + }); err != nil { + return err + } + + return createResource(target) +} + func (c *Client) watchUntilReady(timeout time.Duration, info *resource.Info) error { kind := info.Mapping.GroupVersionKind.Kind switch kind { diff --git a/pkg/kube/client_test.go b/pkg/kube/client_test.go index de5358aee..640210483 100644 --- a/pkg/kube/client_test.go +++ b/pkg/kube/client_test.go @@ -23,7 +23,9 @@ import ( "net/http" "strings" "testing" + "time" + "github.com/stretchr/testify/assert" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" @@ -84,6 +86,36 @@ func notFoundBody() *metav1.Status { } } +func unprocessableEntityBody() *metav1.Status { + return &metav1.Status{ + Code: http.StatusUnprocessableEntity, + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonInvalid, + Message: "cannot change", + Details: &metav1.StatusDetails{}, + } +} + +func conflictEntityBody() *metav1.Status { + return &metav1.Status{ + Code: http.StatusConflict, + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonConflict, + Message: "conflict", + Details: &metav1.StatusDetails{}, + } +} + +func gatewayTimeoutEntityBody() *metav1.Status { + return &metav1.Status{ + Code: http.StatusGatewayTimeout, + Status: metav1.StatusFailure, + Reason: metav1.StatusReasonTimeout, + Message: "timeout", + Details: &metav1.StatusDetails{}, + } +} + func newResponse(code int, obj runtime.Object) (*http.Response, error) { header := http.Header{} header.Set("Content-Type", runtime.ContentTypeJSON) @@ -303,6 +335,203 @@ func TestPerform(t *testing.T) { } } +func TestClient_UpdateRecreate(t *testing.T) { + + is := assert.New(t) + + // setup two pods with one diff that should be patched + listA := newPodList("starfish") + listB := newPodList("starfish") + listB.Items[0].Spec.Containers[0].Ports = []v1.ContainerPort{{Name: "https", ContainerPort: 443}} + + // make sure there is a patchable difference + is.NotEqual(listA.Items[0].Spec.Containers[0].Ports, listB.Items[0].Spec.Containers[0].Ports) + + okResponse := func() *http.Response { + res, _ := newResponse(http.StatusOK, &listA.Items[0]) + return res + } + notFoundResponse := func() *http.Response { + res, _ := newResponse(http.StatusNotFound, notFoundBody()) + return res + } + unprocessableEntityResponse := func() *http.Response { + res, _ := newResponse(http.StatusUnprocessableEntity, unprocessableEntityBody()) + return res + } + conflictEntityResponse := func() *http.Response { + res, _ := newResponse(http.StatusConflict, conflictEntityBody()) + return res + } + gatewayTimeoutResponse := func() *http.Response { + res, _ := newResponse(http.StatusGatewayTimeout, gatewayTimeoutEntityBody()) + return res + } + + tests := []struct { + name string + force bool + responses []*http.Response + finalResponseFactory func() *http.Response + expectedActions []string + fail bool + }{ + { + name: "update recreate should delete and recreate when invalid on PATCH", + force: false, + responses: []*http.Response{ + okResponse(), // GET + okResponse(), // GET + unprocessableEntityResponse(), // PATCH + okResponse(), // DELETE + notFoundResponse(), // GET + okResponse(), // POST + }, + expectedActions: []string{ + "GET", + "GET", + "PATCH", + "DELETE", + "GET", + "POST", + }, + }, + { + name: "update recreate should delete and recreate when conflict on PATCH", + force: false, + responses: []*http.Response{ + okResponse(), // GET + okResponse(), // GET + conflictEntityResponse(), // PATCH + okResponse(), // DELETE + notFoundResponse(), // GET + okResponse(), // POST + }, + expectedActions: []string{ + "GET", + "GET", + "PATCH", + "DELETE", + "GET", + "POST", + }, + }, + { + name: "update recreate should delete and recreate when force flag is true", + force: true, + responses: []*http.Response{ + okResponse(), // GET + okResponse(), // GET + okResponse(), // DELETE + notFoundResponse(), // GET + okResponse(), // POST + }, + expectedActions: []string{ + "GET", + "GET", + "DELETE", + "GET", + "POST", + }, + }, + { + name: "update recreate should fail when timeout on PATCH", + force: false, + responses: []*http.Response{ + okResponse(), // GET + okResponse(), // GET + gatewayTimeoutResponse(), // PATCH + }, + expectedActions: []string{ + "GET", + "GET", + "PATCH", + }, + fail: true, + }, + { + name: "update recreate should fail when timing out after DELETE", + force: false, + responses: []*http.Response{ + okResponse(), // GET + okResponse(), // GET + unprocessableEntityResponse(), // PATCH + okResponse(), // DELETE + }, + // infinitely return OK on get, implying the server does not delete the resource within the given timeout + finalResponseFactory: okResponse, // GET + expectedActions: []string{ + "GET", + "GET", + "PATCH", + "DELETE", + }, + fail: true, + }, + { + name: "update recreate force should fail when timing out after DELETE", + force: true, + responses: []*http.Response{ + okResponse(), // GET + okResponse(), // GET + okResponse(), // DELETE + }, + // infinitely return OK on get, implying the server does not delete the resource within the given timeout + finalResponseFactory: okResponse, // GET + expectedActions: []string{ + "GET", + "GET", + "DELETE", + }, + fail: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var actions []string + c := newTestClient(t) + c.Factory.(*cmdtesting.TestFactory).UnstructuredClient = &fake.RESTClient{ + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + p, m := req.URL.Path, req.Method + t.Logf("got request %s %s", p, m) + if len(tt.responses) == 0 { + if tt.finalResponseFactory != nil { + return tt.finalResponseFactory(), nil + } + t.Fatalf("unexpected request: %s %s", req.Method, req.URL.Path) + return nil, nil + } + // final responses are not added to actions, since it is unpredictable how often the final + // response will be returned on polling + actions = append(actions, m) + var response *http.Response + response, tt.responses = tt.responses[0], tt.responses[1:] + return response, nil + }), + } + first, err := c.Build(objBody(&listA), false) + if err != nil { + t.Fatal(err) + } + second, err := c.Build(objBody(&listB), false) + if err != nil { + t.Fatal(err) + } + + // timeout should be larger than one second, to see actual polling with one second poll interval + _, err = c.UpdateRecreate(first, second, tt.force, 2*time.Second) + if (tt.fail && err == nil) || (!tt.fail && err != nil) { + t.Fatal(err) + } + t.Log(tt.expectedActions) + t.Log(actions) + is.ElementsMatch(tt.expectedActions, actions) + }) + } +} + func TestReal(t *testing.T) { t.Skip("This is a live test, comment this line to run") c := New(nil) diff --git a/pkg/kube/fake/fake.go b/pkg/kube/fake/fake.go index ff800864c..a8a5acff0 100644 --- a/pkg/kube/fake/fake.go +++ b/pkg/kube/fake/fake.go @@ -105,3 +105,13 @@ func (f *FailingKubeClient) WaitAndGetCompletedPodPhase(s string, d time.Duratio } return f.PrintingKubeClient.WaitAndGetCompletedPodPhase(s, d) } + +// UpdateRecreate returns the configured error if set or prints +func (f *FailingKubeClient) UpdateRecreate(r, modified kube.ResourceList, force bool, timeout time.Duration) (*kube.Result, error) { + if f.UpdateError != nil { + return &kube.Result{}, f.UpdateError + } + return f.PrintingKubeClient.UpdateRecreate(r, modified, force, timeout) +} + +var _ kube.InterfaceV2 = (*FailingKubeClient)(nil) diff --git a/pkg/kube/fake/printer.go b/pkg/kube/fake/printer.go index e8bd1845b..b2be6b1e2 100644 --- a/pkg/kube/fake/printer.go +++ b/pkg/kube/fake/printer.go @@ -86,6 +86,11 @@ func (p *PrintingKubeClient) Update(_, modified kube.ResourceList, _ bool) (*kub return &kube.Result{Updated: modified}, nil } +// UpdateRecreate falls back to Update +func (p *PrintingKubeClient) UpdateRecreate(original, target kube.ResourceList, force bool, _ time.Duration) (*kube.Result, error) { + return p.Update(original, target, force) +} + // Build implements KubeClient Build. func (p *PrintingKubeClient) Build(_ io.Reader, _ bool) (kube.ResourceList, error) { return []*resource.Info{}, nil @@ -103,3 +108,5 @@ func bufferize(resources kube.ResourceList) io.Reader { } return strings.NewReader(builder.String()) } + +var _ kube.InterfaceV2 = (*PrintingKubeClient)(nil) diff --git a/pkg/kube/fake/spy.go b/pkg/kube/fake/spy.go new file mode 100644 index 000000000..576dd1a09 --- /dev/null +++ b/pkg/kube/fake/spy.go @@ -0,0 +1,102 @@ +/* +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 fake + +import ( + "io" + "runtime" + "strings" + "time" + + v1 "k8s.io/api/core/v1" + + "helm.sh/helm/v3/pkg/kube" +) + +// KubeClient wrapper which can be used for testing to verify that a certain method has been called a certain +// number of times +type KubeClientSpy struct { + KubeClientV2 kube.InterfaceV2 + // map with function names as keys and number of times it was called as names + Calls map[string]int +} + +func NewKubeClientSpy(kubeClient kube.InterfaceV2) KubeClientSpy { + return KubeClientSpy{ + KubeClientV2: kubeClient, + Calls: make(map[string]int), + } +} + +func functionName() string { + pc := make([]uintptr, 15) + n := runtime.Callers(2, pc) + frames := runtime.CallersFrames(pc[:n]) + frame, _ := frames.Next() + pathSegments := strings.Split(frame.Function, ".") + return pathSegments[len(pathSegments)-1] +} + +func (v KubeClientSpy) Create(resources kube.ResourceList) (*kube.Result, error) { + v.Calls[functionName()]++ + return v.KubeClientV2.Create(resources) +} + +func (v KubeClientSpy) Wait(resources kube.ResourceList, timeout time.Duration) error { + v.Calls[functionName()]++ + return v.KubeClientV2.Wait(resources, timeout) +} + +func (v KubeClientSpy) Delete(resources kube.ResourceList) (*kube.Result, []error) { + v.Calls[functionName()]++ + return v.KubeClientV2.Delete(resources) +} + +func (v KubeClientSpy) WatchUntilReady(resources kube.ResourceList, timeout time.Duration) error { + v.Calls[functionName()]++ + return v.KubeClientV2.WatchUntilReady(resources, timeout) +} + +func (v KubeClientSpy) Update(original, target kube.ResourceList, force bool) (*kube.Result, error) { + v.Calls[functionName()]++ + return v.KubeClientV2.Update(original, target, force) +} + +func (v KubeClientSpy) Build(reader io.Reader, validate bool) (kube.ResourceList, error) { + v.Calls[functionName()]++ + return v.KubeClientV2.Build(reader, validate) +} + +func (v KubeClientSpy) WaitAndGetCompletedPodPhase(name string, timeout time.Duration) (v1.PodPhase, error) { + v.Calls[functionName()]++ + return v.KubeClientV2.WaitAndGetCompletedPodPhase(name, timeout) +} + +func (v KubeClientSpy) IsReachable() error { + v.Calls[functionName()]++ + return v.KubeClientV2.IsReachable() +} + +func (v KubeClientSpy) UpdateRecreate(original, target kube.ResourceList, force bool, timeout time.Duration) (*kube.Result, error) { + v.Calls[functionName()]++ + return v.KubeClientV2.UpdateRecreate(original, target, force, timeout) +} + +func (v KubeClientSpy) WaitWithJobs(resources kube.ResourceList, timeout time.Duration) error { + v.Calls[functionName()]++ + return v.KubeClientV2.WaitWithJobs(resources, timeout) +} diff --git a/pkg/kube/interface.go b/pkg/kube/interface.go index 545985996..1e262452a 100644 --- a/pkg/kube/interface.go +++ b/pkg/kube/interface.go @@ -65,4 +65,25 @@ type Interface interface { IsReachable() error } -var _ Interface = (*Client)(nil) +// Extended Kubernetes client interface +// +// Version 2 interface adds new methods in a backward compatible way. +// In the next API breaking release it could be merged with the base interface. +type InterfaceV2 interface { + Interface + + // Update updates one or more resources or creates the resource if it doesn't exist. + // + // Force controls how to perform the update of a resource: + // + // force: false + // Patch a resource, if that fails due to an StatusReasonInvalid or StatusReasonConflict error, + // delete it and recreate it afterwards. + // force: true + // Delete and recreated without trying to patch it first. + // + // After deleting a resource poll and wait until resource was deleted, fails if server does not delete resource within timeout. + UpdateRecreate(original, target ResourceList, force bool, timeout time.Duration) (*Result, error) +} + +var _ InterfaceV2 = (*Client)(nil) diff --git a/scripts/completions.bash b/scripts/completions.bash new file mode 100644 index 000000000..51b29639b --- /dev/null +++ b/scripts/completions.bash @@ -0,0 +1,1636 @@ +# bash completion for helm -*- shell-script -*- + +__debug() +{ + if [[ -n ${BASH_COMP_DEBUG_FILE} ]]; then + echo "$*" >> "${BASH_COMP_DEBUG_FILE}" + fi +} + +# Homebrew on Macs have version 1.3 of bash-completion which doesn't include +# _init_completion. This is a very minimal version of that function. +__my_init_completion() +{ + COMPREPLY=() + _get_comp_words_by_ref "$@" cur prev words cword +} + +__index_of_word() +{ + local w word=$1 + shift + index=0 + for w in "$@"; do + [[ $w = "$word" ]] && return + index=$((index+1)) + done + index=-1 +} + +__contains_word() +{ + local w word=$1; shift + for w in "$@"; do + [[ $w = "$word" ]] && return + done + return 1 +} + +__handle_reply() +{ + __debug "${FUNCNAME[0]}" + case $cur in + -*) + if [[ $(type -t compopt) = "builtin" ]]; then + compopt -o nospace + fi + local allflags + if [ ${#must_have_one_flag[@]} -ne 0 ]; then + allflags=("${must_have_one_flag[@]}") + else + allflags=("${flags[*]} ${two_word_flags[*]}") + fi + COMPREPLY=( $(compgen -W "${allflags[*]}" -- "$cur") ) + if [[ $(type -t compopt) = "builtin" ]]; then + [[ "${COMPREPLY[0]}" == *= ]] || compopt +o nospace + fi + + # complete after --flag=abc + if [[ $cur == *=* ]]; then + if [[ $(type -t compopt) = "builtin" ]]; then + compopt +o nospace + fi + + local index flag + flag="${cur%%=*}" + __index_of_word "${flag}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + COMPREPLY=() + PREFIX="" + cur="${cur#*=}" + ${flags_completion[${index}]} + if [ -n "${ZSH_VERSION}" ]; then + # zfs completion needs --flag= prefix + eval "COMPREPLY=( \"\${COMPREPLY[@]/#/${flag}=}\" )" + fi + fi + fi + return 0; + ;; + esac + + # check if we are handling a flag with special work handling + local index + __index_of_word "${prev}" "${flags_with_completion[@]}" + if [[ ${index} -ge 0 ]]; then + ${flags_completion[${index}]} + return + fi + + # we are parsing a flag and don't have a special handler, no completion + if [[ ${cur} != "${words[cword]}" ]]; then + return + fi + + local completions + completions=("${commands[@]}") + if [[ ${#must_have_one_noun[@]} -ne 0 ]]; then + completions=("${must_have_one_noun[@]}") + fi + if [[ ${#must_have_one_flag[@]} -ne 0 ]]; then + completions+=("${must_have_one_flag[@]}") + fi + COMPREPLY=( $(compgen -W "${completions[*]}" -- "$cur") ) + + if [[ ${#COMPREPLY[@]} -eq 0 && ${#noun_aliases[@]} -gt 0 && ${#must_have_one_noun[@]} -ne 0 ]]; then + COMPREPLY=( $(compgen -W "${noun_aliases[*]}" -- "$cur") ) + fi + + if [[ ${#COMPREPLY[@]} -eq 0 ]]; then + declare -F __custom_func >/dev/null && __custom_func + fi + + __ltrim_colon_completions "$cur" +} + +# The arguments should be in the form "ext1|ext2|extn" +__handle_filename_extension_flag() +{ + local ext="$1" + _filedir "@(${ext})" +} + +__handle_subdirs_in_dir_flag() +{ + local dir="$1" + pushd "${dir}" >/dev/null 2>&1 && _filedir -d && popd >/dev/null 2>&1 +} + +__handle_flag() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + # if a command required a flag, and we found it, unset must_have_one_flag() + local flagname=${words[c]} + local flagvalue + # if the word contained an = + if [[ ${words[c]} == *"="* ]]; then + flagvalue=${flagname#*=} # take in as flagvalue after the = + flagname=${flagname%%=*} # strip everything after the = + flagname="${flagname}=" # but put the = back + fi + __debug "${FUNCNAME[0]}: looking for ${flagname}" + if __contains_word "${flagname}" "${must_have_one_flag[@]}"; then + must_have_one_flag=() + fi + + # if you set a flag which only applies to this command, don't show subcommands + if __contains_word "${flagname}" "${local_nonpersistent_flags[@]}"; then + commands=() + fi + + # keep flag value with flagname as flaghash + if [ -n "${flagvalue}" ] ; then + flaghash[${flagname}]=${flagvalue} + elif [ -n "${words[ $((c+1)) ]}" ] ; then + flaghash[${flagname}]=${words[ $((c+1)) ]} + else + flaghash[${flagname}]="true" # pad "true" for bool flag + fi + + # skip the argument to a two word flag + if __contains_word "${words[c]}" "${two_word_flags[@]}"; then + c=$((c+1)) + # if we are looking for a flags value, don't show commands + if [[ $c -eq $cword ]]; then + commands=() + fi + fi + + c=$((c+1)) + +} + +__handle_noun() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + if __contains_word "${words[c]}" "${must_have_one_noun[@]}"; then + must_have_one_noun=() + elif __contains_word "${words[c]}" "${noun_aliases[@]}"; then + must_have_one_noun=() + fi + + nouns+=("${words[c]}") + c=$((c+1)) +} + +__handle_command() +{ + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + + local next_command + if [[ -n ${last_command} ]]; then + next_command="_${last_command}_${words[c]//:/__}" + else + if [[ $c -eq 0 ]]; then + next_command="_$(basename "${words[c]//:/__}")" + else + next_command="_${words[c]//:/__}" + fi + fi + c=$((c+1)) + __debug "${FUNCNAME[0]}: looking for ${next_command}" + declare -F $next_command >/dev/null && $next_command +} + +__handle_word() +{ + if [[ $c -ge $cword ]]; then + __handle_reply + return + fi + __debug "${FUNCNAME[0]}: c is $c words[c] is ${words[c]}" + if [[ "${words[c]}" == -* ]]; then + __handle_flag + elif __contains_word "${words[c]}" "${commands[@]}"; then + __handle_command + elif [[ $c -eq 0 ]] && __contains_word "$(basename "${words[c]}")" "${commands[@]}"; then + __handle_command + else + __handle_noun + fi + __handle_word +} + +_helm_completion() +{ + last_command="helm_completion" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + must_have_one_noun+=("bash") + must_have_one_noun+=("zsh") + noun_aliases=() +} + +_helm_create() +{ + last_command="helm_create" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--starter=") + two_word_flags+=("-p") + local_nonpersistent_flags+=("--starter=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_delete() +{ + last_command="helm_delete" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--keep-history") + local_nonpersistent_flags+=("--keep-history") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency_build() +{ + last_command="helm_dependency_build" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency_list() +{ + last_command="helm_dependency_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency_update() +{ + last_command="helm_dependency_update" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--skip-refresh") + local_nonpersistent_flags+=("--skip-refresh") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_dependency() +{ + last_command="helm_dependency" + commands=() + commands+=("build") + commands+=("list") + commands+=("update") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_fetch() +{ + last_command="helm_fetch" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--destination=") + two_word_flags+=("-d") + local_nonpersistent_flags+=("--destination=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--prov") + local_nonpersistent_flags+=("--prov") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--untar") + local_nonpersistent_flags+=("--untar") + flags+=("--untardir=") + local_nonpersistent_flags+=("--untardir=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get_hooks() +{ + last_command="helm_get_hooks" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get_manifest() +{ + last_command="helm_get_manifest" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get_values() +{ + last_command="helm_get_values" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--all") + flags+=("-a") + local_nonpersistent_flags+=("--all") + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_get() +{ + last_command="helm_get" + commands=() + commands+=("hooks") + commands+=("manifest") + commands+=("values") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + local_nonpersistent_flags+=("--revision=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_history() +{ + last_command="helm_history" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--max=") + local_nonpersistent_flags+=("--max=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_home() +{ + last_command="helm_home" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_init() +{ + last_command="helm_init" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--canary-image") + local_nonpersistent_flags+=("--canary-image") + flags+=("--client-only") + flags+=("-c") + local_nonpersistent_flags+=("--client-only") + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--local-repo-url=") + local_nonpersistent_flags+=("--local-repo-url=") + flags+=("--net-host") + local_nonpersistent_flags+=("--net-host") + flags+=("--service-account=") + local_nonpersistent_flags+=("--service-account=") + flags+=("--skip-refresh") + local_nonpersistent_flags+=("--skip-refresh") + flags+=("--tiller-image=") + two_word_flags+=("-i") + local_nonpersistent_flags+=("--tiller-image=") + flags+=("--tiller-tls") + local_nonpersistent_flags+=("--tiller-tls") + flags+=("--tiller-tls-cert=") + local_nonpersistent_flags+=("--tiller-tls-cert=") + flags+=("--tiller-tls-key=") + local_nonpersistent_flags+=("--tiller-tls-key=") + flags+=("--tiller-tls-verify") + local_nonpersistent_flags+=("--tiller-tls-verify") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--upgrade") + local_nonpersistent_flags+=("--upgrade") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_inspect_chart() +{ + last_command="helm_inspect_chart" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_inspect_values() +{ + last_command="helm_inspect_values" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_inspect() +{ + last_command="helm_inspect" + commands=() + commands+=("chart") + commands+=("values") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_install() +{ + last_command="helm_install" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--name=") + two_word_flags+=("-n") + local_nonpersistent_flags+=("--name=") + flags+=("--name-template=") + local_nonpersistent_flags+=("--name-template=") + flags+=("--namespace=") + local_nonpersistent_flags+=("--namespace=") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--replace") + local_nonpersistent_flags+=("--replace") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--set=") + local_nonpersistent_flags+=("--set=") + flags+=("--set-string=") + local_nonpersistent_flags+=("--set-string=") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--values=") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--values=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--wait") + local_nonpersistent_flags+=("--wait") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_lint() +{ + last_command="helm_lint" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--strict") + local_nonpersistent_flags+=("--strict") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_list() +{ + last_command="helm_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--all") + local_nonpersistent_flags+=("--all") + flags+=("--date") + flags+=("-d") + local_nonpersistent_flags+=("--date") + flags+=("--uninstalled") + local_nonpersistent_flags+=("--uninstalled") + flags+=("--uninstalling") + local_nonpersistent_flags+=("--uninstalling") + flags+=("--deployed") + local_nonpersistent_flags+=("--deployed") + flags+=("--failed") + local_nonpersistent_flags+=("--failed") + flags+=("--max=") + two_word_flags+=("-m") + local_nonpersistent_flags+=("--max=") + flags+=("--namespace=") + local_nonpersistent_flags+=("--namespace=") + flags+=("--offset=") + two_word_flags+=("-o") + local_nonpersistent_flags+=("--offset=") + flags+=("--reverse") + flags+=("-r") + local_nonpersistent_flags+=("--reverse") + flags+=("--short") + flags+=("-q") + local_nonpersistent_flags+=("--short") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_package() +{ + last_command="helm_package" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--destination=") + two_word_flags+=("-d") + local_nonpersistent_flags+=("--destination=") + flags+=("--key=") + local_nonpersistent_flags+=("--key=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--save") + local_nonpersistent_flags+=("--save") + flags+=("--sign") + local_nonpersistent_flags+=("--sign") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_install() +{ + last_command="helm_plugin_install" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_list() +{ + last_command="helm_plugin_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_remove() +{ + last_command="helm_plugin_remove" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin_update() +{ + last_command="helm_plugin_update" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_plugin() +{ + last_command="helm_plugin" + commands=() + commands+=("install") + commands+=("list") + commands+=("remove") + commands+=("update") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_add() +{ + last_command="helm_repo_add" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--no-update") + local_nonpersistent_flags+=("--no-update") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_index() +{ + last_command="helm_repo_index" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--merge=") + local_nonpersistent_flags+=("--merge=") + flags+=("--url=") + local_nonpersistent_flags+=("--url=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_list() +{ + last_command="helm_repo_list" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_remove() +{ + last_command="helm_repo_remove" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo_update() +{ + last_command="helm_repo_update" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_repo() +{ + last_command="helm_repo" + commands=() + commands+=("add") + commands+=("index") + commands+=("list") + commands+=("remove") + commands+=("update") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_reset() +{ + last_command="helm_reset" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--force") + flags+=("-f") + local_nonpersistent_flags+=("--force") + flags+=("--remove-helm-home") + local_nonpersistent_flags+=("--remove-helm-home") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_rollback() +{ + last_command="helm_rollback" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--force") + local_nonpersistent_flags+=("--force") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--recreate") + local_nonpersistent_flags+=("--recreate") + flags+=("--recreate-pods") + local_nonpersistent_flags+=("--recreate-pods") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--wait") + local_nonpersistent_flags+=("--wait") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_search() +{ + last_command="helm_search" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--regexp") + flags+=("-r") + local_nonpersistent_flags+=("--regexp") + flags+=("--version=") + two_word_flags+=("-v") + local_nonpersistent_flags+=("--version=") + flags+=("--versions") + flags+=("-l") + local_nonpersistent_flags+=("--versions") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_serve() +{ + last_command="helm_serve" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--address=") + local_nonpersistent_flags+=("--address=") + flags+=("--repo-path=") + local_nonpersistent_flags+=("--repo-path=") + flags+=("--url=") + local_nonpersistent_flags+=("--url=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_status() +{ + last_command="helm_status" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--revision=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_test() +{ + last_command="helm_test" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--cleanup") + local_nonpersistent_flags+=("--cleanup") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_upgrade() +{ + last_command="helm_upgrade" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--ca-file=") + local_nonpersistent_flags+=("--ca-file=") + flags+=("--cert-file=") + local_nonpersistent_flags+=("--cert-file=") + flags+=("--devel") + local_nonpersistent_flags+=("--devel") + flags+=("--disable-hooks") + local_nonpersistent_flags+=("--disable-hooks") + flags+=("--dry-run") + local_nonpersistent_flags+=("--dry-run") + flags+=("--force") + local_nonpersistent_flags+=("--force") + flags+=("--install") + flags+=("-i") + local_nonpersistent_flags+=("--install") + flags+=("--key-file=") + local_nonpersistent_flags+=("--key-file=") + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--namespace=") + local_nonpersistent_flags+=("--namespace=") + flags+=("--no-hooks") + local_nonpersistent_flags+=("--no-hooks") + flags+=("--recreate") + local_nonpersistent_flags+=("--recreate") + flags+=("--recreate-pods") + local_nonpersistent_flags+=("--recreate-pods") + flags+=("--repo=") + local_nonpersistent_flags+=("--repo=") + flags+=("--reset-values") + local_nonpersistent_flags+=("--reset-values") + flags+=("--reuse-values") + local_nonpersistent_flags+=("--reuse-values") + flags+=("--set=") + local_nonpersistent_flags+=("--set=") + flags+=("--timeout=") + local_nonpersistent_flags+=("--timeout=") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--values=") + two_word_flags+=("-f") + local_nonpersistent_flags+=("--values=") + flags+=("--verify") + local_nonpersistent_flags+=("--verify") + flags+=("--version=") + local_nonpersistent_flags+=("--version=") + flags+=("--wait") + local_nonpersistent_flags+=("--wait") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_verify() +{ + last_command="helm_verify" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--keyring=") + local_nonpersistent_flags+=("--keyring=") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm_version() +{ + last_command="helm_version" + commands=() + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--client") + flags+=("-c") + local_nonpersistent_flags+=("--client") + flags+=("--server") + flags+=("-s") + local_nonpersistent_flags+=("--server") + flags+=("--short") + local_nonpersistent_flags+=("--short") + flags+=("--tls") + local_nonpersistent_flags+=("--tls") + flags+=("--tls-ca-cert=") + local_nonpersistent_flags+=("--tls-ca-cert=") + flags+=("--tls-cert=") + local_nonpersistent_flags+=("--tls-cert=") + flags+=("--tls-key=") + local_nonpersistent_flags+=("--tls-key=") + flags+=("--tls-verify") + local_nonpersistent_flags+=("--tls-verify") + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +_helm() +{ + last_command="helm" + commands=() + commands+=("completion") + commands+=("create") + commands+=("delete") + commands+=("dependency") + commands+=("fetch") + commands+=("get") + commands+=("history") + commands+=("home") + commands+=("init") + commands+=("inspect") + commands+=("install") + commands+=("lint") + commands+=("list") + commands+=("package") + commands+=("plugin") + commands+=("repo") + commands+=("reset") + commands+=("rollback") + commands+=("search") + commands+=("serve") + commands+=("status") + commands+=("test") + commands+=("upgrade") + commands+=("verify") + commands+=("version") + + flags=() + two_word_flags=() + local_nonpersistent_flags=() + flags_with_completion=() + flags_completion=() + + flags+=("--debug") + flags+=("--host=") + flags+=("--kube-context=") + flags+=("--tiller-namespace=") + + must_have_one_flag=() + must_have_one_noun=() + noun_aliases=() +} + +__start_helm() +{ + local cur prev words cword + declare -A flaghash 2>/dev/null || : + if declare -F _init_completion >/dev/null 2>&1; then + _init_completion -s || return + else + __my_init_completion -n "=" || return + fi + + local c=0 + local flags=() + local two_word_flags=() + local local_nonpersistent_flags=() + local flags_with_completion=() + local flags_completion=() + local commands=("helm") + local must_have_one_flag=() + local must_have_one_noun=() + local last_command + local nouns=() + + __handle_word +} + +if [[ $(type -t compopt) = "builtin" ]]; then + complete -o default -F __start_helm helm +else + complete -o default -o nospace -F __start_helm helm +fi + +# ex: ts=4 sw=4 et filetype=sh