mirror of https://github.com/helm/helm
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>pull/9836/head
parent
179f90151d
commit
4fdcedc172
@ -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)
|
||||
})
|
||||
}
|
@ -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)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue