diff --git a/pkg/action/hooks_test.go b/pkg/action/hooks_test.go new file mode 100644 index 000000000..0849574cb --- /dev/null +++ b/pkg/action/hooks_test.go @@ -0,0 +1,167 @@ +package action + +import ( + "helm.sh/helm/v3/pkg/chartutil" + "helm.sh/helm/v3/pkg/kube" + kubefake "helm.sh/helm/v3/pkg/kube/fake" + "helm.sh/helm/v3/pkg/release" + "helm.sh/helm/v3/pkg/storage" + "helm.sh/helm/v3/pkg/storage/driver" + "io" + "io/ioutil" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/util/yaml" + "k8s.io/cli-runtime/pkg/resource" + "reflect" + "testing" + "time" +) + +type HookFailedError struct{} + +func (e *HookFailedError) Error() string { + return "Hook failed!" +} + +type HookFailingKubeClient struct { + kubefake.PrintingKubeClient + failOn resource.Info + deleteRecord []resource.Info +} + +func (_ *HookFailingKubeClient) Build(reader io.Reader, _ bool) (kube.ResourceList, error) { + configMap := &v1.ConfigMap{} + + err := yaml.NewYAMLOrJSONDecoder(reader, 1000).Decode(configMap) + + if err != nil { + return kube.ResourceList{}, err + } + + return kube.ResourceList{{ + Name: configMap.Name, + Namespace: configMap.Namespace, + }}, nil +} + +func (h *HookFailingKubeClient) WatchUntilReady(resources kube.ResourceList, duration time.Duration) error { + for _, res := range resources { + if res.Name == h.failOn.Name && res.Namespace == h.failOn.Namespace { + return &HookFailedError{} + } + } + + return h.PrintingKubeClient.WatchUntilReady(resources, duration) +} + +func (h *HookFailingKubeClient) Delete(resources kube.ResourceList) (*kube.Result, []error) { + for _, res := range resources { + h.deleteRecord = append(h.deleteRecord, resource.Info{ + Name: res.Name, + Namespace: res.Namespace, + }) + } + + return h.PrintingKubeClient.Delete(resources) +} + +func TestHooksCleanUp(t *testing.T) { + hookKubeClient := &HookFailingKubeClient{kubefake.PrintingKubeClient{Out: ioutil.Discard}, resource.Info{ + Name: "build-config-2", + Namespace: "test", + }, []resource.Info{}} + + configuration := &Configuration{ + Releases: storage.Init(driver.NewMemory()), + KubeClient: hookKubeClient, + Capabilities: chartutil.DefaultCapabilities, + Log: func(format string, v ...interface{}) { + t.Helper() + if *verbose { + t.Logf(format, v...) + } + }, + } + + hookEvent := release.HookPreInstall + + r := &release.Release{ + Name: "test-release", + Namespace: "test", + Hooks: []*release.Hook{ + { + Name: "hook-1", + Kind: "ConfigMap", + Path: "templates/service_account.yaml", + Manifest: `apiVersion: v1 +kind: ConfigMap +metadata: + name: build-config-1 + namespace: test +data: + foo: bar +`, + Weight: -5, + Events: []release.HookEvent{ + hookEvent, + }, + DeletePolicies: []release.HookDeletePolicy{ + release.HookBeforeHookCreation, + release.HookSucceeded, + release.HookFailed, + }, + LastRun: release.HookExecution{ + Phase: release.HookPhaseSucceeded, + }, + }, + { + Name: "hook-2", + Kind: "ConfigMap", + Path: "templates/job.yaml", + Manifest: `apiVersion: v1 +kind: ConfigMap +metadata: + name: build-config-2 + namespace: test +data: + foo: bar +`, + Weight: 0, + Events: []release.HookEvent{ + hookEvent, + }, + DeletePolicies: []release.HookDeletePolicy{ + release.HookBeforeHookCreation, + release.HookSucceeded, + release.HookFailed, + }, + LastRun: release.HookExecution{ + Phase: release.HookPhaseFailed, + }, + }, + }, + } + + _ = configuration.execHook(r, hookEvent, 600) + + if !reflect.DeepEqual(hookKubeClient.deleteRecord, []resource.Info{ + { + Name: "build-config-1", + Namespace: "test", + }, + { + Name: "build-config-2", + Namespace: "test", + }, + { + Name: "build-config-2", + Namespace: "test", + }, + }) { + t.Fatalf("Got unexpected delete record") + } + + //if err != nil { + // t.Fatalf("An expected error occured: %#v", err) + //} +}