Skip to content

Commit 49d3647

Browse files
authored
fix(crd-generator): Ensure deterministic ordering of CRD versions (#5784)
* Fix CRDGeneratorTest#checkGenerationIsDeterministic and extend to test v1beta1 CRDVersion * Add CRDGeneratorTest#checkGenerationMultipleVersionsOfCRDsIsDeterministic * Fix broken link in JavaDoc for KubernetesVersionFactory * Add sortByPriority for generic lists to KubernetesVersionPriority utility class * Add SortCustomResourceDefinitionVersionDecorator to CRDGenerator The implementation uses a method from KubernetesVersionPriority to sort the versions by priority. The version with the highest priority comes first. This ensures deterministic generation und should fix #5508. * Add changelog for #5508 * Add license headers to SortCustomResourceDefinitionDecorators * Add not-null check to KubernetesVersionPriority#sortByPriority * Add NPE tests to KubernetesVersionPriorityTest * Fix javadoc in KubernetesVersionPriority
1 parent 2505659 commit 49d3647

File tree

12 files changed

+605
-13
lines changed

12 files changed

+605
-13
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* Fix #5729: ensure that kind is set for generic resource lists
77
* Fix #3032: JUnit5 Kubernetes Extension works with Nested tests
88
* Fix #5759: Don't annotate KubeSchema and ValidationSchema classes
9+
* Fix #5508: (crd-generator) Ensure deterministic ordering of CustomResourceDefinitionVersions
910

1011
#### Improvements
1112
* Fix #5701: Owner reference validity check regarding scope and namespace

crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/CustomResourceHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import io.fabric8.crd.generator.v1.decorator.SetDeprecatedVersionDecorator;
3333
import io.fabric8.crd.generator.v1.decorator.SetServedVersionDecorator;
3434
import io.fabric8.crd.generator.v1.decorator.SetStorageVersionDecorator;
35+
import io.fabric8.crd.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator;
3536
import io.fabric8.crd.generator.v1.decorator.SortPrinterColumnsDecorator;
3637
import io.sundr.model.TypeDef;
3738

@@ -92,6 +93,7 @@ protected void addDecorators(CustomResourceInfo config, TypeDef def, Optional<St
9293
resources.decorate(new SetStorageVersionDecorator(name, version, config.storage()));
9394
resources.decorate(new SetDeprecatedVersionDecorator(name, version, config.deprecated(), config.deprecationWarning()));
9495
resources.decorate(new EnsureSingleStorageVersionDecorator(name));
96+
resources.decorate(new SortCustomResourceDefinitionVersionDecorator(name));
9597
resources.decorate(new SortPrinterColumnsDecorator(name, version));
9698
}
9799

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (C) 2015 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.fabric8.crd.generator.v1.decorator;
17+
18+
import io.fabric8.crd.generator.decorator.Decorator;
19+
import io.fabric8.kubernetes.api.model.ObjectMeta;
20+
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionSpecFluent;
21+
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion;
22+
import io.fabric8.kubernetes.client.utils.KubernetesVersionPriority;
23+
24+
public class SortCustomResourceDefinitionVersionDecorator
25+
extends CustomResourceDefinitionDecorator<CustomResourceDefinitionSpecFluent<?>> {
26+
public SortCustomResourceDefinitionVersionDecorator(String name) {
27+
super(name);
28+
}
29+
30+
@Override
31+
public void andThenVisit(CustomResourceDefinitionSpecFluent<?> spec, ObjectMeta resourceMeta) {
32+
spec.withVersions(KubernetesVersionPriority.sortByPriority(spec.buildVersions(), CustomResourceDefinitionVersion::getName));
33+
}
34+
35+
@Override
36+
public Class<? extends Decorator>[] after() {
37+
return new Class[] { EnsureSingleStorageVersionDecorator.class };
38+
}
39+
40+
@Override
41+
public String toString() {
42+
return getClass().getName() + " [name:" + getName() + "]";
43+
}
44+
45+
}

crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/CustomResourceHandler.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import io.fabric8.crd.generator.v1beta1.decorator.SetDeprecatedVersionDecorator;
3434
import io.fabric8.crd.generator.v1beta1.decorator.SetServedVersionDecorator;
3535
import io.fabric8.crd.generator.v1beta1.decorator.SetStorageVersionDecorator;
36+
import io.fabric8.crd.generator.v1beta1.decorator.SortCustomResourceDefinitionVersionDecorator;
3637
import io.fabric8.crd.generator.v1beta1.decorator.SortPrinterColumnsDecorator;
3738
import io.sundr.model.TypeDef;
3839

@@ -93,6 +94,7 @@ protected void addDecorators(CustomResourceInfo config, TypeDef def,
9394
resources.decorate(new SetStorageVersionDecorator(name, version, config.storage()));
9495
resources.decorate(new SetDeprecatedVersionDecorator(name, version, config.deprecated(), config.deprecationWarning()));
9596
resources.decorate(new EnsureSingleStorageVersionDecorator(name));
97+
resources.decorate(new SortCustomResourceDefinitionVersionDecorator(name));
9698
resources.decorate(new PromoteSingleVersionAttributesDecorator(name));
9799
resources.decorate(new SortPrinterColumnsDecorator(name, version));
98100
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright (C) 2015 Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.fabric8.crd.generator.v1beta1.decorator;
17+
18+
import io.fabric8.crd.generator.decorator.Decorator;
19+
import io.fabric8.kubernetes.api.model.ObjectMeta;
20+
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinitionSpecFluent;
21+
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinitionVersion;
22+
import io.fabric8.kubernetes.client.utils.KubernetesVersionPriority;
23+
24+
public class SortCustomResourceDefinitionVersionDecorator
25+
extends CustomResourceDefinitionDecorator<CustomResourceDefinitionSpecFluent<?>> {
26+
public SortCustomResourceDefinitionVersionDecorator(String name) {
27+
super(name);
28+
}
29+
30+
@Override
31+
public void andThenVisit(CustomResourceDefinitionSpecFluent<?> spec, ObjectMeta resourceMeta) {
32+
spec.withVersions(KubernetesVersionPriority.sortByPriority(spec.buildVersions(), CustomResourceDefinitionVersion::getName));
33+
}
34+
35+
@Override
36+
public Class<? extends Decorator>[] after() {
37+
return new Class[] { EnsureSingleStorageVersionDecorator.class };
38+
}
39+
40+
@Override
41+
public String toString() {
42+
return getClass().getName() + " [name:" + getName() + "]";
43+
}
44+
45+
}

crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java

Lines changed: 51 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import io.fabric8.kubernetes.client.CustomResource;
5252
import io.fabric8.kubernetes.client.utils.Serialization;
5353
import io.fabric8.kubernetes.model.Scope;
54+
import org.junit.jupiter.api.RepeatedTest;
5455
import org.junit.jupiter.api.Test;
5556
import org.opentest4j.AssertionFailedError;
5657
import org.slf4j.Logger;
@@ -482,19 +483,65 @@ void checkCRDGenerator() {
482483
void checkGenerationIsDeterministic() throws Exception {
483484
// generated CRD
484485
final File outputDir = Files.createTempDirectory("crd-").toFile();
485-
final CustomResourceInfo info = CustomResourceInfo.fromClass(Complex.class);
486-
final CRDGenerationInfo crdInfo = newCRDGenerator().inOutputDir(outputDir).forCRDVersions(info.version())
487-
.customResources(info).customResourceClasses(Complex.class).detailedGenerate();
488-
final File crdFile = new File(crdInfo.getCRDInfos(info.crdName()).get(info.version()).getFilePath());
486+
final String crdName = CustomResourceInfo.fromClass(Complex.class).crdName();
487+
final CRDGenerationInfo crdInfo = newCRDGenerator()
488+
.inOutputDir(outputDir)
489+
.forCRDVersions("v1", "v1beta1")
490+
.customResourceClasses(Complex.class)
491+
.detailedGenerate();
492+
final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
493+
final File crdFileV1Beta1 = new File(crdInfo.getCRDInfos(crdName).get("v1beta1").getFilePath());
489494

490495
// expected CRD
491496
final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName());
497+
final URL crdResourceV1Beta1 = CRDGeneratorTest.class.getResource("/" + crdFileV1Beta1.getName());
498+
492499
assertNotNull(crdResource);
500+
assertNotNull(crdResourceV1Beta1);
501+
final File expectedCrdFile = new File(crdResource.getFile());
502+
final File expectedCrdFileV1Beta1 = new File(crdResourceV1Beta1.getFile());
503+
assertFileEquals(expectedCrdFile, crdFile);
504+
assertFileEquals(expectedCrdFileV1Beta1, crdFileV1Beta1);
505+
506+
// only delete the generated files if the test is successful
507+
assertTrue(crdFile.delete());
508+
assertTrue(crdFileV1Beta1.delete());
509+
assertTrue(outputDir.delete());
510+
}
511+
512+
@RepeatedTest(value = 10)
513+
void checkGenerationMultipleVersionsOfCRDsIsDeterministic() throws Exception {
514+
// generated CRD
515+
final File outputDir = Files.createTempDirectory("crd-").toFile();
516+
final CustomResourceInfo infoV1 = CustomResourceInfo.fromClass(Multiple.class);
517+
final CustomResourceInfo infoV2 = CustomResourceInfo.fromClass(io.fabric8.crd.example.multiple.v2.Multiple.class);
518+
assertEquals(infoV1.crdName(), infoV2.crdName());
519+
final String crdName = infoV1.crdName();
520+
521+
final CRDGenerationInfo crdInfo = newCRDGenerator()
522+
.inOutputDir(outputDir)
523+
.customResourceClasses(Multiple.class,
524+
io.fabric8.crd.example.multiple.v2.Multiple.class)
525+
.forCRDVersions("v1", "v1beta1")
526+
.detailedGenerate();
527+
528+
final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
529+
final File crdFileV1Beta1 = new File(crdInfo.getCRDInfos(crdName).get("v1beta1").getFilePath());
530+
531+
// expected CRD
532+
final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName());
533+
final URL crdResourceV1Beta1 = CRDGeneratorTest.class.getResource("/" + crdFileV1Beta1.getName());
534+
assertNotNull(crdResource);
535+
assertNotNull(crdResourceV1Beta1);
536+
493537
final File expectedCrdFile = new File(crdResource.getFile());
538+
final File expectedCrdFileV1Beta1 = new File(crdResourceV1Beta1.getFile());
494539
assertFileEquals(expectedCrdFile, crdFile);
540+
assertFileEquals(expectedCrdFileV1Beta1, crdFileV1Beta1);
495541

496542
// only delete the generated files if the test is successful
497543
assertTrue(crdFile.delete());
544+
assertTrue(crdFileV1Beta1.delete());
498545
assertTrue(outputDir.delete());
499546
}
500547

0 commit comments

Comments
 (0)