Add ownership verification to uninstall action

This commit adds ownership verification to the uninstall action. It checks that the resource to be deleted is owned by the chart before deleting it.

Fixes: #31333

Signed-off-by: Evans Mungai <mbuevans@gmail.com>
pull/31584/head
Evans Mungai 1 month ago
parent 6d5b5aab32
commit f552950b05
No known key found for this signature in database
GPG Key ID: BBEB812143DD14E1

@ -336,7 +336,13 @@ func (u *Uninstall) deleteRelease(rel *release.Release) (kube.ResourceList, stri
// Delete only owned resources
if len(ownedResources) > 0 {
u.cfg.Logger().Debug("deleting resources part of this release", "count", len(ownedResources), "propagation", u.DeletionPropagation)
for _, info := range ownedResources {
u.cfg.Logger().Debug("deleting resource owned by this release",
"kind", info.Mapping.GroupVersionKind.Kind,
"name", info.Name,
"namespace", info.Namespace,
"release", rel.Name)
}
_, errs = u.cfg.KubeClient.Delete(ownedResources, parseCascadingFlag(u.DeletionPropagation, u.cfg.Logger()))
}
}

@ -17,9 +17,11 @@ limitations under the License.
package action
import (
"bytes"
"errors"
"fmt"
"io"
"log/slog"
"testing"
"github.com/stretchr/testify/assert"
@ -147,9 +149,16 @@ func TestUninstallRelease_Cascade(t *testing.T) {
}
}`
unAction.cfg.Releases.Create(rel)
// Create dummy resources with Mapping but no Client - this skips ownership verification
// (nil Client is treated as owned) and goes directly to delete
dummyResources := kube.ResourceList{
newDeploymentResource("secret", ""),
}
failer := unAction.cfg.KubeClient.(*kubefake.FailingKubeClient)
failer.DeleteError = fmt.Errorf("Uninstall with cascade failed")
failer.BuildDummy = true
failer.DummyResources = dummyResources
unAction.cfg.KubeClient = failer
_, err := unAction.Run(rel.Name)
require.Error(t, err)
@ -169,3 +178,178 @@ func TestUninstallRun_UnreachableKubeClient(t *testing.T) {
assert.Nil(t, result)
assert.ErrorContains(t, err, "connection refused")
}
func TestUninstallRelease_OwnershipVerification(t *testing.T) {
is := assert.New(t)
// Create a buffer to capture log output
logBuffer := &bytes.Buffer{}
handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelDebug})
config := actionConfigFixture(t)
config.SetLogger(handler)
unAction := NewUninstall(config)
unAction.DisableHooks = true
unAction.DryRun = false
unAction.KeepHistory = true
rel := releaseStub()
rel.Name = "ownership-test"
rel.Namespace = "default"
rel.Manifest = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-configmap
labels:
app.kubernetes.io/managed-by: Helm
annotations:
meta.helm.sh/release-name: ownership-test
meta.helm.sh/release-namespace: default
data:
key: value`
config.Releases.Create(rel)
// Create dummy resources with proper ownership metadata
labels := map[string]string{
"app.kubernetes.io/managed-by": "Helm",
}
annotations := map[string]string{
"meta.helm.sh/release-name": "ownership-test",
"meta.helm.sh/release-namespace": "default",
}
dummyResources := kube.ResourceList{
newDeploymentWithOwner("owned-deploy", "default", labels, annotations),
}
failer := config.KubeClient.(*kubefake.FailingKubeClient)
failer.DummyResources = dummyResources
resi, err := unAction.Run(rel.Name)
is.NoError(err)
is.NotNil(resi)
res, err := releaserToV1Release(resi.Release)
is.NoError(err)
is.Equal(common.StatusUninstalled, res.Info.Status)
// Verify log contains debug message about deleting owned resource
logOutput := logBuffer.String()
is.Contains(logOutput, "deleting resource owned by this release")
is.Contains(logOutput, "owned-deploy")
is.Contains(logOutput, "Deployment")
}
func TestUninstallRelease_OwnershipVerification_WithKeepPolicy(t *testing.T) {
is := assert.New(t)
// Create a buffer to capture log output
logBuffer := &bytes.Buffer{}
handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelWarn})
config := actionConfigFixture(t)
config.SetLogger(handler)
unAction := NewUninstall(config)
unAction.DisableHooks = true
unAction.DryRun = false
unAction.KeepHistory = true
rel := releaseStub()
rel.Name = "keep-and-ownership"
rel.Namespace = "default"
rel.Manifest = `apiVersion: v1
kind: Secret
metadata:
name: kept-secret
annotations:
helm.sh/resource-policy: keep
meta.helm.sh/release-name: keep-and-ownership
meta.helm.sh/release-namespace: default
labels:
app.kubernetes.io/managed-by: Helm
type: Opaque
data:
password: cGFzc3dvcmQ=
---
apiVersion: v1
kind: ConfigMap
metadata:
name: deleted-configmap
labels:
app.kubernetes.io/managed-by: Helm
annotations:
meta.helm.sh/release-name: keep-and-ownership
meta.helm.sh/release-namespace: default
data:
key: value`
config.Releases.Create(rel)
// Create dummy resources - one unowned to test logging
dummyResources := kube.ResourceList{
newDeploymentWithOwner("unowned-deploy", "default", nil, nil),
}
failer := config.KubeClient.(*kubefake.FailingKubeClient)
failer.DummyResources = dummyResources
res, err := unAction.Run(rel.Name)
is.NoError(err)
is.NotNil(res)
// Should contain info about kept resources
is.Contains(res.Info, "kept due to the resource policy")
// Verify log contains warning about skipped unowned resource
logOutput := logBuffer.String()
is.Contains(logOutput, "skipping delete of resource not owned by this release")
is.Contains(logOutput, "unowned-deploy")
}
func TestUninstallRelease_DryRun_OwnershipVerification(t *testing.T) {
is := assert.New(t)
// Create a buffer to capture log output
logBuffer := &bytes.Buffer{}
handler := slog.NewTextHandler(logBuffer, &slog.HandlerOptions{Level: slog.LevelWarn})
config := actionConfigFixture(t)
config.SetLogger(handler)
unAction := NewUninstall(config)
unAction.DisableHooks = true
unAction.DryRun = true
rel := releaseStub()
rel.Name = "dryrun-ownership"
rel.Namespace = "default"
rel.Manifest = `apiVersion: v1
kind: ConfigMap
metadata:
name: test-configmap
labels:
app.kubernetes.io/managed-by: Helm
annotations:
meta.helm.sh/release-name: dryrun-ownership
meta.helm.sh/release-namespace: default
data:
key: value`
config.Releases.Create(rel)
// Create dummy resources - one unowned to test dry-run logging
dummyResources := kube.ResourceList{
newDeploymentWithOwner("dryrun-unowned-deploy", "default", nil, nil),
}
failer := config.KubeClient.(*kubefake.FailingKubeClient)
failer.DummyResources = dummyResources
resi, err := unAction.Run(rel.Name)
is.NoError(err)
is.NotNil(resi)
is.NotNil(resi.Release)
res, err := releaserToV1Release(resi.Release)
is.NoError(err)
is.Equal("dryrun-ownership", res.Name)
// Verify log contains dry-run warning about resources that would be skipped
logOutput := logBuffer.String()
is.Contains(logOutput, "dry-run: would skip resource")
is.Contains(logOutput, "dryrun-unowned-deploy")
is.Contains(logOutput, "Deployment")
}

Loading…
Cancel
Save