Skip to content

Commit

Permalink
fix(crd-generator): Ensure deterministic ordering of CRD versions (#5784
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
baloo42 authored Mar 12, 2024
1 parent 2505659 commit 49d3647
Show file tree
Hide file tree
Showing 12 changed files with 605 additions and 13 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* Fix #5729: ensure that kind is set for generic resource lists
* Fix #3032: JUnit5 Kubernetes Extension works with Nested tests
* Fix #5759: Don't annotate KubeSchema and ValidationSchema classes
* Fix #5508: (crd-generator) Ensure deterministic ordering of CustomResourceDefinitionVersions

#### Improvements
* Fix #5701: Owner reference validity check regarding scope and namespace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import io.fabric8.crd.generator.v1.decorator.SetDeprecatedVersionDecorator;
import io.fabric8.crd.generator.v1.decorator.SetServedVersionDecorator;
import io.fabric8.crd.generator.v1.decorator.SetStorageVersionDecorator;
import io.fabric8.crd.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator;
import io.fabric8.crd.generator.v1.decorator.SortPrinterColumnsDecorator;
import io.sundr.model.TypeDef;

Expand Down Expand Up @@ -92,6 +93,7 @@ protected void addDecorators(CustomResourceInfo config, TypeDef def, Optional<St
resources.decorate(new SetStorageVersionDecorator(name, version, config.storage()));
resources.decorate(new SetDeprecatedVersionDecorator(name, version, config.deprecated(), config.deprecationWarning()));
resources.decorate(new EnsureSingleStorageVersionDecorator(name));
resources.decorate(new SortCustomResourceDefinitionVersionDecorator(name));
resources.decorate(new SortPrinterColumnsDecorator(name, version));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.v1.decorator;

import io.fabric8.crd.generator.decorator.Decorator;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionSpecFluent;
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion;
import io.fabric8.kubernetes.client.utils.KubernetesVersionPriority;

public class SortCustomResourceDefinitionVersionDecorator
extends CustomResourceDefinitionDecorator<CustomResourceDefinitionSpecFluent<?>> {
public SortCustomResourceDefinitionVersionDecorator(String name) {
super(name);
}

@Override
public void andThenVisit(CustomResourceDefinitionSpecFluent<?> spec, ObjectMeta resourceMeta) {
spec.withVersions(KubernetesVersionPriority.sortByPriority(spec.buildVersions(), CustomResourceDefinitionVersion::getName));
}

@Override
public Class<? extends Decorator>[] after() {
return new Class[] { EnsureSingleStorageVersionDecorator.class };
}

@Override
public String toString() {
return getClass().getName() + " [name:" + getName() + "]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import io.fabric8.crd.generator.v1beta1.decorator.SetDeprecatedVersionDecorator;
import io.fabric8.crd.generator.v1beta1.decorator.SetServedVersionDecorator;
import io.fabric8.crd.generator.v1beta1.decorator.SetStorageVersionDecorator;
import io.fabric8.crd.generator.v1beta1.decorator.SortCustomResourceDefinitionVersionDecorator;
import io.fabric8.crd.generator.v1beta1.decorator.SortPrinterColumnsDecorator;
import io.sundr.model.TypeDef;

Expand Down Expand Up @@ -93,6 +94,7 @@ protected void addDecorators(CustomResourceInfo config, TypeDef def,
resources.decorate(new SetStorageVersionDecorator(name, version, config.storage()));
resources.decorate(new SetDeprecatedVersionDecorator(name, version, config.deprecated(), config.deprecationWarning()));
resources.decorate(new EnsureSingleStorageVersionDecorator(name));
resources.decorate(new SortCustomResourceDefinitionVersionDecorator(name));
resources.decorate(new PromoteSingleVersionAttributesDecorator(name));
resources.decorate(new SortPrinterColumnsDecorator(name, version));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2015 Red Hat, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.fabric8.crd.generator.v1beta1.decorator;

import io.fabric8.crd.generator.decorator.Decorator;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinitionSpecFluent;
import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinitionVersion;
import io.fabric8.kubernetes.client.utils.KubernetesVersionPriority;

public class SortCustomResourceDefinitionVersionDecorator
extends CustomResourceDefinitionDecorator<CustomResourceDefinitionSpecFluent<?>> {
public SortCustomResourceDefinitionVersionDecorator(String name) {
super(name);
}

@Override
public void andThenVisit(CustomResourceDefinitionSpecFluent<?> spec, ObjectMeta resourceMeta) {
spec.withVersions(KubernetesVersionPriority.sortByPriority(spec.buildVersions(), CustomResourceDefinitionVersion::getName));
}

@Override
public Class<? extends Decorator>[] after() {
return new Class[] { EnsureSingleStorageVersionDecorator.class };
}

@Override
public String toString() {
return getClass().getName() + " [name:" + getName() + "]";
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.utils.Serialization;
import io.fabric8.kubernetes.model.Scope;
import org.junit.jupiter.api.RepeatedTest;
import org.junit.jupiter.api.Test;
import org.opentest4j.AssertionFailedError;
import org.slf4j.Logger;
Expand Down Expand Up @@ -482,19 +483,65 @@ void checkCRDGenerator() {
void checkGenerationIsDeterministic() throws Exception {
// generated CRD
final File outputDir = Files.createTempDirectory("crd-").toFile();
final CustomResourceInfo info = CustomResourceInfo.fromClass(Complex.class);
final CRDGenerationInfo crdInfo = newCRDGenerator().inOutputDir(outputDir).forCRDVersions(info.version())
.customResources(info).customResourceClasses(Complex.class).detailedGenerate();
final File crdFile = new File(crdInfo.getCRDInfos(info.crdName()).get(info.version()).getFilePath());
final String crdName = CustomResourceInfo.fromClass(Complex.class).crdName();
final CRDGenerationInfo crdInfo = newCRDGenerator()
.inOutputDir(outputDir)
.forCRDVersions("v1", "v1beta1")
.customResourceClasses(Complex.class)
.detailedGenerate();
final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
final File crdFileV1Beta1 = new File(crdInfo.getCRDInfos(crdName).get("v1beta1").getFilePath());

// expected CRD
final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName());
final URL crdResourceV1Beta1 = CRDGeneratorTest.class.getResource("/" + crdFileV1Beta1.getName());

assertNotNull(crdResource);
assertNotNull(crdResourceV1Beta1);
final File expectedCrdFile = new File(crdResource.getFile());
final File expectedCrdFileV1Beta1 = new File(crdResourceV1Beta1.getFile());
assertFileEquals(expectedCrdFile, crdFile);
assertFileEquals(expectedCrdFileV1Beta1, crdFileV1Beta1);

// only delete the generated files if the test is successful
assertTrue(crdFile.delete());
assertTrue(crdFileV1Beta1.delete());
assertTrue(outputDir.delete());
}

@RepeatedTest(value = 10)
void checkGenerationMultipleVersionsOfCRDsIsDeterministic() throws Exception {
// generated CRD
final File outputDir = Files.createTempDirectory("crd-").toFile();
final CustomResourceInfo infoV1 = CustomResourceInfo.fromClass(Multiple.class);
final CustomResourceInfo infoV2 = CustomResourceInfo.fromClass(io.fabric8.crd.example.multiple.v2.Multiple.class);
assertEquals(infoV1.crdName(), infoV2.crdName());
final String crdName = infoV1.crdName();

final CRDGenerationInfo crdInfo = newCRDGenerator()
.inOutputDir(outputDir)
.customResourceClasses(Multiple.class,
io.fabric8.crd.example.multiple.v2.Multiple.class)
.forCRDVersions("v1", "v1beta1")
.detailedGenerate();

final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath());
final File crdFileV1Beta1 = new File(crdInfo.getCRDInfos(crdName).get("v1beta1").getFilePath());

// expected CRD
final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName());
final URL crdResourceV1Beta1 = CRDGeneratorTest.class.getResource("/" + crdFileV1Beta1.getName());
assertNotNull(crdResource);
assertNotNull(crdResourceV1Beta1);

final File expectedCrdFile = new File(crdResource.getFile());
final File expectedCrdFileV1Beta1 = new File(crdResourceV1Beta1.getFile());
assertFileEquals(expectedCrdFile, crdFile);
assertFileEquals(expectedCrdFileV1Beta1, crdFileV1Beta1);

// only delete the generated files if the test is successful
assertTrue(crdFile.delete());
assertTrue(crdFileV1Beta1.delete());
assertTrue(outputDir.delete());
}

Expand Down
Loading

0 comments on commit 49d3647

Please sign in to comment.