Skip to content

Commit 9b3ea60

Browse files
committed
fix: support creating v1beta CRDs to avoid data loss during conversion to v1
1 parent 9f126de commit 9b3ea60

File tree

3 files changed

+97
-13
lines changed

3 files changed

+97
-13
lines changed

pkg/controller/operators/catalog/step.go

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,42 @@ func (b *builder) NewCRDStep(manifest string, client clientset.Interface, status
9898
return v1alpha1.StepStatusCreated, nil
9999
}
100100
case v1alpha1.StepStatusUnknown, v1alpha1.StepStatusNotPresent:
101-
crd, err := crdlib.Serialize(manifest)
101+
// data loss converting from v1beta1 -> v1 CRDs requires we create v1beta1 CRDs with the v1beta1 client
102+
// first establish APIVersion of provided CRD
103+
var crdi interface{}
104+
var createError error
105+
version, err := crdlib.Version(&manifest)
102106
if err != nil {
103107
return v1alpha1.StepStatusUnknown, err
104108
}
109+
logger.Debugf("crd version %s", version)
110+
if version == crdlib.V1Version {
111+
crd, err := crdlib.SerializeV1(manifest)
112+
if err != nil {
113+
return v1alpha1.StepStatusUnknown, err
114+
}
115+
_, createError = client.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
116+
crdi = crd
117+
} else if version == crdlib.V1Beta1Version {
118+
crd, err := crdlib.SerializeV1Beta1(manifest)
119+
if err != nil {
120+
return v1alpha1.StepStatusUnknown, err
121+
}
122+
logger.Debugf("creating CRD %v", crdi)
123+
_, createError = client.ApiextensionsV1beta1().CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
124+
crdi = crd
125+
} else {
126+
return v1alpha1.StepStatusUnknown, fmt.Errorf("unkown CRD version: %s", version)
127+
}
128+
129+
// convert to v1 type for remaining CRD reconciliation logic
130+
crd, err := crdlib.SerializeV1FromExisting(crdi)
131+
logger.Debugf("converted crd %v", crd)
132+
if err != nil {
133+
return v1alpha1.StepStatusUnknown, fmt.Errorf("unable to serialize into v1 CRD from existing manifest: %s", err)
134+
}
105135

106-
_, err = client.ApiextensionsV1().CustomResourceDefinitions().Create(context.TODO(), crd, metav1.CreateOptions{})
107-
if k8serrors.IsAlreadyExists(err) {
136+
if k8serrors.IsAlreadyExists(createError) {
108137
currentCRD, _ := client.ApiextensionsV1().CustomResourceDefinitions().Get(context.TODO(), crd.GetName(), metav1.GetOptions{})
109138
// Compare 2 CRDs to see if it needs to be updatetd
110139
if crdlib.NotEqual(currentCRD, crd) {
@@ -149,7 +178,7 @@ func (b *builder) NewCRDStep(manifest string, client clientset.Interface, status
149178
// If it already existed, mark the step as Present.
150179
// they were equal - mark CRD as present
151180
return v1alpha1.StepStatusPresent, nil
152-
} else if err != nil {
181+
} else if createError != nil {
153182
// Unexpected error creating the CRD.
154183
return v1alpha1.StepStatusUnknown, err
155184
}

pkg/lib/crd/serialize.go

Lines changed: 54 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import (
77

88
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
99
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
10+
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
1011
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
1112
"k8s.io/apimachinery/pkg/runtime"
1213
"k8s.io/apimachinery/pkg/util/yaml"
1314
)
1415

1516
const (
16-
Kind = "CustomResourceDefinition"
17-
APIVersion = "apiextensions.k8s.io/v1"
17+
Kind = "CustomResourceDefinition"
18+
Group = "apiextensions.k8s.io/"
1819
)
1920

2021
var (
@@ -26,17 +27,16 @@ func init() {
2627
install.Install(scheme)
2728
}
2829

29-
// Serialize takes in a CRD manifest and returns a v1 versioned CRD object.
30+
// SerializeV1 takes in a CRD manifest and returns a v1 versioned CRD object.
3031
// Compatible with v1beta1 or v1 CRD manifests.
31-
func Serialize(manifest string) (*apiextensionsv1.CustomResourceDefinition, error) {
32+
func SerializeV1(manifest string) (*apiextensionsv1.CustomResourceDefinition, error) {
3233
u := &unstructured.Unstructured{}
3334
reader := bytes.NewReader([]byte(manifest))
3435
decoder := yaml.NewYAMLOrJSONDecoder(reader, 30)
3536
if err := decoder.Decode(u); err != nil {
3637
return nil, fmt.Errorf("crd unmarshaling failed: %s", err)
3738
}
3839

39-
4040
// Step through unversioned type to support v1beta1 -> v1
4141
unversioned := &apiextensions.CustomResourceDefinition{}
4242
if err := scheme.Convert(u, unversioned, nil); err != nil {
@@ -51,18 +51,64 @@ func Serialize(manifest string) (*apiextensionsv1.CustomResourceDefinition, erro
5151
// set CRD type meta
5252
// for purposes of fake client for unit tests to pass
5353
crd.TypeMeta.Kind = Kind
54-
crd.TypeMeta.APIVersion = APIVersion
55-
54+
crd.TypeMeta.APIVersion = Group + V1Version
5655

5756
// for each version in the CRD, check and make sure there is a schema
5857
// if not a schema, give a default schema of props
5958
for i := range crd.Spec.Versions {
6059
if crd.Spec.Versions[i].Schema == nil {
6160
schema := &apiextensionsv1.JSONSchemaProps{Type: "object"}
62-
crd.Spec.Versions[i].Schema = &apiextensionsv1.CustomResourceValidation{OpenAPIV3Schema:schema}
61+
crd.Spec.Versions[i].Schema = &apiextensionsv1.CustomResourceValidation{OpenAPIV3Schema: schema}
6362
}
6463
}
6564

65+
return crd, nil
66+
}
67+
68+
// SerializeV1 takes in a CRD manifest and returns a v1 versioned CRD object.
69+
// Compatible with v1beta1 or v1 CRD manifests.
70+
func SerializeV1Beta1(manifest string) (*apiextensionsv1beta1.CustomResourceDefinition, error) {
71+
u := &unstructured.Unstructured{}
72+
reader := bytes.NewReader([]byte(manifest))
73+
decoder := yaml.NewYAMLOrJSONDecoder(reader, 30)
74+
if err := decoder.Decode(u); err != nil {
75+
return nil, fmt.Errorf("crd unmarshaling failed: %s", err)
76+
}
77+
78+
unversioned := &apiextensions.CustomResourceDefinition{}
79+
if err := scheme.Convert(u, unversioned, nil); err != nil {
80+
return nil, fmt.Errorf("failed to convert crd from unstructured to internal: %s\nto v1: %s", u, err)
81+
}
82+
83+
crd := &apiextensionsv1beta1.CustomResourceDefinition{}
84+
if err := scheme.Convert(unversioned, crd, nil); err != nil {
85+
return nil, fmt.Errorf("failed to convert crd from internal to v1: %s\nto v1: %s", u, err)
86+
}
87+
88+
// set CRD type meta
89+
// for purposes of fake client for unit tests to pass
90+
crd.TypeMeta.Kind = Kind
91+
crd.TypeMeta.APIVersion = Group + V1Beta1Version
6692

6793
return crd, nil
6894
}
95+
96+
// SerializeV1FromExisting takes in either a v1beta1 CRD or a v1 CRD type and returns a v1 CRD type.
97+
func SerializeV1FromExisting(crd interface{}) (*apiextensionsv1.CustomResourceDefinition, error) {
98+
if c, ok := crd.(*apiextensionsv1.CustomResourceDefinition); ok {
99+
return c, nil
100+
}
101+
if c, ok := crd.(*apiextensionsv1beta1.CustomResourceDefinition); ok {
102+
unversioned := &apiextensions.CustomResourceDefinition{}
103+
if err := scheme.Convert(c, unversioned, nil); err != nil {
104+
return nil, fmt.Errorf("failed to convert crd from unstructured to internal: %s\nto v1: %s", c.String(), err)
105+
}
106+
107+
c := &apiextensionsv1.CustomResourceDefinition{}
108+
if err := scheme.Convert(unversioned, crd, nil); err != nil {
109+
return nil, fmt.Errorf("failed to convert crd from internal to v1: %s\nto v1: %s", c.String(), err)
110+
}
111+
return c, nil
112+
}
113+
return nil, fmt.Errorf("unable to determine crd type")
114+
}

pkg/lib/crd/version.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,24 @@ var supportedCRDVersions = map[string]struct{}{
2323

2424
// Version takes a CRD manifest and determines whether it is v1 or v1beta1 type based on the APIVersion.
2525
func Version(manifest *string) (string, error) {
26+
if manifest == nil {
27+
return "", fmt.Errorf("empty CRD manifest")
28+
}
29+
2630
dec := yaml.NewYAMLOrJSONDecoder(strings.NewReader(*manifest), 10)
2731
unst := &unstructured.Unstructured{}
2832
if err := dec.Decode(unst); err != nil {
2933
return "", err
3034
}
3135

3236
v := unst.GetObjectKind().GroupVersionKind().Version
37+
// some e2e test fixtures do not provide an API version in their typemeta
38+
// assume these are v1beta types
39+
if v == "" {
40+
v = V1Beta1Version
41+
}
3342
if _, ok := supportedCRDVersions[v]; !ok {
34-
return "", fmt.Errorf("could not determine CRD version from manifest")
43+
return "", fmt.Errorf("CRD APIVersion from manifest not supported: %s", v)
3544
}
3645

3746
return v, nil

0 commit comments

Comments
 (0)