Repository: operator-framework/operator-lifecycle-manager
Pull request #2136 (2021-05-04 14:07:18)

Fix CR validation bug on go 1.16
See https://github.com/kubernetes/kube-openapi/pull/230 for details

openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2136#" title="Author self-approved">ecordell</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)~~ [ecordell]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2136#" title="Author self-approved">ecordell</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/openshift/windows-machine-config-operator/blob/master/OWNERS)~~ [ecordell]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: @ecordell: The following test **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | c73b3e75f366a2fb00e3ac22dff58e94508a8a91 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-unit/1389583652450471936) | `/test unit`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=operator-framework&repo=operator-lifecycle-manager&pr=2136). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:ecordell).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2136#" title="Author self-approved">ecordell</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)~~ [ecordell]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
ecordell: /hold cancel

openshift-ci-robot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2136#pullrequestreview-651313775" title="LGTM">dinhxuanvu</a>*, *<a href="https://github.com/operator-framework/operator-lifecycle-manager/pull/2136#" title="Author self-approved">ecordell</a>*

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=operator-framework%2Foperator-lifecycle-manager).

The pull request process is described [here](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/operator-framework/operator-lifecycle-manager/blob/master/OWNERS)~~ [ecordell]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
openshift-ci[bot]: @ecordell: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | c73b3e75f366a2fb00e3ac22dff58e94508a8a91 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-unit/1389583652450471936) | `/test unit`
ci/prow/images | e729aa05346c8b90a6b43ce2d4c9d35b2eeb1f39 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-images/1389588911784726528) | `/test images`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=operator-framework&repo=operator-lifecycle-manager&pr=2136). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:ecordell).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @ecordell: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/unit | c73b3e75f366a2fb00e3ac22dff58e94508a8a91 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-unit/1389583652450471936) | `/test unit`
ci/prow/images | e729aa05346c8b90a6b43ce2d4c9d35b2eeb1f39 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-images/1389588911784726528) | `/test images`
ci/prow/verify | e729aa05346c8b90a6b43ce2d4c9d35b2eeb1f39 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-verify/1389588911843446784) | `/test verify`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=operator-framework&repo=operator-lifecycle-manager&pr=2136). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:ecordell).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->
openshift-ci[bot]: @ecordell: The following tests **failed**, say `/retest` to rerun all failed tests:

Test name | Commit | Details | Rerun command
--- | --- | --- | ---
ci/prow/images | e729aa05346c8b90a6b43ce2d4c9d35b2eeb1f39 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-images/1389588911784726528) | `/test images`
ci/prow/verify | e729aa05346c8b90a6b43ce2d4c9d35b2eeb1f39 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-verify/1389588911843446784) | `/test verify`
ci/prow/unit | e729aa05346c8b90a6b43ce2d4c9d35b2eeb1f39 | [link](https://prow.ci.openshift.org/view/gs/origin-ci-test/pr-logs/pull/operator-framework_operator-lifecycle-manager/2136/pull-ci-operator-framework-operator-lifecycle-manager-master-unit/1389588911805698048) | `/test unit`

[Full PR test history](https://prow.ci.openshift.org/pr-history?org=operator-framework&repo=operator-lifecycle-manager&pr=2136). [Your PR dashboard](https://prow.ci.openshift.org/pr?query=is:pr+state:open+author:ecordell).

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
<!-- test report -->

Patch:
diff --git a/go.mod b/go.mod
index 5e05b4daf..a42dea1c5 100644
--- a/go.mod
+++ b/go.mod
@@ -41,6 +41,7 @@ require (
 	google.golang.org/grpc v1.30.0
 	gopkg.in/yaml.v2 v2.4.0
 	helm.sh/helm/v3 v3.1.0-rc.1.0.20201215141456-e71d38b414eb
+	// can't update to 0.21 until https://github.com/kubernetes/apiserver/issues/65 is resolved
 	k8s.io/api v0.20.6
 	k8s.io/apiextensions-apiserver v0.20.6
 	k8s.io/apimachinery v0.20.6
@@ -50,7 +51,7 @@ require (
 	k8s.io/component-base v0.20.6
 	k8s.io/klog v1.0.0
 	k8s.io/kube-aggregator v0.20.4
-	k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd
+	k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7
 	k8s.io/utils v0.0.0-20210111153108-fddb29f9d009
 	rsc.io/letsencrypt v0.0.3 // indirect
 	sigs.k8s.io/controller-runtime v0.8.3
diff --git a/go.sum b/go.sum
index 96fc0dec8..45d40afcc 100644
--- a/go.sum
+++ b/go.sum
@@ -1439,8 +1439,9 @@ k8s.io/kube-aggregator v0.20.4 h1:j/SUwPy1eO+ud3XOUGmH18gISPyerqhXOoNRZDbv3fs=
 k8s.io/kube-aggregator v0.20.4/go.mod h1:0ixQ9De7KXyHteXizS6nVtrnKqGa4kiuxl9rEBsNccw=
 k8s.io/kube-openapi v0.0.0-20200121204235-bf4fb3bd569c/go.mod h1:GRQhZsXIAJ1xR0C9bd8UpWHZ5plfAS9fzPjJuQ6JL3E=
 k8s.io/kube-openapi v0.0.0-20200805222855-6aeccd4b50c6/go.mod h1:UuqjUnNftUyPE5H64/qeyjQoUZhGpeFDVdxjTeEVN2o=
-k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd h1:sOHNzJIkytDF6qadMNKhhDRpc6ODik8lVC6nOur7B2c=
 k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM=
+k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7 h1:vEx13qjvaZ4yfObSSXW7BrMc/KQBBT/Jyee8XtLf4x0=
+k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7/go.mod h1:wXW5VT87nVfh/iLV8FpR2uDvrFyomxbtb1KivDbvPTE=
 k8s.io/kubectl v0.18.0/go.mod h1:LOkWx9Z5DXMEg5KtOjHhRiC1fqJPLyCr3KtQgEolCkU=
 k8s.io/kubectl v0.20.0 h1:q6HH6jILYi2lkzFqBhs63M4bKLxYlM0HpFJ///MgARA=
 k8s.io/kubectl v0.20.0/go.mod h1:8x5GzQkgikz7M2eFGGuu6yOfrenwnw5g4RXOUgbjR1M=
diff --git a/pkg/controller/operators/catalog/operator.go b/pkg/controller/operators/catalog/operator.go
index a53ac49b9..27d123a6a 100644
--- a/pkg/controller/operators/catalog/operator.go
+++ b/pkg/controller/operators/catalog/operator.go
@@ -1600,7 +1600,7 @@ func validateExistingCRs(dynamicClient dynamic.Interface, gvr schema.GroupVersio
 		}
 		err = validation.ValidateCustomResource(field.NewPath(""), cr.UnstructuredContent(), validator).ToAggregate()
 		if err != nil {
-			return fmt.Errorf("error validating custom resource against new schema %#v: %s", newCRD.Spec.Validation, err)
+			return fmt.Errorf("error validating custom resource against new schema for %s %s/%s: %v", newCRD.Spec.Names.Kind, cr.GetNamespace(), cr.GetName(), err)
 		}
 	}
 	return nil
diff --git a/vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go b/vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go
index adc76f6c0..99b33d34f 100644
--- a/vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go
+++ b/vendor/k8s.io/kube-openapi/pkg/schemaconv/smd.go
@@ -27,6 +27,10 @@ import (
 	"sigs.k8s.io/structured-merge-diff/v4/schema"
 )
 
+const (
+	quantityResource = "io.k8s.apimachinery.pkg.api.resource.Quantity"
+)
+
 // ToSchema converts openapi definitions into a schema suitable for structured
 // merge (i.e. kubectl apply v2).
 func ToSchema(models proto.Models) (*schema.Schema, error) {
@@ -414,29 +418,33 @@ func ptr(s schema.Scalar) *schema.Scalar { return &s }
 
 func (c *convert) VisitPrimitive(p *proto.Primitive) {
 	a := c.top()
-	switch p.Type {
-	case proto.Integer:
-		a.Scalar = ptr(schema.Numeric)
-	case proto.Number:
-		a.Scalar = ptr(schema.Numeric)
-	case proto.String:
-		switch p.Format {
-		case "":
-			a.Scalar = ptr(schema.String)
-		case "byte":
-			// byte really means []byte and is encoded as a string.
-			a.Scalar = ptr(schema.String)
-		case "int-or-string":
-			a.Scalar = ptr(schema.Scalar("untyped"))
-		case "date-time":
-			a.Scalar = ptr(schema.Scalar("untyped"))
+	if c.currentName == quantityResource {
+		a.Scalar = ptr(schema.Scalar("untyped"))
+	} else {
+		switch p.Type {
+		case proto.Integer:
+			a.Scalar = ptr(schema.Numeric)
+		case proto.Number:
+			a.Scalar = ptr(schema.Numeric)
+		case proto.String:
+			switch p.Format {
+			case "":
+				a.Scalar = ptr(schema.String)
+			case "byte":
+				// byte really means []byte and is encoded as a string.
+				a.Scalar = ptr(schema.String)
+			case "int-or-string":
+				a.Scalar = ptr(schema.Scalar("untyped"))
+			case "date-time":
+				a.Scalar = ptr(schema.Scalar("untyped"))
+			default:
+				a.Scalar = ptr(schema.Scalar("untyped"))
+			}
+		case proto.Boolean:
+			a.Scalar = ptr(schema.Boolean)
 		default:
 			a.Scalar = ptr(schema.Scalar("untyped"))
 		}
-	case proto.Boolean:
-		a.Scalar = ptr(schema.Boolean)
-	default:
-		a.Scalar = ptr(schema.Scalar("untyped"))
 	}
 }
 
diff --git a/vendor/k8s.io/kube-openapi/pkg/util/util.go b/vendor/k8s.io/kube-openapi/pkg/util/util.go
index 1eb674eea..0be06989e 100644
--- a/vendor/k8s.io/kube-openapi/pkg/util/util.go
+++ b/vendor/k8s.io/kube-openapi/pkg/util/util.go
@@ -80,6 +80,9 @@ func ToRESTFriendlyName(name string) string {
 // Example for vendored Go type:
 //     Original full path:  k8s.io/kubernetes/vendor/k8s.io/api/core/v1.Pod
 //     Canonical name:      k8s.io/api/core/v1.Pod
+//
+//     Original full path:  vendor/k8s.io/api/core/v1.Pod
+//     Canonical name:      k8s.io/api/core/v1.Pod
 type OpenAPICanonicalTypeNamer interface {
 	OpenAPICanonicalTypeName() string
 }
@@ -100,6 +103,8 @@ func GetCanonicalTypeName(model interface{}) string {
 	path := t.PkgPath()
 	if strings.Contains(path, "/vendor/") {
 		path = path[strings.Index(path, "/vendor/")+len("/vendor/"):]
+	} else if strings.HasPrefix(path, "vendor/") {
+		path = strings.TrimPrefix(path, "vendor/")
 	}
 	return path + "." + t.Name()
 }
diff --git a/vendor/k8s.io/kube-openapi/pkg/validation/validate/type.go b/vendor/k8s.io/kube-openapi/pkg/validation/validate/type.go
index 5a9a80245..6469719dd 100644
--- a/vendor/k8s.io/kube-openapi/pkg/validation/validate/type.go
+++ b/vendor/k8s.io/kube-openapi/pkg/validation/validate/type.go
@@ -132,7 +132,7 @@ func (t *typeValidator) Applies(source interface{}, kind reflect.Kind) bool {
 func (t *typeValidator) Validate(data interface{}) *Result {
 	result := new(Result)
 	result.Inc()
-	if data == nil || reflect.DeepEqual(reflect.Zero(reflect.TypeOf(data)), reflect.ValueOf(data)) {
+	if data == nil {
 		// nil or zero value for the passed structure require Type: null
 		if len(t.Type) > 0 && !t.Type.Contains(nullType) && !t.Nullable { // TODO: if a property is not required it also passes this
 			return errorHelp.sErr(errors.InvalidType(t.Path, t.In, strings.Join(t.Type, ","), nullType))
diff --git a/vendor/modules.txt b/vendor/modules.txt
index f0586f34a..46b867737 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -1463,7 +1463,7 @@ k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1b
 k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces
 k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1
 k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1
-# k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd
+# k8s.io/kube-openapi v0.0.0-20210305001622-591a79e4bda7
 ## explicit
 k8s.io/kube-openapi/cmd/openapi-gen
 k8s.io/kube-openapi/cmd/openapi-gen/args


Test patch:
diff --git a/pkg/controller/operators/catalog/operator_test.go b/pkg/controller/operators/catalog/operator_test.go
index e263177ef..4c5693dd9 100644
--- a/pkg/controller/operators/catalog/operator_test.go
+++ b/pkg/controller/operators/catalog/operator_test.go
@@ -13,6 +13,8 @@ import (
 	"testing/quick"
 	"time"
 
+	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
+
 	"github.com/sirupsen/logrus"
 	"github.com/stretchr/testify/require"
 	"golang.org/x/time/rate"
@@ -30,6 +32,7 @@ import (
 	"k8s.io/apimachinery/pkg/runtime/schema"
 	"k8s.io/apimachinery/pkg/types"
 	utilclock "k8s.io/apimachinery/pkg/util/clock"
+	k8syaml "k8s.io/apimachinery/pkg/util/yaml"
 	utilyaml "k8s.io/apimachinery/pkg/util/yaml"
 	"k8s.io/apiserver/pkg/storage/names"
 	fakedynamic "k8s.io/client-go/dynamic/fake"
@@ -1284,6 +1287,70 @@ func TestCompetingCRDOwnersExist(t *testing.T) {
 	}
 }
 
+func TestValidateExistingCRs(t *testing.T) {
+	unstructuredForFile := func(file string) *unstructured.Unstructured {
+		data, err := ioutil.ReadFile(file)
+		require.NoError(t, err)
+		dec := k8syaml.NewYAMLOrJSONDecoder(strings.NewReader(string(data)), 30)
+		k8sFile := &unstructured.Unstructured{}
+		require.NoError(t, dec.Decode(k8sFile))
+		return k8sFile
+	}
+
+	unversionedCRDForV1beta1File := func(file string) *apiextensions.CustomResourceDefinition {
+		data, err := ioutil.ReadFile(file)
+		require.NoError(t, err)
+		dec := k8syaml.NewYAMLOrJSONDecoder(strings.NewReader(string(data)), 30)
+		k8sFile := &apiextensionsv1beta1.CustomResourceDefinition{}
+		require.NoError(t, dec.Decode(k8sFile))
+		convertedCRD := &apiextensions.CustomResourceDefinition{}
+		require.NoError(t, apiextensionsv1beta1.Convert_v1beta1_CustomResourceDefinition_To_apiextensions_CustomResourceDefinition(k8sFile, convertedCRD, nil))
+		return convertedCRD
+	}
+
+	tests := []struct {
+		name            string
+		existingObjects []runtime.Object
+		gvr             schema.GroupVersionResource
+		newCRD          *apiextensions.CustomResourceDefinition
+		want            error
+	}{
+		{
+			name: "label validation",
+			existingObjects: []runtime.Object{
+				unstructuredForFile("testdata/hivebug/cr.yaml"),
+			},
+			gvr: schema.GroupVersionResource{
+				Group:    "hive.openshift.io",
+				Version:  "v1",
+				Resource: "machinepools",
+			},
+			newCRD: unversionedCRDForV1beta1File("testdata/hivebug/crd.yaml"),
+		},
+		{
+			name: "fail validation",
+			existingObjects: []runtime.Object{
+				unstructuredForFile("testdata/hivebug/fail.yaml"),
+			},
+			gvr: schema.GroupVersionResource{
+				Group:    "hive.openshift.io",
+				Version:  "v1",
+				Resource: "machinepools",
+			},
+			newCRD: unversionedCRDForV1beta1File("testdata/hivebug/crd.yaml"),
+			want:   fmt.Errorf("error validating custom resource against new schema for MachinePool /test: [[].spec.clusterDeploymentRef: Invalid value: \"null\": spec.clusterDeploymentRef in body must be of type object: \"null\", [].spec.name: Required value, [].spec.platform: Required value]"),
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			client := fakedynamic.NewSimpleDynamicClientWithCustomListKinds(runtime.NewScheme(), map[schema.GroupVersionResource]string{
+				tt.gvr: "UnstructuredList",
+			}, tt.existingObjects...)
+			require.Equal(t, tt.want, validateExistingCRs(client, tt.gvr, tt.newCRD))
+		})
+	}
+}
+
 func fakeConfigMapData() map[string]string {
 	data := make(map[string]string)
 	yaml, err := yaml.Marshal([]apiextensionsv1beta1.CustomResourceDefinition{crd("fake-crd")})
diff --git a/pkg/controller/operators/catalog/testdata/hivebug/cr.yaml b/pkg/controller/operators/catalog/testdata/hivebug/cr.yaml
new file mode 100644
index 000000000..73bb3421a
--- /dev/null
+++ b/pkg/controller/operators/catalog/testdata/hivebug/cr.yaml
@@ -0,0 +1,18 @@
+apiVersion: hive.openshift.io/v1
+kind: MachinePool
+metadata:
+  name: test
+spec:
+  name: "test"
+  platform:
+    aws:
+      rootVolume:
+        iops: 1
+        size: 1
+        type: a
+      type: m4-large
+  clusterDeploymentRef:
+    name: osd-abc
+  labels:
+    node-role.kubernetes.io: infra
+    node-role.kubernetes.io/infra: ""
\ No newline at end of file
diff --git a/pkg/controller/operators/catalog/testdata/hivebug/crd.yaml b/pkg/controller/operators/catalog/testdata/hivebug/crd.yaml
new file mode 100644
index 000000000..de167defa
--- /dev/null
+++ b/pkg/controller/operators/catalog/testdata/hivebug/crd.yaml
@@ -0,0 +1,471 @@
+apiVersion: apiextensions.k8s.io/v1beta1
+kind: CustomResourceDefinition
+metadata:
+  creationTimestamp: null
+  name: machinepools.hive.openshift.io
+spec:
+  additionalPrinterColumns:
+    - JSONPath: .spec.name
+      name: PoolName
+      type: string
+    - JSONPath: .spec.clusterDeploymentRef.name
+      name: ClusterDeployment
+      type: string
+    - JSONPath: .spec.replicas
+      name: Replicas
+      type: integer
+  group: hive.openshift.io
+  names:
+    kind: MachinePool
+    listKind: MachinePoolList
+    plural: machinepools
+    singular: machinepool
+  scope: Namespaced
+  subresources:
+    scale:
+      specReplicasPath: .spec.replicas
+      statusReplicasPath: .status.replicas
+    status: {}
+  validation:
+    openAPIV3Schema:
+      description: MachinePool is the Schema for the machinepools API
+      properties:
+        apiVersion:
+          description: 'APIVersion defines the versioned schema of this representation
+            of an object. Servers should convert recognized schemas to the latest
+            internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources'
+          type: string
+        kind:
+          description: 'Kind is a string value representing the REST resource this
+            object represents. Servers may infer this from the endpoint the client
+            submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds'
+          type: string
+        metadata:
+          type: object
+        spec:
+          description: MachinePoolSpec defines the desired state of MachinePool
+          properties:
+            autoscaling:
+              description: Autoscaling is the details for auto-scaling the machine
+                pool. Replicas and autoscaling cannot be used together.
+              properties:
+                maxReplicas:
+                  description: MaxReplicas is the maximum number of replicas for the
+                    machine pool.
+                  format: int32
+                  type: integer
+                minReplicas:
+                  description: MinReplicas is the minimum number of replicas for the
+                    machine pool.
+                  format: int32
+                  type: integer
+              required:
+                - maxReplicas
+                - minReplicas
+              type: object
+            clusterDeploymentRef:
+              description: ClusterDeploymentRef references the cluster deployment
+                to which this machine pool belongs.
+              properties:
+                name:
+                  description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
+                    TODO: Add other useful fields. apiVersion, kind, uid?'
+                  type: string
+              type: object
+            labels:
+              additionalProperties:
+                type: string
+              description: Map of label string keys and values that will be applied
+                to the created MachineSet's MachineSpec. This list will overwrite
+                any modifications made to Node labels on an ongoing basis.
+              type: object
+            name:
+              description: Name is the name of the machine pool.
+              type: string
+            platform:
+              description: Platform is configuration for machine pool specific to
+                the platform.
+              properties:
+                aws:
+                  description: AWS is the configuration used when installing on AWS.
+                  properties:
+                    rootVolume:
+                      description: EC2RootVolume defines the storage for ec2 instance.
+                      properties:
+                        iops:
+                          description: IOPS defines the iops for the storage.
+                          type: integer
+                        size:
+                          description: Size defines the size of the storage.
+                          type: integer
+                        type:
+                          description: Type defines the type of the storage.
+                          type: string
+                      required:
+                        - iops
+                        - size
+                        - type
+                      type: object
+                    spotMarketOptions:
+                      description: SpotMarketOptions allows users to configure instances
+                        to be run using AWS Spot instances.
+                      properties:
+                        maxPrice:
+                          description: 'The maximum price the user is willing to pay
+                            for their instances Default: On-Demand price'
+                          type: string
+                      type: object
+                    subnets:
+                      description: Subnets is the list of subnets to which to attach
+                        the machines. There must be exactly one private subnet for
+                        each availability zone used. If public subnets are specified,
+                        there must be exactly one private and one public subnet specified
+                        for each availability zone.
+                      items:
+                        type: string
+                      type: array
+                    type:
+                      description: InstanceType defines the ec2 instance type. eg.
+                        m4-large
+                      type: string
+                    zones:
+                      description: Zones is list of availability zones that can be
+                        used.
+                      items:
+                        type: string
+                      type: array
+                  required:
+                    - rootVolume
+                    - type
+                  type: object
+                azure:
+                  description: Azure is the configuration used when installing on
+                    Azure.
+                  properties:
+                    osDisk:
+                      description: OSDisk defines the storage for instance.
+                      properties:
+                        diskSizeGB:
+                          description: DiskSizeGB defines the size of disk in GB.
+                          format: int32
+                          type: integer
+                      required:
+                        - diskSizeGB
+                      type: object
+                    type:
+                      description: InstanceType defines the azure instance type. eg.
+                        Standard_DS_V2
+                      type: string
+                    zones:
+                      description: Zones is list of availability zones that can be
+                        used. eg. ["1", "2", "3"]
+                      items:
+                        type: string
+                      type: array
+                  required:
+                    - osDisk
+                    - type
+                  type: object
+                gcp:
+                  description: GCP is the configuration used when installing on GCP.
+                  properties:
+                    osDisk:
+                      description: OSDisk defines the storage for instances.
+                      properties:
+                        diskSizeGB:
+                          description: DiskSizeGB defines the size of disk in GB.
+                            Defaulted internally to 128.
+                          format: int64
+                          maximum: 65536
+                          minimum: 16
+                          type: integer
+                        diskType:
+                          description: DiskType defines the type of disk. The valid
+                            values are pd-standard and pd-ssd. Defaulted internally
+                            to pd-ssd.
+                          enum:
+                            - pd-ssd
+                            - pd-standard
+                          type: string
+                        encryptionKey:
+                          description: EncryptionKey defines the KMS key to be used
+                            to encrypt the disk.
+                          properties:
+                            kmsKey:
+                              description: KMSKey is a reference to a KMS Key to use
+                                for the encryption.
+                              properties:
+                                keyRing:
+                                  description: KeyRing is the name of the KMS Key
+                                    Ring which the KMS Key belongs to.
+                                  type: string
+                                location:
+                                  description: Location is the GCP location in which
+                                    the Key Ring exists.
+                                  type: string
+                                name:
+                                  description: Name is the name of the customer managed
+                                    encryption key to be used for the disk encryption.
+                                  type: string
+                                projectID:
+                                  description: ProjectID is the ID of the Project
+                                    in which the KMS Key Ring exists. Defaults to
+                                    the VM ProjectID if not set.
+                                  type: string
+                              required:
+                                - keyRing
+                                - location
+                                - name
+                              type: object
+                            kmsKeyServiceAccount:
+                              description: KMSKeyServiceAccount is the service account
+                                being used for the encryption request for the given
+                                KMS key. If absent, the Compute Engine default service
+                                account is used. See https://cloud.google.com/compute/docs/access/service-accounts#compute_engine_service_account
+                                for details on the default service account.
+                              type: string
+                          type: object
+                      type: object
+                    type:
+                      description: InstanceType defines the GCP instance type. eg.
+                        n1-standard-4
+                      type: string
+                    zones:
+                      description: Zones is list of availability zones that can be
+                        used.
+                      items:
+                        type: string
+                      type: array
+                  required:
+                    - type
+                  type: object
+                openstack:
+                  description: OpenStack is the configuration used when installing
+                    on OpenStack.
+                  properties:
+                    flavor:
+                      description: Flavor defines the OpenStack Nova flavor. eg. m1.large
+                        The json key here differs from the installer which uses both
+                        "computeFlavor" and type "type" depending on which type you're
+                        looking at, and the resulting field on the MachineSet is "flavor".
+                        We are opting to stay consistent with the end result.
+                      type: string
+                    rootVolume:
+                      description: RootVolume defines the root volume for instances
+                        in the machine pool. The instances use ephemeral disks if
+                        not set.
+                      properties:
+                        size:
+                          description: Size defines the size of the volume in gibibytes
+                            (GiB). Required
+                          type: integer
+                        type:
+                          description: Type defines the type of the volume. Required
+                          type: string
+                      required:
+                        - size
+                        - type
+                      type: object
+                  required:
+                    - flavor
+                  type: object
+                ovirt:
+                  description: Ovirt is the configuration used when installing on
+                    oVirt.
+                  properties:
+                    cpu:
+                      description: CPU defines the VM CPU.
+                      properties:
+                        cores:
+                          description: Cores is the number of cores per socket. Total
+                            CPUs is (Sockets * Cores)
+                          format: int32
+                          type: integer
+                        sockets:
+                          description: Sockets is the number of sockets for a VM.
+                            Total CPUs is (Sockets * Cores)
+                          format: int32
+                          type: integer
+                      required:
+                        - cores
+                        - sockets
+                      type: object
+                    memoryMB:
+                      description: MemoryMB is the size of a VM's memory in MiBs.
+                      format: int32
+                      type: integer
+                    osDisk:
+                      description: OSDisk is the the root disk of the node.
+                      properties:
+                        sizeGB:
+                          description: SizeGB size of the bootable disk in GiB.
+                          format: int64
+                          type: integer
+                      required:
+                        - sizeGB
+                      type: object
+                    vmType:
+                      description: VMType defines the workload type of the VM.
+                      enum:
+                        - ""
+                        - desktop
+                        - server
+                        - high_performance
+                      type: string
+                  type: object
+                vsphere:
+                  description: VSphere is the configuration used when installing on
+                    vSphere
+                  properties:
+                    coresPerSocket:
+                      description: NumCoresPerSocket is the number of cores per socket
+                        in a vm. The number of vCPUs on the vm will be NumCPUs/NumCoresPerSocket.
+                      format: int32
+                      type: integer
+                    cpus:
+                      description: NumCPUs is the total number of virtual processor
+                        cores to assign a vm.
+                      format: int32
+                      type: integer
+                    memoryMB:
+                      description: Memory is the size of a VM's memory in MB.
+                      format: int64
+                      type: integer
+                    osDisk:
+                      description: OSDisk defines the storage for instance.
+                      properties:
+                        diskSizeGB:
+                          description: DiskSizeGB defines the size of disk in GB.
+                          format: int32
+                          type: integer
+                      required:
+                        - diskSizeGB
+                      type: object
+                  required:
+                    - coresPerSocket
+                    - cpus
+                    - memoryMB
+                    - osDisk
+                  type: object
+              type: object
+            replicas:
+              description: Replicas is the count of machines for this machine pool.
+                Replicas and autoscaling cannot be used together. Default is 1, if
+                autoscaling is not used.
+              format: int64
+              type: integer
+            taints:
+              description: List of taints that will be applied to the created MachineSet's
+                MachineSpec. This list will overwrite any modifications made to Node
+                taints on an ongoing basis.
+              items:
+                description: The node this Taint is attached to has the "effect" on
+                  any pod that does not tolerate the Taint.
+                properties:
+                  effect:
+                    description: Required. The effect of the taint on pods that do
+                      not tolerate the taint. Valid effects are NoSchedule, PreferNoSchedule
+                      and NoExecute.
+                    type: string
+                  key:
+                    description: Required. The taint key to be applied to a node.
+                    type: string
+                  timeAdded:
+                    description: TimeAdded represents the time at which the taint
+                      was added. It is only written for NoExecute taints.
+                    format: date-time
+                    type: string
+                  value:
+                    description: The taint value corresponding to the taint key.
+                    type: string
+                required:
+                  - effect
+                  - key
+                type: object
+              type: array
+          required:
+            - clusterDeploymentRef
+            - name
+            - platform
+          type: object
+        status:
+          description: MachinePoolStatus defines the observed state of MachinePool
+          properties:
+            conditions:
+              description: Conditions includes more detailed status for the cluster
+                deployment
+              items:
+                description: MachinePoolCondition contains details for the current
+                  condition of a machine pool
+                properties:
+                  lastProbeTime:
+                    description: LastProbeTime is the last time we probed the condition.
+                    format: date-time
+                    type: string
+                  lastTransitionTime:
+                    description: LastTransitionTime is the last time the condition
+                      transitioned from one status to another.
+                    format: date-time
+                    type: string
+                  message:
+                    description: Message is a human-readable message indicating details
+                      about last transition.
+                    type: string
+                  reason:
+                    description: Reason is a unique, one-word, CamelCase reason for
+                      the condition's last transition.
+                    type: string
+                  status:
+                    description: Status is the status of the condition.
+                    type: string
+                  type:
+                    description: Type is the type of the condition.
+                    type: string
+                required:
+                  - status
+                  - type
+                type: object
+              type: array
+            machineSets:
+              description: MachineSets is the status of the machine sets for the machine
+                pool on the remote cluster.
+              items:
+                description: MachineSetStatus is the status of a machineset in the
+                  remote cluster.
+                properties:
+                  maxReplicas:
+                    description: MaxReplicas is the maximum number of replicas for
+                      the machine set.
+                    format: int32
+                    type: integer
+                  minReplicas:
+                    description: MinReplicas is the minimum number of replicas for
+                      the machine set.
+                    format: int32
+                    type: integer
+                  name:
+                    description: Name is the name of the machine set.
+                    type: string
+                  replicas:
+                    description: Replicas is the current number of replicas for the
+                      machine set.
+                    format: int32
+                    type: integer
+                required:
+                  - maxReplicas
+                  - minReplicas
+                  - name
+                  - replicas
+                type: object
+              type: array
+            replicas:
+              description: Replicas is the current number of replicas for the machine
+                pool.
+              format: int32
+              type: integer
+          type: object
+  version: v1
+  versions:
+    - name: v1
+      served: true
+      storage: true
\ No newline at end of file
diff --git a/pkg/controller/operators/catalog/testdata/hivebug/fail.yaml b/pkg/controller/operators/catalog/testdata/hivebug/fail.yaml
new file mode 100644
index 000000000..98e2dd2f5
--- /dev/null
+++ b/pkg/controller/operators/catalog/testdata/hivebug/fail.yaml
@@ -0,0 +1,6 @@
+apiVersion: hive.openshift.io/v1
+kind: MachinePool
+metadata:
+  name: test
+spec:
+  clusterDeploymentRef: null
\ No newline at end of file