From 1198582813b6c13fff43ed824a1e122cd0799958 Mon Sep 17 00:00:00 2001 From: Aleksey Dukhovniy Date: Thu, 2 Apr 2020 18:49:35 +0200 Subject: [PATCH] Add integration tests for the instance admission controller (#1442) Summary: the tests discovered a bug in the instance admission controller (IAC) where IAC was trying to reset `PlanExecution.PlanName` when a plan becomes terminal. However, it wasn't doing this because: 1. it wasn't notified when `Instance.Status` was being updated (_Status_ is a sub-resource) and 2. even if it would, `Instance` resource **can not be changed** upon `Status` subresource updates (any resource is only allowed to modify itself from the webhook) To fix this problem we would either need to merge `Status` back into the `Instance` resource or duplicate part of the `Status` information in the `Instance.Spec`. After long discussions, we decided to introduce a new `PlanExecution.Status` field which is populated by the instance controller and duplicates the corresponding `Status.PlanStatus` field. Signed-off-by: Aleksey Dukhovniy --- config/crds/kudo.dev_instances.yaml | 3 + go.mod | 3 + pkg/apis/kudo/v1beta1/instance_types.go | 1 + .../kudo/v1beta1/instance_types_helpers.go | 1 + .../v1beta1/instance_types_helpers_test.go | 2 + .../instance/instance_controller.go | 1 + .../cmd/testdata/deploy-kudo-ns.yaml.golden | 3 + .../cmd/testdata/deploy-kudo-sa.yaml.golden | 3 + .../testdata/deploy-kudo-webhook.yaml.golden | 3 + .../cmd/testdata/deploy-kudo.yaml.golden | 3 + pkg/kudoctl/kudoinit/crd/bindata.go | 2 +- pkg/kudoctl/kudoinit/prereq/webhook.go | 7 +- pkg/webhook/instance_admission.go | 15 +- .../instance_admission_integration_test.go | 307 ++++++++++++++++++ pkg/webhook/instance_admission_test.go | 4 +- 15 files changed, 341 insertions(+), 17 deletions(-) create mode 100644 pkg/webhook/instance_admission_integration_test.go diff --git a/config/crds/kudo.dev_instances.yaml b/config/crds/kudo.dev_instances.yaml index d797b5469..38a46f749 100644 --- a/config/crds/kudo.dev_instances.yaml +++ b/config/crds/kudo.dev_instances.yaml @@ -94,6 +94,9 @@ spec: properties: planName: type: string + status: + description: ExecutionStatus captures the state of the rollout. + type: string uid: description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being diff --git a/go.mod b/go.mod index af7cf4fdc..5404a0547 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,9 @@ require ( github.com/gosuri/uitable v0.0.4 github.com/kudobuilder/kuttl v0.1.0 github.com/manifoldco/promptui v0.6.0 + github.com/onsi/ginkgo v1.11.0 + github.com/onsi/gomega v1.8.1 + github.com/prometheus/common v0.4.1 github.com/spf13/afero v1.2.2 github.com/spf13/cobra v0.0.5 github.com/spf13/pflag v1.0.5 diff --git a/pkg/apis/kudo/v1beta1/instance_types.go b/pkg/apis/kudo/v1beta1/instance_types.go index 8f4660378..87b002272 100644 --- a/pkg/apis/kudo/v1beta1/instance_types.go +++ b/pkg/apis/kudo/v1beta1/instance_types.go @@ -47,6 +47,7 @@ type InstanceSpec struct { type PlanExecution struct { PlanName string `json:"planName,omitempty"` UID apimachinerytypes.UID `json:"uid,omitempty"` + Status ExecutionStatus `json:"status,omitempty"` // Future PE options like Force: bool. Not needed for now } diff --git a/pkg/apis/kudo/v1beta1/instance_types_helpers.go b/pkg/apis/kudo/v1beta1/instance_types_helpers.go index 0cff6f469..00a46a9bd 100644 --- a/pkg/apis/kudo/v1beta1/instance_types_helpers.go +++ b/pkg/apis/kudo/v1beta1/instance_types_helpers.go @@ -66,6 +66,7 @@ func (i *Instance) UpdateInstanceStatus(planStatus *PlanStatus, updatedTimestamp if v.Name == planStatus.Name { planStatus.LastUpdatedTimestamp = updatedTimestamp i.Status.PlanStatus[k] = *planStatus + i.Spec.PlanExecution.Status = planStatus.Status i.Status.AggregatedStatus.Status = planStatus.Status } } diff --git a/pkg/apis/kudo/v1beta1/instance_types_helpers_test.go b/pkg/apis/kudo/v1beta1/instance_types_helpers_test.go index 825201f81..d659fe451 100644 --- a/pkg/apis/kudo/v1beta1/instance_types_helpers_test.go +++ b/pkg/apis/kudo/v1beta1/instance_types_helpers_test.go @@ -130,6 +130,8 @@ func TestInstance_ResetPlanStatus(t *testing.T) { // we test that UID has changed. afterwards, we replace it with the old one and compare new // plan status with the desired state assert.NotEqual(t, instance.Status.PlanStatus["deploy"].UID, oldUID) + assert.Equal(t, instance.Spec.PlanExecution.Status, ExecutionPending) + oldPlanStatus := instance.Status.PlanStatus["deploy"] statusCopy := oldPlanStatus.DeepCopy() statusCopy.UID = testUUID diff --git a/pkg/controller/instance/instance_controller.go b/pkg/controller/instance/instance_controller.go index 1e99096ed..b7d614555 100644 --- a/pkg/controller/instance/instance_controller.go +++ b/pkg/controller/instance/instance_controller.go @@ -604,6 +604,7 @@ func fetchNewExecutionPlan(i *v1beta1.Instance, ov *v1beta1.OperatorVersion) (*s i.Spec.PlanExecution.PlanName = v1beta1.CleanupPlanName i.Spec.PlanExecution.UID = uuid.NewUUID() + i.Spec.PlanExecution.Status = v1beta1.ExecutionNeverRun } newPlanScheduled := func() bool { diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden index 8cf9c6f9d..96f4f9f75 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-ns.yaml.golden @@ -488,6 +488,9 @@ spec: properties: planName: type: string + status: + description: ExecutionStatus captures the state of the rollout. + type: string uid: description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden index a76634590..67709482b 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-sa.yaml.golden @@ -488,6 +488,9 @@ spec: properties: planName: type: string + status: + description: ExecutionStatus captures the state of the rollout. + type: string uid: description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo-webhook.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo-webhook.yaml.golden index 5783bd85c..34a8ade08 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo-webhook.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo-webhook.yaml.golden @@ -488,6 +488,9 @@ spec: properties: planName: type: string + status: + description: ExecutionStatus captures the state of the rollout. + type: string uid: description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being diff --git a/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden b/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden index b766b4dc5..c371e9b7b 100644 --- a/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden +++ b/pkg/kudoctl/cmd/testdata/deploy-kudo.yaml.golden @@ -488,6 +488,9 @@ spec: properties: planName: type: string + status: + description: ExecutionStatus captures the state of the rollout. + type: string uid: description: UID is a type that holds unique ID values, including UUIDs. Because we don't ONLY use UUIDs, this is an alias to string. Being diff --git a/pkg/kudoctl/kudoinit/crd/bindata.go b/pkg/kudoctl/kudoinit/crd/bindata.go index 4929063b0..87b39e13c 100644 --- a/pkg/kudoctl/kudoinit/crd/bindata.go +++ b/pkg/kudoctl/kudoinit/crd/bindata.go @@ -79,7 +79,7 @@ func (fi bindataFileInfo) Sys() interface{} { return nil } -var _configCrdsKudoDev_instancesYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xc4\x5a\x5b\x6f\x1b\xb9\xf5\x7f\xd7\xa7\x38\xf0\x3e\x38\xc6\x5a\xa3\x7f\xfc\x07\x8a\x42\x28\x0a\xa4\x4e\xb6\x50\x9b\xda\x46\xe4\xa4\x58\x64\x53\x80\x22\x8f\x34\xac\x39\xe4\x84\x17\xc9\xea\x66\xbf\x7b\x71\xc8\x99\xd1\x65\x66\x74\xc9\x6e\xb6\x7c\xb1\x87\x43\x9e\xcb\xef\x5c\x78\x78\x34\x83\xe1\x70\x38\x60\xa5\xfc\x80\xd6\x49\xa3\xc7\xc0\x4a\x89\xcf\x1e\x35\x3d\xb9\xec\xe9\x8f\x2e\x93\x66\xb4\x7c\x39\x43\xcf\x5e\x0e\x9e\xa4\x16\x63\xb8\x0d\xce\x9b\xe2\x1d\x3a\x13\x2c\xc7\xd7\x38\x97\x5a\x7a\x69\xf4\xa0\x40\xcf\x04\xf3\x6c\x3c\x00\x60\x5a\x1b\xcf\x68\xda\xd1\x23\x00\x37\xda\x5b\xa3\x14\xda\xe1\x02\x75\xf6\x14\x66\x38\x0b\x52\x09\xb4\x91\x43\xcd\x7f\xf9\x7f\xd9\x4d\xf6\x87\x01\x00\xb7\x18\xb7\x3f\xca\x02\x9d\x67\x45\x39\x06\x1d\x94\x1a\x00\x68\x56\xe0\x18\xa4\x76\x9e\x69\x8e\x2e\x7b\x0a\xc2\x64\x02\x97\x03\x57\x22\x27\x66\x0b\x6b\x42\x39\x86\x66\x3e\x6d\xa9\xe4\x48\x3a\x4c\xaa\xdd\x71\x4a\x49\xe7\xff\xbe\x33\xfd\x56\x3a\x1f\x5f\x95\x2a\x58\xa6\xb6\xb8\xc5\x59\x27\xf5\x22\x28\x66\x37\xf3\x03\x00\xc7\x4d\x89\x63\xb8\x23\x56\x25\xe3\x28\x68\x2e\xcc\x6c\x85\x53\xc5\xde\x79\xe6\x83\x1b\xc3\xcf\xbf\x0c\x00\x96\x4c\x49\x11\xb5\x4c\x2f\x4d\x89\xfa\xd5\xc3\xe4\xc3\xff\x4f\x79\x8e\x05\x4b\x93\x00\x02\x1d\xb7\xb2\x8c\xeb\x1a\x11\x41\x3a\xf0\x39\x42\x5a\x0a\x73\x63\xe3\x63\x23\x28\xbc\x7a\x98\x64\x15\x81\xd2\x9a\x12\xad\x97\xb5\x10\x34\xb6\x8c\xde\xcc\xed\xb1\xba\x24\x59\xd2\x1a\x10\x64\x66\x4c\x2c\x2b\x63\xa1\x00\x97\x98\x9b\x39\xf8\x5c\x3a\xb0\x58\x5a\x74\xa8\x93\xe1\xb7\xc8\x02\x2d\x61\x1a\xcc\xec\xdf\xc8\x7d\x06\x53\xb4\x44\x04\x5c\x6e\x82\x12\xe4\x1b\x4b\xb4\x1e\x2c\x72\xb3\xd0\xf2\x3f\x0d\x65\x07\xde\x44\x96\x8a\x79\xac\x4c\x52\x0f\xa9\x3d\x5a\xcd\x14\xa1\x18\xf0\x1a\x98\x16\x50\xb0\x35\x58\x24\x1e\x10\xf4\x16\xb5\xb8\xc4\x65\xf0\x0f\x63\x09\xa2\xb9\x19\x43\xee\x7d\xe9\xc6\xa3\xd1\x42\xfa\xda\xcd\xb9\x29\x8a\xa0\xa5\x5f\x8f\xa2\xb3\xca\x59\xf0\xc6\xba\x91\xc0\x25\xaa\x91\x93\x8b\x21\xb3\x3c\x97\x1e\xb9\x0f\x16\x47\xac\x94\xc3\x28\xb8\x8e\x5e\x9e\x15\xe2\xbb\xc6\xd6\x97\x5b\x92\xfa\x35\xb9\x85\xf3\x56\xea\x45\x33\x1d\xbd\xb0\x17\x77\x72\x46\xb2\x2f\xab\xb6\x25\xf9\x37\xf0\xd2\x14\xa1\xf2\xee\xcd\xf4\x11\x6a\xa6\xd1\x04\xbb\x98\x47\xb4\x37\xdb\xdc\x06\x78\x02\x4a\xea\x39\xda\x64\xb8\xb9\x35\x45\xa4\x88\x5a\x94\x46\x6a\x1f\x1f\xb8\x92\xa8\x77\x41\x77\x61\x56\x48\x4f\x96\xfe\x1c\xd0\x79\xb2\x4f\x06\xb7\x31\xd8\x61\x86\x10\x4a\xc1\x3c\x8a\x0c\x26\x1a\x6e\x59\x81\xea\x96\x39\xfc\xe6\xb0\x13\xc2\x6e\x48\x90\x1e\x07\x7e\x3b\x47\xed\x2e\x4c\x68\x35\xd3\x75\x32\xe9\xb4\x50\x1d\x84\xd3\x12\xf9\x4e\x68\x08\x74\xd2\x92\xfb\x7a\xe6\x91\x9c\xbe\x5e\x99\x6d\x91\xea\x0a\xc7\x2a\xfc\x2d\xf3\xc6\x76\xc4\x65\x4b\x82\xfb\xdd\xb5\x51\x5c\x39\x97\x48\x4e\x63\x71\x8e\x16\x29\x47\x78\x43\x3e\x94\x5e\xf1\xfd\x3d\x7b\xe4\x6b\x7f\xc9\xf6\xe6\xfb\xa4\x85\xde\x24\xd2\x29\xf0\xab\x87\x49\x9d\x38\x52\xbe\xc0\x5a\xce\x16\xc7\x5e\xe3\xd5\x63\x2e\x51\x89\x07\xe6\xf3\xa3\x5c\x2f\x27\xf3\xc4\x26\x86\x51\x84\xa3\x94\xc8\x71\x27\x1f\xc5\xa4\x89\x4c\xa4\xc9\x0e\x92\x00\xe4\x6d\x16\xab\xf5\xd7\x29\x68\xaa\xd8\xdc\xe4\x30\xcf\xa4\x06\x96\xb2\x3a\xfc\x6d\x7a\x7f\x37\xfa\xab\x49\xb2\x76\xd2\x64\x9c\xa3\x73\xc9\x55\x0a\xd4\xfe\x1a\x5c\xe0\x39\x30\x57\x7b\xd1\x94\xde\x64\x05\xd3\x72\x8e\xce\x67\x15\x07\xb4\xee\xe3\xcd\xa7\x2e\xcc\x00\x7e\x30\x16\xf0\x99\x15\xa5\xc2\x6b\x90\x09\xe5\x26\x0b\xd4\x4e\x21\x5d\x02\xa2\xa1\x07\x2b\xe9\x73\xd9\xad\x38\x83\xd2\x88\x4a\xe1\x55\x54\xd4\xb3\x27\x04\x53\x29\x1a\x10\x94\x7c\xc2\x31\x5c\x90\x97\x6d\x89\xf8\x33\x1d\xb9\xbf\x5c\x74\xd2\x7c\xb1\xca\xd1\x22\x5c\xd0\x92\x8b\x24\x58\x93\xe8\x69\xae\xf6\x8f\x8d\x80\x3e\x67\x1e\xbc\x95\x8b\x05\x5a\xec\x46\x33\x66\x2f\xca\x0a\x57\x60\x2c\xe9\xae\xcd\x16\x81\x48\x96\x6c\x56\x85\x89\x68\x09\xfc\xf1\xe6\x53\x8f\xb4\xbb\x38\x81\xd4\x02\x9f\xe1\x06\xa4\x4e\xa8\x94\x46\x5c\x65\xf0\x18\x3d\x62\xad\x3d\x7b\x26\x3e\x3c\x37\x0e\x35\x18\xad\xd6\xdd\xd2\x1a\xc8\xd9\x12\xc1\x99\x02\x61\x85\x4a\x0d\x53\x16\x11\xb0\x62\x6b\xd2\xbf\x36\x17\x79\x18\x83\x92\x59\xbf\x7b\x84\x76\x52\x7d\xbc\x7f\x7d\x3f\x4e\x52\x91\x0b\x2d\x34\x89\x42\xa9\x79\x2e\xe9\xa0\xa4\x13\x32\xa5\x7b\xf2\xc9\x08\x47\x48\xce\xe1\x0d\xf0\x9c\xe9\x05\x76\x92\x8d\x9a\x22\xcc\x03\x25\xe0\xec\xf2\xdc\x68\xdd\x3f\xeb\xea\xd1\x71\xe6\xed\x27\x86\xff\xd1\xc9\x71\x92\x5a\xb1\x0c\x3d\xaa\xd6\xdd\x96\x3f\x1f\x54\x8b\x0a\x62\xab\xd1\x63\xd4\x4c\x18\xee\x48\x29\x8e\xa5\x77\x23\xb3\x44\xbb\x94\xb8\x1a\xad\x8c\x7d\x92\x7a\x31\x24\x47\x1c\x26\x4f\x70\xa3\x58\xdc\x8e\xbe\x8b\x7f\xbe\x4a\x8b\x58\xae\x9e\xa6\x4a\x5c\xfa\x7b\xe8\x43\x7c\xdc\xe8\x6c\x75\xea\x62\xe8\xd4\x53\xe9\x72\x5a\x1f\x8e\x7b\x3b\x29\x24\x56\xb9\xe4\x79\x5d\xd9\x6e\xb2\x67\x67\x8c\x14\x4c\xa4\x94\xcb\xf4\xfa\x9b\xbb\x2d\x01\x19\x2c\xc9\xb3\x1e\x56\xf7\xaa\x21\xd3\x82\xfe\x77\xd2\x79\x9a\x3f\x1b\xb9\x20\x4f\x08\xd2\xf7\x93\xd7\xbf\x8f\x33\x07\x79\x76\x44\x76\x56\x71\x34\x4a\x66\x59\x81\x1e\x6d\xab\x80\x61\x42\xc4\x9b\x2b\x53\x0f\x07\x8a\x9c\xaf\xe2\xa9\x98\x7e\xf3\x8c\x3c\xf8\x63\x85\xdc\xe5\x63\x3c\x0c\x99\x45\xf0\x2b\x43\xe9\x9f\x4a\x38\xda\x0f\x58\x13\x00\xce\x34\x95\xd7\xcd\x09\x38\x06\x78\x79\xd5\x12\x54\x6a\x21\x2d\x72\xaf\xd6\xe0\x73\x6b\xc2\x22\xaf\x0a\xf2\x78\x74\x00\x37\xd6\xa2\x2b\x8d\x16\x74\xa8\x34\xa8\xd4\xe9\x7d\xbb\xa6\xcd\x1e\x1a\xcc\x5a\x5c\x0a\x56\x02\xdc\x5c\x41\x8b\x97\x43\x1f\x6f\x26\x95\x83\xec\xd2\xdb\xc6\x23\x3e\x51\x36\xe9\x2e\xec\xe0\x9f\xb9\x54\xd8\x68\x03\x2f\x5e\x5e\xd5\x9a\x3b\xc8\x59\x59\xa2\x76\x74\xd4\xdb\x35\x78\x59\x20\x30\x08\x0e\x6d\x75\x80\xb5\xe5\x65\x1b\x55\xaf\x81\x6d\xc4\x7e\x71\x73\xb5\x01\x34\x01\x1e\x03\xdd\xd1\x15\x49\x34\x17\x6a\x27\x7d\x48\x7d\x8c\x16\xe5\x55\x8e\x7a\xcb\xbb\x40\x18\x74\xfa\xf2\xd2\x57\xa2\x00\x66\x8b\x8c\xd8\xa3\x95\x46\x48\x0e\x33\xc6\x9f\x42\x19\xeb\xaf\xde\x52\x86\xa2\xc3\x4a\x51\xdf\xf0\xf0\x59\xba\x08\x6a\xb5\x77\x2e\x15\x66\xf0\xaa\xf1\x5b\xb5\xae\x6a\x33\x13\x51\xb1\xc6\x14\x6d\x50\x8d\x25\x07\xe2\xa8\x62\x31\x41\xc7\xec\x86\x49\xca\x23\x84\x87\x0d\x5a\x47\xc7\x50\x4c\xbb\xbd\x33\xbf\x45\xf3\xce\x78\x1c\xc3\x8e\x55\x2b\xe3\xd5\xb7\xa1\x08\x68\x2c\xbb\x88\x63\x8f\xef\xb5\x31\x8d\x95\xde\x64\x0a\xb7\xef\xdf\xbd\x7b\x73\xf7\xf8\xf6\xc7\x2a\x0a\xe8\x52\x79\x1f\xaf\x34\x5b\x4d\x8e\xad\xa6\x12\xbc\x98\xdc\x5e\x11\xb4\xc2\xe8\xb6\x5f\xc5\xc2\x2d\xe1\x59\x49\x7b\xbd\x5d\x09\xad\xa4\x52\x14\x5f\x5c\x21\xb3\xc4\xe9\x0d\xe3\xf9\x5e\x0c\xb6\x68\xe6\x8c\x02\x35\x68\xf9\x39\x20\x50\x62\x74\xa6\xbe\x0b\x44\xbf\x21\xd5\x23\x89\x19\x25\xcb\xe1\xc6\xd5\xa4\x4f\x0c\xa9\x00\xec\xf0\x56\x8d\x2b\x22\xb7\x9f\xfd\x0e\x5d\xc3\xca\x2a\x9e\xba\x12\xf8\xaf\x4e\xfa\xa4\x5a\xec\x45\x10\xa5\x64\x9f\xdc\x28\xe1\x6a\xd5\x27\xaf\xab\xf6\xca\x35\x48\xcd\x55\x10\x5d\x8c\x68\xbc\x7f\x3f\x79\xed\x32\x80\xbf\x20\x67\xc1\x51\xd9\x4b\xc6\xba\xf4\x70\x7f\xf7\xf6\x47\x8a\xe1\xb4\xa2\xb2\x0c\xb1\xd4\xc0\x94\x4c\x4d\xa0\xa4\x40\xdc\xdd\x47\xbf\x92\x90\xb3\x92\x7c\xd6\xc5\x06\x91\xf6\xd1\xfd\x72\x54\xa5\x83\x82\x6e\x2e\x2e\xd8\x4a\x0b\x62\x16\xdf\xc6\x33\xa7\x93\xa4\x30\xb1\x7c\x5e\xa0\x27\x57\x9b\xab\xd8\xdc\xf8\x4d\x8e\xa5\xee\x9e\x43\x6a\x0e\x1e\xed\x3a\xc4\x65\x3b\x7d\x07\x33\xab\xd2\x56\xab\xf1\x70\x42\xdf\x81\x2d\x16\x16\x17\xa4\xdb\xb4\x25\x40\x4b\x88\x57\x7b\x8b\xc9\x50\xf5\x91\x5e\x5d\x51\x9a\xf0\x74\xb5\xa0\x56\x2e\x3b\x72\x5d\xd3\x75\x8a\x61\x92\x16\x9f\xd3\x7c\xe0\x5e\x2e\xf1\xe1\x6b\x7d\xbf\x0d\x76\xa7\xbe\x4d\x7e\xab\xd4\x6d\xdc\x2b\x06\x79\x8d\x76\xac\x88\x8c\x52\x26\x9c\xdb\xcd\x38\x58\x42\x74\xdb\xe3\xb4\xb2\x65\x47\x8b\x8b\x87\x86\x1a\x6c\x37\x6a\x63\x03\x23\x4d\xc7\x1a\x21\x5a\xe2\x27\x0d\x8f\x39\xba\xae\xcb\x20\x95\x29\xa9\xbf\x11\x55\x4f\xf1\xe3\x2d\xd3\x2e\x4a\xe4\x68\x6f\xf7\xf8\x02\x77\x74\x66\x77\xd0\xac\x13\x3c\x7c\xe9\xd9\xba\x21\x71\x78\x2c\x3b\x88\xc7\x3d\x6f\xac\x35\x36\x3e\xfd\x69\x18\xc7\x9f\xe3\xf4\x03\xa6\xe3\x68\x9b\xf6\xbf\xfa\x78\x77\xd0\xfe\xe9\xb0\x58\xcb\x1e\xb1\xbf\x4f\x32\x0c\xeb\xbf\xc3\xef\x4f\xa7\xdd\xde\xdb\xc3\xe4\x04\xb9\x97\xbd\x72\x7f\x81\x1f\x98\x67\x0a\x30\xe2\xd6\x90\x8e\xff\xdc\x9a\xa2\x54\xe8\xbb\x9c\x83\xe8\x7e\x69\x37\x51\x0e\xc5\x30\x80\x62\xce\xbf\x4f\x6d\xe3\xcd\x4f\x3d\x9d\xc9\x78\x6e\x6c\xc1\xfc\x18\x68\xed\x90\x2a\xbf\xce\x55\x3a\x28\xc5\x66\x0a\xc7\xe0\x6d\xe8\x5e\x72\x30\x2d\x00\x14\xe8\x1c\x5b\x74\x26\x94\xa3\x7b\xfb\x9a\x02\x47\x37\x96\x39\x73\xdd\x00\x01\x48\x8f\x45\xcf\xab\xbd\x30\x7f\x20\x2a\xa7\x84\x39\xad\xeb\x21\x78\xd8\x5c\x69\x1c\x84\xe8\x24\x7d\xd3\xe8\x87\xeb\x0c\x22\xfd\x89\xbc\x1e\xbf\x7d\x42\x3f\x53\x40\x2c\x0f\xca\x77\xd0\xc0\x1d\x2a\x4c\x3d\x96\x27\x58\x99\xf8\x1e\x24\x7a\x8a\xa9\xd3\x38\xc1\xe0\x69\x9c\x04\x48\x1a\xc7\x8c\x7f\x36\xc1\xe3\x8e\x90\xc6\xf9\xee\x70\x94\x24\x9c\xea\x30\x67\x2a\xd5\x5b\x1d\x74\x2d\x63\xd6\xb2\xee\x76\xf3\x09\x84\x0e\x93\x38\x04\xed\xb7\x88\xae\x23\x00\xf5\xdc\x5b\xbe\xd5\xcd\xe5\xd7\xdd\x5d\x7a\x48\xee\xde\x68\xce\xbd\xbd\xf4\xc9\xb9\x73\xa7\x39\xf9\xfe\x72\x04\xf0\x03\xce\xb3\x03\xb8\x53\x92\x63\xf5\x33\xd1\x0c\x01\x75\xec\x0b\xc5\xfe\xd6\x2c\x78\x02\x8d\xa7\x9f\x8a\x09\xb0\xb4\x78\x96\x00\x6d\xf7\x41\xac\x40\x4b\xae\xe2\xf0\x73\x48\x8d\x57\x0d\x6b\x56\xa8\xf8\xeb\x8a\xd1\x4e\x8a\x78\x99\x76\x72\xa1\xe5\x5c\x72\xa6\x3d\xac\x62\xdf\x28\xb2\x93\xfe\xb2\x7d\xa3\xd3\x66\x5f\xfa\xd3\x2f\x67\x7b\x53\x9b\xef\x55\xaa\x4f\x63\x9a\xa9\x18\x24\xc3\xea\x23\x95\xcd\x5b\x80\x74\x41\xdb\x2a\x4b\x9c\x37\x96\x72\x6a\x9a\xd9\x44\x18\xe3\x1c\x4b\x8f\xe2\x6e\xff\xa3\x95\x8b\x54\x5b\xd5\xdf\xa4\xc4\x47\x6e\x74\xba\x0c\xb8\x31\x7c\xfc\x34\x48\x54\x51\x7c\xa8\x85\xa1\xc9\xff\x06\x00\x00\xff\xff\xb9\xf2\xc3\xaa\xe5\x23\x00\x00") +var _configCrdsKudoDev_instancesYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xd4\x5a\xeb\x6f\x1b\xb9\x11\xff\xae\xbf\x62\xe0\xfb\xe0\x18\x67\xad\x1a\x17\x28\x0a\xa1\x28\x90\x3a\xb9\x42\x6d\x6a\x1b\x91\x93\xe2\x90\x4b\x01\x8a\x1c\x69\x59\x73\xc9\x0d\x1f\x92\xd5\xcb\xfd\xef\xc5\x90\xbb\xab\xc7\xee\xea\x71\xbd\x5c\x71\xfc\x92\x88\x22\xe7\xf1\x9b\x07\x67\xc6\x1a\x0c\x87\xc3\x01\x2b\xe5\x07\xb4\x4e\x1a\x3d\x06\x56\x4a\x7c\xf6\xa8\xe9\x93\xcb\x9e\xfe\xe8\x32\x69\x46\xcb\x97\x33\xf4\xec\xe5\xe0\x49\x6a\x31\x86\xdb\xe0\xbc\x29\xde\xa1\x33\xc1\x72\x7c\x8d\x73\xa9\xa5\x97\x46\x0f\x0a\xf4\x4c\x30\xcf\xc6\x03\x00\xa6\xb5\xf1\x8c\xb6\x1d\x7d\x04\xe0\x46\x7b\x6b\x94\x42\x3b\x5c\xa0\xce\x9e\xc2\x0c\x67\x41\x2a\x81\x36\x72\xa8\xf9\x2f\x7f\x97\xdd\x64\x7f\x18\x00\x70\x8b\xf1\xfa\xa3\x2c\xd0\x79\x56\x94\x63\xd0\x41\xa9\x01\x80\x66\x05\x8e\x41\x6a\xe7\x99\xe6\xe8\xb2\xa7\x20\x4c\x26\x70\x39\x70\x25\x72\x62\xb6\xb0\x26\x94\x63\x68\xf6\xd3\x95\x4a\x8e\xa4\xc3\xa4\xba\x1d\xb7\x94\x74\xfe\xef\x3b\xdb\x6f\xa5\xf3\xf1\xab\x52\x05\xcb\xd4\x16\xb7\xb8\xeb\xa4\x5e\x04\xc5\xec\x66\x7f\x00\xe0\xb8\x29\x71\x0c\x77\xc4\xaa\x64\x1c\x05\xed\x85\x99\xad\x70\xaa\xd8\x3b\xcf\x7c\x70\x63\xf8\xf1\xa7\x01\xc0\x92\x29\x29\xa2\x96\xe9\x4b\x53\xa2\x7e\xf5\x30\xf9\xf0\xfb\x29\xcf\xb1\x60\x69\x13\x40\xa0\xe3\x56\x96\xf1\x5c\x23\x22\x48\x07\x3e\x47\x48\x47\x61\x6e\x6c\xfc\xd8\x08\x0a\xaf\x1e\x26\x59\x45\xa0\xb4\xa6\x44\xeb\x65\x2d\x04\xad\x2d\xa3\x37\x7b\x7b\xac\x2e\x49\x96\x74\x06\x04\x99\x19\x13\xcb\xca\x58\x28\xc0\x25\xe6\x66\x0e\x3e\x97\x0e\x2c\x96\x16\x1d\xea\x64\xf8\x2d\xb2\x40\x47\x98\x06\x33\xfb\x37\x72\x9f\xc1\x14\x2d\x11\x01\x97\x9b\xa0\x04\xf9\xc6\x12\xad\x07\x8b\xdc\x2c\xb4\xfc\x4f\x43\xd9\x81\x37\x91\xa5\x62\x1e\x2b\x93\xd4\x4b\x6a\x8f\x56\x33\x45\x28\x06\xbc\x06\xa6\x05\x14\x6c\x0d\x16\x89\x07\x04\xbd\x45\x2d\x1e\x71\x19\xfc\xc3\x58\x82\x68\x6e\xc6\x90\x7b\x5f\xba\xf1\x68\xb4\x90\xbe\x76\x73\x6e\x8a\x22\x68\xe9\xd7\xa3\xe8\xac\x72\x16\xbc\xb1\x6e\x24\x70\x89\x6a\xe4\xe4\x62\xc8\x2c\xcf\xa5\x47\xee\x83\xc5\x11\x2b\xe5\x30\x0a\xae\xa3\x97\x67\x85\xf8\xa6\xb1\xf5\xe5\x96\xa4\x7e\x4d\x6e\xe1\xbc\x95\x7a\xd1\x6c\x47\x2f\xec\xc5\x9d\x9c\x91\xec\xcb\xaa\x6b\x49\xfe\x0d\xbc\xb4\x45\xa8\xbc\x7b\x33\x7d\x84\x9a\x69\x34\xc1\x2e\xe6\x11\xed\xcd\x35\xb7\x01\x9e\x80\x92\x7a\x8e\x36\x19\x6e\x6e\x4d\x11\x29\xa2\x16\xa5\x91\xda\xc7\x0f\x5c\x49\xd4\xbb\xa0\xbb\x30\x2b\xa4\x27\x4b\x7f\x0e\xe8\x3c\xd9\x27\x83\xdb\x18\xec\x30\x43\x08\xa5\x60\x1e\x45\x06\x13\x0d\xb7\xac\x40\x75\xcb\x1c\x7e\x75\xd8\x09\x61\x37\x24\x48\x8f\x03\xbf\x9d\xa3\x76\x0f\x26\xb4\x9a\xed\x3a\x99\x74\x5a\xa8\x0e\xc2\x69\x89\x7c\x27\x34\x04\x3a\x69\xc9\x7d\x3d\xf3\x48\x4e\x5f\x9f\xcc\xb6\x48\x75\x85\x63\x15\xfe\x96\x79\x63\x3b\xe2\xb2\x25\xc1\xfd\xee\xd9\x28\xae\x9c\x4b\x24\xa7\xb1\x38\x47\x8b\x94\x23\xbc\x21\x1f\x4a\x5f\xf1\xfd\x3b\x7b\xe4\x6b\x7f\xc9\xf6\xf6\xfb\xa4\x85\xde\x24\xd2\x29\xf0\xab\x87\x49\x9d\x38\x52\xbe\xc0\x5a\xce\x16\xc7\x5e\xe3\xd5\x6b\x2e\x51\x89\x07\xe6\xf3\xa3\x5c\x2f\x27\xf3\xc4\x26\x86\x51\x84\xa3\x94\xc8\x71\x27\x1f\xc5\xa4\x89\x4c\xa4\xcd\x0e\x92\x00\xe4\x6d\x16\xab\xf3\xd7\x29\x68\xaa\xd8\xdc\xe4\x30\xcf\xa4\x06\x96\xb2\x3a\xfc\x6d\x7a\x7f\x37\xfa\xab\x49\xb2\x76\xd2\x64\x9c\xa3\x73\xc9\x55\x0a\xd4\xfe\x1a\x5c\xe0\x39\x30\x57\x7b\xd1\x94\xbe\xc9\x0a\xa6\xe5\x1c\x9d\xcf\x2a\x0e\x68\xdd\xc7\x9b\x4f\x5d\x98\x01\x7c\x67\x2c\xe0\x33\x2b\x4a\x85\xd7\x20\x13\xca\x4d\x16\xa8\x9d\x42\xba\x04\x44\x43\x0f\x56\xd2\xe7\xb2\x5b\x71\x06\xa5\x11\x95\xc2\xab\xa8\xa8\x67\x4f\x08\xa6\x52\x34\x20\x28\xf9\x84\x63\xb8\x20\x2f\xdb\x12\xf1\x47\x7a\x72\x7f\xba\xe8\xa4\xf9\x62\x95\xa3\x45\xb8\xa0\x23\x17\x49\xb0\x26\xd1\xd3\x5e\xed\x1f\x1b\x01\x7d\xce\x3c\x78\x2b\x17\x0b\xb4\xd8\x8d\x66\xcc\x5e\x94\x15\xae\xc0\x58\xd2\x5d\x9b\x2d\x02\x91\x2c\xd9\xac\x0a\x13\xd1\x12\xf8\xe3\xcd\xa7\x1e\x69\x77\x71\x02\xa9\x05\x3e\xc3\x0d\x48\x9d\x50\x29\x8d\xb8\xca\xe0\x31\x7a\xc4\x5a\x7b\xf6\x4c\x7c\x78\x6e\x1c\x6a\x30\x5a\xad\xbb\xa5\x35\x90\xb3\x25\x82\x33\x05\xc2\x0a\x95\x1a\xa6\x2c\x22\x60\xc5\xd6\xa4\x7f\x6d\x2e\xf2\x30\x06\x25\xb3\x7e\xf7\x09\xed\xa4\xfa\x78\xff\xfa\x7e\x9c\xa4\x22\x17\x5a\x68\x12\x85\x52\xf3\x5c\xd2\x43\x49\x2f\x64\x4a\xf7\xe4\x93\x11\x8e\x90\x9c\xc3\x1b\xe0\x39\xd3\x0b\xec\x24\x1b\x35\x45\x98\x07\x4a\xc0\xd9\xe5\xb9\xd1\xba\xff\xd6\xd5\xab\xe3\xcd\xdb\x4f\x0c\xff\xa7\x97\xe3\x24\xb5\x62\x19\x7a\x54\xad\xbb\x2d\x7f\x3e\xa8\x16\x15\xc4\x56\xa3\xc7\xa8\x99\x30\xdc\x91\x52\x1c\x4b\xef\x46\x66\x89\x76\x29\x71\x35\x5a\x19\xfb\x24\xf5\x62\x48\x8e\x38\x4c\x9e\xe0\x46\xb1\xb8\x1d\x7d\x13\xff\xf9\x59\x5a\xc4\x72\xf5\x34\x55\xe2\xd1\x5f\x43\x1f\xe2\xe3\x46\x67\xab\x53\x17\x43\xa7\xbe\x4a\x97\xd3\xfa\x71\xdc\xbb\x49\x21\xb1\xca\x25\xcf\xeb\xca\x76\x93\x3d\x3b\x63\xa4\x60\x22\xa5\x5c\xa6\xd7\x5f\xdd\x6d\x09\xc8\x60\x49\x9e\xf5\xb0\xea\xab\x86\x4c\x0b\xfa\xbf\x93\xce\xd3\xfe\xd9\xc8\x05\x79\x42\x90\xbe\x9f\xbc\xfe\x75\x9c\x39\xc8\xb3\x23\xb2\xb3\x8a\xa3\x55\x32\xcb\x0a\xf4\x68\x5b\x05\x0c\x13\x22\x76\xae\x4c\x3d\x1c\x28\x72\x7e\x16\x4f\xc5\xf4\x9b\x67\xe4\xc1\x1f\x2b\xe4\x2e\x1f\xe3\x63\xc8\x2c\x82\x5f\x19\x4a\xff\x54\xc2\xd1\x7d\xc0\x9a\x00\x70\xa6\xa9\xbc\x6e\x5e\xc0\x31\xc0\xcb\xab\x96\xa0\x52\x0b\x69\x91\x7b\xb5\x06\x9f\x5b\x13\x16\x79\x55\x90\xc7\xa7\x03\xb8\xb1\x16\x5d\x69\xb4\xa0\x47\xa5\x41\xa5\x4e\xef\xdb\x35\x6d\xf6\xd0\x60\xd6\xe2\x52\xb0\x12\xe0\xe6\x0a\x5a\xbc\x1c\xfa\xd8\x99\x54\x0e\xb2\x4b\x6f\x1b\x8f\xf8\x89\xb2\x49\x77\x61\x07\xff\xcc\xa5\xc2\x46\x1b\x78\xf1\xf2\xaa\xd6\xdc\x41\xce\xca\x12\xb5\xa3\xa7\xde\xae\xc1\xcb\x02\x81\x41\x70\x68\xab\x07\xac\x2d\x2f\xdb\xa8\x7a\x0d\x6c\x23\xf6\x8b\x9b\xab\x0d\xa0\x09\xf0\x18\xe8\x8e\x5a\x24\xd1\x34\xd4\x4e\xfa\x90\xe6\x18\x2d\xca\xab\x1c\xf5\x96\x77\x81\x30\xe8\xf4\xe5\xa5\xaf\x44\x01\xcc\x16\x19\xb1\x47\x2b\x8d\x90\x1c\x66\x8c\x3f\x85\x32\xd6\x5f\xbd\xa5\x0c\x45\x87\x95\xa2\xee\xf0\xf0\x59\xba\x08\x6a\x75\x77\x2e\x15\x66\xf0\xaa\xf1\x5b\xb5\xae\x6a\x33\x13\x51\xb1\xc6\x14\x6d\x50\x8d\x25\x07\xe2\xa8\x62\x31\x41\xcf\xec\x86\x49\xca\x23\x84\x87\x0d\x5a\x47\xc7\x50\x4c\xbb\xbd\x37\xbf\x45\xf3\xce\x78\x1c\xc3\x8e\x55\x2b\xe3\xd5\xdd\x50\x04\x34\x96\x5d\xc4\xb1\xc7\xf7\xda\x98\xc6\x4a\x6f\x32\x85\xdb\xf7\xef\xde\xbd\xb9\x7b\x7c\xfb\x7d\x15\x05\xd4\x54\xde\xc7\x96\x66\x6b\xc8\xb1\x35\x54\x82\x17\x93\xdb\x2b\x82\x56\x18\xdd\xf6\xab\x58\xb8\x25\x3c\x2b\x69\xaf\xb7\x2b\xa1\x95\x54\x8a\xe2\x8b\x2b\x64\x96\x38\xbd\x61\x3c\xdf\x8b\xc1\x16\xcd\x9c\x51\xa0\x06\x2d\x3f\x07\x04\x4a\x8c\xce\xd4\xbd\x40\xf4\x1b\x52\x3d\x92\x98\x51\xb2\x1c\x6e\x5c\x4d\xfa\xc4\x90\x0a\xc0\x0e\x6f\xd5\xb8\x22\x72\xfb\xd9\xef\x50\x1b\x56\x56\xf1\xd4\x95\xc0\x0f\x26\xfd\x6a\x1a\x75\x2c\xef\x37\x36\x9e\xc6\xf3\xc0\x59\x49\x6e\x91\x5a\xde\xa6\xd5\x8d\xaf\x82\x51\xca\x84\xf3\x3b\xba\x53\x5e\x1f\xc2\x38\x0e\x45\x88\x52\x72\x94\xdc\x28\xe1\x6a\x1b\x4c\x5e\x57\x73\x9e\x6b\x90\x9a\xab\x20\xba\x18\xd1\x7a\xff\x7e\xf2\xda\x65\x00\x7f\x41\xce\x82\xa3\xfa\x9b\xbc\xe6\xd2\xc3\xfd\xdd\xdb\xef\x29\x99\xa4\x13\x95\x8b\x10\x4b\x0d\x4c\xc9\x34\x8d\x4a\x0a\xc4\xdb\x7d\xf4\x2b\x09\x1b\x94\xa4\xf6\xa8\x7d\x8c\x83\x1c\x55\xe9\xa0\xa0\x16\xca\x05\x5b\x69\x41\xcc\xe2\xb7\xf1\xf1\xeb\x24\x29\x4c\xac\xe3\x17\xe8\xc9\xe7\xe7\x2a\x4e\x59\x7e\x91\xf7\xb1\x7b\xf8\xd1\xf2\x8b\xee\xf1\x47\x72\x87\xed\x01\x88\x99\x55\xf9\xb3\x35\x01\x39\x61\x00\xc2\x16\x0b\x8b\x0b\xd2\x6d\xda\xe9\x98\xbb\x03\x85\xbd\xc3\x64\xa8\xba\xb6\xa8\x7a\xa5\x26\x4f\xb8\x5a\x50\x2b\x97\x1d\x49\xb7\x19\x7f\xc5\x78\x4d\x87\xcf\x99\x82\x70\x2f\x97\xf8\xf0\x1b\x0f\xc2\x83\xb5\x4c\xb7\x3d\x4e\xab\x9f\x76\xb4\xb8\x78\x68\xa8\xc1\xf6\xc4\x38\x4e\x52\xd2\x76\x2c\x56\xa2\x25\x7e\xd0\xf0\x98\xa3\xeb\xea\x4a\xa9\x5e\x4a\x83\x96\xa8\x7a\x8a\x1f\x6f\x99\x76\x51\x22\x47\x77\xbb\xd7\x17\xb8\xa3\xe2\xa1\x83\x66\xfd\xd2\xc0\x97\x9e\xab\x1b\x12\x87\xd7\xb2\x83\x78\xbc\xf3\xc6\x5a\x63\xe3\xa7\x3f\x0d\xe3\xfa\x73\xdc\x7e\xc0\xf4\x2e\x6e\xd3\xfe\x57\x1f\xef\x0e\xda\x3f\x1c\x16\x6b\xd9\x23\xf6\xb7\x49\x86\x61\xfd\xef\xf0\xdb\xd3\x69\xb7\xef\xf6\x30\x39\x41\xee\x65\xaf\xdc\x5f\xe0\x3b\xe6\x99\x02\x8c\xb8\x35\xa4\xe3\x7f\x6e\x4d\x51\x2a\xf4\x5d\xce\x41\x74\xbf\xb4\xa7\x39\x87\x62\x18\x40\x31\xe7\xdf\xa7\xf9\xf5\xe6\x6f\x4e\x9d\xc9\x78\x6e\x6c\xc1\xfc\x18\xe8\xec\x90\x4a\xd0\xce\x53\x3a\x28\xc5\x66\x0a\xc7\xe0\x6d\xe8\x3e\x72\x30\x2d\x00\x14\xe8\x1c\x5b\x74\x26\x94\xa3\x77\xfb\xa6\x13\x47\x2f\x96\x39\x73\xdd\x00\x01\x48\x8f\x45\xcf\x57\x7b\x61\xfe\x40\x54\x4e\x09\x73\x3a\xd7\x43\xf0\xb0\xb9\xd2\x3a\x08\xd1\x49\xfa\xa6\xd5\x0f\xd7\x19\x44\xfa\x13\x79\xbd\x7e\xf9\x84\x7e\xa6\x80\x58\x1e\x94\xef\xa0\x81\x3b\x54\x98\x7a\x2c\x4f\xb0\x32\xf1\x3d\x48\xf4\x14\x53\xa7\x75\x82\xc1\xd3\x3a\x09\x90\xb4\x8e\x19\xff\x6c\x82\xc7\x1d\x21\xad\xf3\xdd\xe1\x28\x49\x38\xd5\x61\xce\x54\xaa\xb7\x3a\xe8\x3a\xc6\xac\x65\xdd\x73\xef\x13\x08\x1d\x26\x71\x08\xda\xaf\x11\x5d\x47\x00\xea\xe9\x5b\xbe\x56\xe7\xf2\xbf\xf5\x2e\x3d\x24\x77\x3b\x9a\x73\xbb\x97\x3e\x39\x77\x7a\x9a\x93\xfb\x97\x23\x80\x1f\x70\x9e\x1d\xc0\x9d\x92\x1c\xab\xbf\x57\xcd\x10\x50\xc7\x01\x55\x1c\xb4\xcd\x82\x27\xd0\x78\xfa\x9b\x35\x01\x96\x0e\xcf\x12\xa0\xed\x81\x8c\x15\x68\xc9\x55\x1c\x7e\x0e\x69\x02\xac\x61\xcd\x0a\x15\xff\xcc\x63\xb4\x93\x22\x76\xf5\x4e\x2e\xb4\x9c\x4b\xce\xb4\x87\x55\x1c\x60\x45\x76\xd2\x5f\xb6\x3b\x3a\x6d\xf6\xa5\x3f\xbd\x39\xdb\xdb\xda\xfc\x70\xa6\xfa\x8d\x4e\xb3\x15\x83\x64\x58\xfd\x5a\x66\xf3\x2d\x40\x6a\xd0\xb6\xca\x12\xe7\x8d\xa5\x9c\x9a\x76\x36\x11\xc6\x38\xc7\xd2\xa3\xb8\xdb\xff\xf5\xcc\x45\xaa\xad\xea\x1f\xc7\xc4\x8f\xdc\xe8\xd4\x0c\xb8\x31\x7c\xfc\x34\x48\x54\x51\x7c\xa8\x85\xa1\xcd\xff\x06\x00\x00\xff\xff\x7b\x41\x99\x30\x6e\x24\x00\x00") func configCrdsKudoDev_instancesYamlBytes() ([]byte, error) { return bindataRead( diff --git a/pkg/kudoctl/kudoinit/prereq/webhook.go b/pkg/kudoctl/kudoinit/prereq/webhook.go index 938377452..a52f65d58 100644 --- a/pkg/kudoctl/kudoinit/prereq/webhook.go +++ b/pkg/kudoctl/kudoinit/prereq/webhook.go @@ -59,7 +59,7 @@ func (k kudoWebHook) Install(client *kube.Client) error { if err := installUnstructured(client.DynamicClient, certificate(k.opts.Namespace)); err != nil { return err } - if err := installAdmissionWebhook(client.KubeClient.AdmissionregistrationV1beta1(), instanceAdmissionWebhook(k.opts.Namespace)); err != nil { + if err := installAdmissionWebhook(client.KubeClient.AdmissionregistrationV1beta1(), InstanceAdmissionWebhook(k.opts.Namespace)); err != nil { return err } return nil @@ -79,7 +79,7 @@ func (k kudoWebHook) AsRuntimeObjs() []runtime.Object { return make([]runtime.Object, 0) } - av := instanceAdmissionWebhook(k.opts.Namespace) + av := InstanceAdmissionWebhook(k.opts.Namespace) cert := certificate(k.opts.Namespace) objs := []runtime.Object{&av} for _, c := range cert { @@ -160,7 +160,8 @@ func installAdmissionWebhook(client clientv1beta1.MutatingWebhookConfigurationsG return err } -func instanceAdmissionWebhook(ns string) admissionv1beta1.MutatingWebhookConfiguration { +// InstanceAdmissionWebhook returns a MutatingWebhookConfiguration for the instance admission controller. +func InstanceAdmissionWebhook(ns string) admissionv1beta1.MutatingWebhookConfiguration { namespacedScope := admissionv1beta1.NamespacedScope failedType := admissionv1beta1.Fail equivalentType := admissionv1beta1.Equivalent diff --git a/pkg/webhook/instance_admission.go b/pkg/webhook/instance_admission.go index 4c5ef0cc8..1e1b71edd 100644 --- a/pkg/webhook/instance_admission.go +++ b/pkg/webhook/instance_admission.go @@ -10,7 +10,6 @@ import ( "github.com/thoas/go-funk" "k8s.io/api/admission/v1beta1" - "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/uuid" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" @@ -120,12 +119,14 @@ func handleUpdate(ia *InstanceAdmission, req admission.Request) admission.Respon return admission.Denied(err.Error()) } - // populate Instance.PlanExecution with the plan triggered by param change and evtl. a new UID + // populate Instance.PlanExecution with the plan triggered by param change and evtl. a new UID/Status if triggered != nil { new.Spec.PlanExecution.PlanName = *triggered new.Spec.PlanExecution.UID = "" + new.Spec.PlanExecution.Status = "" if *triggered != "" { - new.Spec.PlanExecution.UID = uuid.NewUUID() // if there is a new plan, generate new UID + new.Spec.PlanExecution.UID = uuid.NewUUID() // if there is a new plan, generate new UID + new.Spec.PlanExecution.Status = kudov1beta1.ExecutionNeverRun // and set status to NEVER_RUN } marshaled, err := json.Marshal(new) @@ -183,7 +184,7 @@ func admitUpdate(old, new *kudov1beta1.Instance, ov *kudov1beta1.OperatorVersion isPlanRetriggered := hadPlan && newPlan == oldPlan && newUID != oldUID isPlanCancellation := hadPlan && newPlan == "" isDeleting := new.IsDeleting() // a non-empty meta.deletionTimestamp is a signal to switch to the uninstalling life-cycle phase - isPlanTerminal := isTerminal(new, newPlan, new.Spec.PlanExecution.UID) + isPlanTerminal := new.Spec.PlanExecution.Status.IsTerminal() parameterDefs, err := changedParameterDefinitions(old.Spec.Parameters, new.Spec.Parameters, ov) if err != nil { @@ -284,12 +285,6 @@ func admitUpdate(old, new *kudov1beta1.Instance, ov *kudov1beta1.OperatorVersion } } -// isTerminal returns true if passed plan exists, has the same uid and is terminal -func isTerminal(i *kudov1beta1.Instance, plan string, uid types.UID) bool { - status := i.PlanStatus(plan) - return status != nil && status.UID == uid && status.Status.IsTerminal() -} - // triggeredByParameterUpdate determines what plan to run based on parameters that changed and the corresponding parameter trigger. func triggeredByParameterUpdate(params []kudov1beta1.Parameter, ov *kudov1beta1.OperatorVersion) (*string, error) { // If no parameters were changed, we return an empty string so no plan would be triggered diff --git a/pkg/webhook/instance_admission_integration_test.go b/pkg/webhook/instance_admission_integration_test.go new file mode 100644 index 000000000..1233df566 --- /dev/null +++ b/pkg/webhook/instance_admission_integration_test.go @@ -0,0 +1,307 @@ +// +build integration + +package webhook + +import ( + "context" + "fmt" + "log" + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/uuid" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/envtest" + "sigs.k8s.io/controller-runtime/pkg/envtest/printer" + "sigs.k8s.io/controller-runtime/pkg/manager" + ctrhook "sigs.k8s.io/controller-runtime/pkg/webhook" + + "github.com/kudobuilder/kudo/pkg/apis" + "github.com/kudobuilder/kudo/pkg/apis/kudo/v1beta1" + "github.com/kudobuilder/kudo/pkg/kudoctl/kudoinit/crd" + "github.com/kudobuilder/kudo/pkg/kudoctl/kudoinit/prereq" +) + +func TestSource(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecsWithDefaultAndCustomReporters(t, "Webhook Test Suite", []Reporter{printer.NewlineReporter{}}) +} + +var env *envtest.Environment +var instanceAdmissionWebhookPath string + +var _ = BeforeSuite(func() { + log.SetOutput(GinkgoWriter) + env = &envtest.Environment{} + + // 1. add webhook configuration to the env. we use the same webhook configuration that `kudo init` generates + log.Print("test.BeforeSuite: initializing webhook configuration") + iaw := prereq.InstanceAdmissionWebhook(v1.NamespaceDefault) + instanceAdmissionWebhookPath = *iaw.Webhooks[0].ClientConfig.Service.Path + + env.WebhookInstallOptions = envtest.WebhookInstallOptions{MutatingWebhooks: []runtime.Object{&iaw}} + + // 2. add KUDO CRDs + log.Print("test.BeforeSuite: initializing CRDs") + env.CRDs = crd.NewInitializer().Resources() + + _, err := env.Start() + Expect(err).NotTo(HaveOccurred()) +}, envtest.StartTimeout) + +var _ = AfterSuite(func() { + Expect(env.Stop()).NotTo(HaveOccurred()) +}, envtest.StopTimeout) + +var _ = Describe("Test", func() { + + var defaultTimeout float64 = 5 // seconds + + var c client.Client + var stop chan struct{} + + // every test case gets it's own manager and client instances. not sure if that's the best + // practice but it seems to be fast enough. + BeforeEach(func() { + // 1. initializing manager + log.Print("test.BeforeEach: initializing manager") + mgr, err := manager.New(env.Config, manager.Options{ + Port: env.WebhookInstallOptions.LocalServingPort, + Host: env.WebhookInstallOptions.LocalServingHost, + CertDir: env.WebhookInstallOptions.LocalServingCertDir, + MetricsBindAddress: "0", // disable metrics server + }) + Expect(err).NotTo(HaveOccurred()) + + // 2. initializing scheme with v1beta1 types + log.Print("test.BeforeEach: initializing scheme") + err = apis.AddToScheme(mgr.GetScheme()) + Expect(err).NotTo(HaveOccurred()) + + // 3. registering instance admission controller + log.Print("test.BeforeEach: initializing webhook server") + server := mgr.GetWebhookServer() + server.Register(instanceAdmissionWebhookPath, &ctrhook.Admission{Handler: &InstanceAdmission{}}) + + // 4. starting the manager + stop = make(chan struct{}) + go func() { + err = mgr.Start(stop) + Expect(err).NotTo(HaveOccurred()) + }() + + // 5. creating the client. **Note:** client.New method will create an uncached client, a cached one + // (e.g. mgr.GetClient) leads to caching issues in this test. + log.Print("test.BeforeEach: initializing client") + c, err = client.New(env.Config, client.Options{Scheme: mgr.GetScheme()}) + Expect(err).NotTo(HaveOccurred()) + }) + + AfterEach(func() { + close(stop) + }) + + deploy := v1beta1.DeployPlanName + update := v1beta1.UpdatePlanName + cleanup := v1beta1.CleanupPlanName + + ov := &v1beta1.OperatorVersion{ + ObjectMeta: metav1.ObjectMeta{Name: "foo-operator", Namespace: "default"}, + TypeMeta: metav1.TypeMeta{Kind: "OperatorVersion", APIVersion: "kudo.dev/v1beta1"}, + Spec: v1beta1.OperatorVersionSpec{ + Plans: map[string]v1beta1.Plan{deploy: {}, update: {}, cleanup: {}}, + Parameters: []v1beta1.Parameter{ + { + Name: "foo", + Trigger: deploy, + }, + { + Name: "bar", + Trigger: update, + }, + }, + }, + } + + idle := &v1beta1.Instance{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "kudo.dev/v1beta1", + Kind: "Instance", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "foo-instance", + Namespace: "default", + }, + Spec: v1beta1.InstanceSpec{ + OperatorVersion: v1.ObjectReference{Name: "foo-operator"}, + }, + } + + Describe("Instance admission controller", func() { + var key client.ObjectKey + + It("should allow instance creation", func() { + // 1. create the OV first + ov := ov.DeepCopy() + err := c.Create(context.TODO(), ov) + Expect(err).NotTo(HaveOccurred()) + + // 2. create the initial Instance + err = c.Create(context.TODO(), idle) + Expect(err).NotTo(HaveOccurred()) + }, defaultTimeout) + + It("should schedule deploy plan when an instance is created", func() { + // 1. admission controller should schedule 'deploy' plan and add cleanup finalizer for new instances + key = keyFrom(idle) + i := instanceWith(c, key) + + Expect(i.Spec.PlanExecution.PlanName).Should(Equal(deploy)) + Expect(i.Spec.PlanExecution.UID).ShouldNot(BeEmpty()) + Expect(i.HasCleanupFinalizer()).Should(BeTrue()) + }, defaultTimeout) + + It("should allow rescheduling of the deploy plan directly", func() { + i := instanceWith(c, key) + + // 1. restart currently running plan by resetting the UID + uid := i.Spec.PlanExecution.UID // save current plan UID for later checks + i.Spec.PlanExecution.UID = "" + + err := c.Update(context.TODO(), i) + Expect(err).NotTo(HaveOccurred()) + Expect(i.Spec.PlanExecution.PlanName).Should(Equal(deploy)) + Expect(i.Spec.PlanExecution.UID).ShouldNot(BeEmpty()) + Expect(i.Spec.PlanExecution.UID).NotTo(Equal(uid)) // same plan but new UID + }, defaultTimeout) + + It("should allow rescheduling of the deploy plan through parameter update", func() { + i := instanceWith(c, key) + + // 1. restart currently running plan by resetting the UID + uid := i.Spec.PlanExecution.UID // save current plan UID for later checks + i.Spec.Parameters = map[string]string{"foo": "2"} + + err := c.Update(context.TODO(), i) + Expect(err).NotTo(HaveOccurred()) + Expect(i.Spec.PlanExecution.PlanName).Should(Equal(deploy)) + Expect(i.Spec.PlanExecution.UID).ShouldNot(BeEmpty()) + Expect(i.Spec.PlanExecution.UID).NotTo(Equal(uid)) // same plan but new UID + Expect(i.Spec.PlanExecution.Status).Should(Equal(v1beta1.ExecutionNeverRun)) + }, defaultTimeout) + + It("should NOT allow scheduling of another plan while deploy is running", func() { + i := instanceWith(c, key) + + // 1. try to start the 'update' plan while another plan is in progress + i.Spec.PlanExecution.PlanName = update + + err := c.Update(context.TODO(), i) + Expect(err).To(HaveOccurred()) // boom + }, defaultTimeout) + + It("should NOT allow upgrading instance while a plan is running", func() { + // 1. create new OV first + newOv := ov.DeepCopy() + newOv.Name = fmt.Sprintf("%s-%s", ov.Name, "2.0") + err := c.Create(context.TODO(), newOv) + Expect(err).NotTo(HaveOccurred()) + + i := instanceWith(c, key) + + // 2. bump instance OV reference + i.Spec.OperatorVersion.Name = newOv.Name + err = c.Update(context.TODO(), i) + Expect(err).To(HaveOccurred()) // boom + }, defaultTimeout) + + It("should NOT allow updating parameters while another plan is running", func() { + i := instanceWith(c, key) + + // 1. try to instance parameters that would trigger a different plan while another plan is in progress + i.Spec.Parameters = map[string]string{"bar": "2"} + + err := c.Update(context.TODO(), i) + Expect(err).To(HaveOccurred()) // boom + }, defaultTimeout) + + It("should clear scheduled plan when it is completed", func() { + i := instanceWith(c, key) + + // 1. finish the 'deploy' plan by updating the status field + i.Spec.PlanExecution.Status = v1beta1.ExecutionComplete + err := c.Update(context.TODO(), i) + Expect(err).NotTo(HaveOccurred()) + + // 2. admission controller should reset the Spec.PlanExecution + i = instanceWith(c, key) + Expect(string(i.Spec.PlanExecution.Status)).Should(Equal("")) + Expect(string(i.Spec.PlanExecution.UID)).Should(Equal("")) + Expect(i.Spec.PlanExecution.PlanName).Should(Equal("")) + + }, defaultTimeout) + + It("cleanup plan can NOT be scheduled if the instance is not being deleted", func() { + i := instanceWith(c, key) + + // 1. finish the 'deploy' plan by updating the status field + i.Spec.PlanExecution.PlanName = v1beta1.CleanupPlanName + err := c.Update(context.TODO(), i) + Expect(err).To(HaveOccurred()) + + }, defaultTimeout) + + It("cleanup plan CAN be scheduled if the instance is being deleted", func() { + // 1. delete the instance + i := instanceWith(c, key) + err := c.Delete(context.TODO(), i) + Expect(err).NotTo(HaveOccurred()) + + // 2. schedule the 'cleanup' plan like the controller would + i = instanceWith(c, key) + uid := i.Spec.PlanExecution.UID + + i.Spec.PlanExecution.PlanName = v1beta1.CleanupPlanName + i.Spec.PlanExecution.UID = uuid.NewUUID() + err = c.Update(context.TODO(), i) + Expect(err).NotTo(HaveOccurred()) + + // 3. admission controller should reset the Spec.PlanExecution + i = instanceWith(c, key) + Expect(i.Spec.PlanExecution.PlanName).Should(Equal(v1beta1.CleanupPlanName)) + Expect(i.Spec.PlanExecution.UID).ShouldNot(Equal(uid)) + + }, defaultTimeout) + + It("another plan can NOT be scheduled while cleanup is running", func() { + i := instanceWith(c, key) + + // 1. finish the 'deploy' plan by updating the status field + i.Spec.PlanExecution.PlanName = v1beta1.DeployPlanName + err := c.Update(context.TODO(), i) + Expect(err).To(HaveOccurred()) + + }, defaultTimeout) + }) +}) + +// keyFrom method is a small helper method to retrieve an ObjectKey from the given object. Meant to be used within this test class +// only as it will fail the test should an error occur. +func keyFrom(obj runtime.Object) client.ObjectKey { + key, err := client.ObjectKeyFromObject(obj) + Expect(err).NotTo(HaveOccurred()) + return key +} + +// instanceWith method is a small helper method to retrieve an Instance with the give key. Meant to be used within this test class +// only as it will fail the test should an error occur. +func instanceWith(c client.Client, key client.ObjectKey) *v1beta1.Instance { + i := &v1beta1.Instance{} + err := c.Get(context.TODO(), key, i) + Expect(err).NotTo(HaveOccurred()) + return i +} diff --git a/pkg/webhook/instance_admission_test.go b/pkg/webhook/instance_admission_test.go index 3ff6633e2..d9ab33fe5 100644 --- a/pkg/webhook/instance_admission_test.go +++ b/pkg/webhook/instance_admission_test.go @@ -168,9 +168,7 @@ func TestValidateUpdate(t *testing.T) { old: scheduled, new: func() *v1beta1.Instance { i := scheduled.DeepCopy() - i.Status.PlanStatus = map[string]v1beta1.PlanStatus{ - deploy: {Name: deploy, UID: testUUID, Status: v1beta1.ExecutionComplete}, - } + i.Spec.PlanExecution.Status = v1beta1.ExecutionComplete return i }(), ov: ov,