@ -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" )
}