diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a31e67b39..cc42006579b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ #### Bugs * Fix #4369: Informers will retry with a backoff on list/watch failure as they did in 5.12 and prior. * Fix #4350: SchemaSwap annotation is now repeatable and is applied multiple times if classes are used more than once in the class hierarchy. +* Fix #4413: Stack overflow on multimaps (and other non-trivial generic classes) +* Fix #4487: Schema for multimaps is now generated correctly * Fix #3733: The authentication command from the .kube/config won't be discarded if no arguments are specified * Fix #4441: corrected patch base handling for the patch methods available from a Resource - resource(item).patch() will be evaluated as resource(latest).patch(item). Also undeprecated patch(item), which is consistent with leaving patch(context, item) undeprecated as well. For consistency with the other operations (such as edit), patch(item) will use the context item as the base when available, or the server side item when not. This means that patch(item) is only the same as resource(item).patch() when the patch(item) is called when the context item is missing or is the same as the latest. * Fix #4442: TokenRefreshInterceptor doesn't overwrite existing OAuth token with empty string @@ -25,6 +27,7 @@ * Fix #4243: Update Tekton triggers model to v0.20.2 * Fix #4383: bump snakeyaml from 1.30 to 1.31 * Fix #4347: Update Kubernetes Model to v1.25.0 +* Fix #4413: Update sundrio to 0.93.1 #### New Features * Fix #4398: add annotation @PreserveUnknownFields for marking generated field have `x-kubernetes-preserve-unknown-fields: true` defined diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/utils/Types.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/utils/Types.java index a4f9498f323..90909672be3 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/utils/Types.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/utils/Types.java @@ -25,6 +25,10 @@ import io.sundr.model.*; import io.sundr.model.functions.GetDefinition; import io.sundr.model.repo.DefinitionRepository; +import io.sundr.model.utils.TypeArguments; +import io.sundr.model.visitors.ApplyTypeParamMappingToMethod; +import io.sundr.model.visitors.ApplyTypeParamMappingToProperty; +import io.sundr.model.visitors.ApplyTypeParamMappingToTypeArguments; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +37,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; - public class Types { private Types() { throw new IllegalStateException("Utility class"); @@ -45,7 +48,8 @@ private Types() { public static final AdapterContext REFLECTION_CONTEXT = AdapterContext.create(DefinitionRepository.getRepository()); /** - * Make sure the generation context is reset so that types can be properly introspected if classes have changed since the last generation round. + * Make sure the generation context is reset so that types can be properly introspected if classes have changed since the last + * generation round. */ public static void resetGenerationContext() { DefinitionRepository.getRepository().clear(); @@ -62,10 +66,10 @@ public static TypeDef unshallow(TypeDef definition) { final List properties = Types.projectProperties(definition); // re-create TypeDef with all the needed information return new TypeDef(definition.getKind(), definition.getPackageName(), - definition.getName(), definition.getComments(), definition.getAnnotations(), classRefs, - definition.getImplementsList(), definition.getParameters(), properties, - definition.getConstructors(), definition.getMethods(), definition.getOuterTypeName(), - definition.getInnerTypes(), definition.getModifiers(), definition.getAttributes()); + definition.getName(), definition.getComments(), definition.getAnnotations(), classRefs, + definition.getImplementsList(), definition.getParameters(), properties, + definition.getConstructors(), definition.getMethods(), definition.getOuterTypeName(), + definition.getInnerTypes(), definition.getModifiers(), definition.getAttributes()); } public static TypeDef typeDefFrom(ClassRef classRef) { @@ -73,87 +77,30 @@ public static TypeDef typeDefFrom(ClassRef classRef) { } private static TypeDef projectDefinition(ClassRef ref) { - List arguments = ref.getArguments(); TypeDef definition = GetDefinition.of(ref); - if (arguments.isEmpty()) { + Map mappings = TypeArguments.getGenericArgumentsMappings(ref, definition); + if (mappings.isEmpty()) { return definition; + } else { + return new TypeDefBuilder(definition) + .accept(new ApplyTypeParamMappingToTypeArguments(mappings)) // existing type arguments must be handled before methods and properties + .accept(new ApplyTypeParamMappingToProperty(mappings), + new ApplyTypeParamMappingToMethod(mappings)) + .build(); } - - List parameters = definition.getParameters(); - Map mappings = new HashMap<>(); - for (int i = 0; i < arguments.size(); i++) { - String name = parameters.get(i).getName(); - TypeRef typeRef = arguments.get(i); - mappings.put(name, typeRef); - } - - return new TypeDefBuilder(definition) - .accept(mapClassRefArguments(mappings), mapGenericProperties(mappings)) - .build(); - } - - /** - * Map generic properties to known {@link TypeRef} based on the specified mappings. - * Example: Given a property {@code T size} and a map containing {@code T -> Integer} the final - * property will be: {@code Integer type}. - * @param mappings A map that maps class arguments names to types. - * @return a visitors that performs the actual mapping. - */ - private static TypedVisitor mapGenericProperties(Map mappings) { - return new TypedVisitor() { - @Override - public void visit(PropertyBuilder property) { - TypeRef typeRef = property.buildTypeRef(); - if (typeRef instanceof TypeParamRef) { - TypeParamRef typeParamRef = (TypeParamRef) typeRef; - String key = typeParamRef.getName(); - TypeRef paramRef = mappings.get(key); - if (paramRef != null) { - property.withTypeRef(paramRef); - } - } - } - }; - } - - /** - * Map arguments, of {@link ClassRef} instances to known {@link TypeRef} based on the specified mappings. - * Example: Given a class reference to {@code Shape} and a map containing {@code T -> Integer} - * the final reference will be: {@code Shape type}. - * @param mappings A map that maps class arguments names to types. - * @return a visitors that performs the actual mapping. - */ - private static TypedVisitor mapClassRefArguments(Map mappings) { - return new TypedVisitor() { - @Override - public void visit(ClassRefBuilder c) { - List arguments = new ArrayList<>(); - for (TypeRef arg : c.buildArguments()) { - TypeRef mappedRef = arg; - if (arg instanceof TypeParamRef) { - TypeParamRef typeParamRef = (TypeParamRef) arg; - TypeRef mapping = mappings.get(typeParamRef.getName()); - if (mapping != null) { - mappedRef = mapping; - } - } - arguments.add(mappedRef); - } - c.withArguments(arguments); - } - }; } private static Set projectSuperClasses(TypeDef definition) { List extendsList = definition.getExtendsList(); return extendsList.stream() - .flatMap(s -> Stream.concat(Stream.of(s), projectDefinition(s).getExtendsList().stream())) - .collect(Collectors.toSet()); + .flatMap(s -> Stream.concat(Stream.of(s), projectDefinition(s).getExtendsList().stream())) + .collect(Collectors.toSet()); } /** * All non-static properties (including inherited). - * @param typeDef The type. + * + * @param typeDef The type. * @return A list with all properties. */ private static List projectProperties(TypeDef typeDef) { @@ -172,23 +119,20 @@ private static List projectProperties(TypeDef typeDef) { return !p.isStatic(); }), typeDef.getExtendsList().stream() - .filter(e -> !e.getFullyQualifiedName().startsWith("java.")) - .flatMap(e -> projectProperties(projectDefinition(e)) - .stream() - .filter(p -> filterCustomResourceProperties(e).test(p))) - ) + .filter(e -> !e.getFullyQualifiedName().startsWith("java.")) + .flatMap(e -> projectProperties(projectDefinition(e)) + .stream() + .filter(p -> filterCustomResourceProperties(e).test(p)))) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } - private static Predicate filterCustomResourceProperties(ClassRef ref) { return p -> !p.isStatic() && - (!ref.getFullyQualifiedName().equals(CUSTOM_RESOURCE_NAME) || - (p.getName().equals("spec") || p.getName().equals("status"))); + (!ref.getFullyQualifiedName().equals(CUSTOM_RESOURCE_NAME) || + (p.getName().equals("spec") || p.getName().equals("status"))); } - public static void output(TypeDef def) { final StringBuilder builder = new StringBuilder(300); Types.describeType(def, "", new HashSet<>(), builder); @@ -258,7 +202,7 @@ public boolean isUnreliable() { public static SpecAndStatus resolveSpecAndStatusTypes(TypeDef definition) { Optional optionalCustomResourceRef = definition.getExtendsList().stream() - .filter(s -> s.getFullyQualifiedName().equals(CUSTOM_RESOURCE_NAME)).findFirst(); + .filter(s -> s.getFullyQualifiedName().equals(CUSTOM_RESOURCE_NAME)).findFirst(); if (optionalCustomResourceRef.isPresent()) { ClassRef customResourceRef = optionalCustomResourceRef.get(); List arguments = customResourceRef.getArguments(); @@ -309,14 +253,14 @@ public static boolean isNamespaced(TypeDef definition, Set visited) { */ public static Optional findStatusProperty(TypeDef typeDef) { return typeDef.getProperties().stream() - .filter(Types::isStatusProperty) - .findFirst(); + .filter(Types::isStatusProperty) + .findFirst(); } - /** * Returns true if the specified property corresponds to status. * A property qualifies as `status` if it is called `status`. + * * @param property the property we want to check * @return {@code true} if named {@code status}, {@code false} otherwise */ diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/example/map/ContainingMapsSpec.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/map/ContainingMapsSpec.java index e09a7ee8f02..78069f58891 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/example/map/ContainingMapsSpec.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/example/map/ContainingMapsSpec.java @@ -15,6 +15,7 @@ */ package io.fabric8.crd.example.map; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -32,4 +33,32 @@ public Map>> getTest2() { return test2; } + private MultiHashMap stringToIntMultiMap1; + private MultiMap stringToIntMultiMap2; + private SwappedParametersMap, String> stringToIntMultiMap3; + private RedundantParametersMap> stringToIntMultiMap4; + private RedundantParametersStringToIntMultiMap stringToIntMultiMap5; + private StringKeyedMultiHashMap stringToIntMultiMap6; + private IntValuedMultiMap stringToIntMultiMap7; + + static class MultiHashMap extends HashMap> { + } + + interface MultiMap extends Map> { + } + + interface SwappedParametersMap extends Map { + } + + interface RedundantParametersMap extends Map { + } + + interface RedundantParametersStringToIntMultiMap extends Map> { + } + + static class StringKeyedMultiHashMap extends MultiHashMap { + } + + interface IntValuedMultiMap extends MultiMap { + } } diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java index a1f96364356..2e8c98c9fa8 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java @@ -137,9 +137,9 @@ void shouldProperlyRecordNumberOfGeneratedCRDs() { assertEquals(0, generator.detailedGenerate().numberOfGeneratedCRDs()); final CRDGenerationInfo info = generator - .customResourceClasses(Simplest.class, Child.class, Joke.class, JokeRequest.class) - .forCRDVersions("v1", "v1beta1") - .withOutput(output).detailedGenerate(); + .customResourceClasses(Simplest.class, Child.class, Joke.class, JokeRequest.class) + .forCRDVersions("v1", "v1beta1") + .withOutput(output).detailedGenerate(); assertEquals(4 * 2, info.numberOfGeneratedCRDs()); final Map> details = info.getCRDDetailsPerNameAndVersion(); @@ -160,9 +160,9 @@ void shouldProperlyGenerateMultipleVersionsOfCRDs() { CRDGenerator generator = new CRDGenerator(); final String specVersion = "v1beta1"; final CRDGenerationInfo info = generator - .customResourceClasses(Multiple.class, io.fabric8.crd.example.multiple.v2.Multiple.class) - .forCRDVersions(specVersion) - .withOutput(output).detailedGenerate(); + .customResourceClasses(Multiple.class, io.fabric8.crd.example.multiple.v2.Multiple.class) + .forCRDVersions(specVersion) + .withOutput(output).detailedGenerate(); assertEquals(1, info.numberOfGeneratedCRDs()); final Map> details = info.getCRDDetailsPerNameAndVersion(); @@ -185,13 +185,15 @@ void shouldProperlyGenerateMultipleVersionsOfCRDs() { assertTrue(versions.stream().filter(v -> v.getName().equals("v1")).count() == 1); assertTrue(versions.stream().filter(v -> v.getName().equals("v2")).count() == 1); - - Class[] mustContainTraversedClasses = {Multiple.class, MultipleSpec.class, io.fabric8.crd.example.multiple.v2.Multiple.class, io.fabric8.crd.example.multiple.v2.MultipleSpec.class}; + Class[] mustContainTraversedClasses = { Multiple.class, MultipleSpec.class, + io.fabric8.crd.example.multiple.v2.Multiple.class, io.fabric8.crd.example.multiple.v2.MultipleSpec.class }; final Set dependentClassNames = infos.get(specVersion).getDependentClassNames(); - Arrays.stream(mustContainTraversedClasses).map(Class::getCanonicalName).forEach(c -> assertTrue(dependentClassNames.contains(c), "should contain " + c)); + Arrays.stream(mustContainTraversedClasses).map(Class::getCanonicalName) + .forEach(c -> assertTrue(dependentClassNames.contains(c), "should contain " + c)); } - @Test void notDefiningOutputShouldNotGenerateAnything() { + @Test + void notDefiningOutputShouldNotGenerateAnything() { CRDGenerator generator = new CRDGenerator(); assertEquals(0, generator.generate()); @@ -201,24 +203,25 @@ void shouldProperlyGenerateMultipleVersionsOfCRDs() { assertEquals(0, generator.generate()); } - @Test void generatingACycleShouldFail() { + @Test + void generatingACycleShouldFail() { final CRDGenerator generator = new CRDGenerator() - .customResourceClasses(Cyclic.class) - .forCRDVersions("v1", "v1beta1") - .withOutput(output); + .customResourceClasses(Cyclic.class) + .forCRDVersions("v1", "v1beta1") + .withOutput(output); assertThrows( - IllegalArgumentException.class, - () -> generator.detailedGenerate(), - "An IllegalArgument Exception hasn't been thrown when generating a CRD with cyclic references" - ); + IllegalArgumentException.class, + () -> generator.detailedGenerate(), + "An IllegalArgument Exception hasn't been thrown when generating a CRD with cyclic references"); } - @Test void notGeneratingACycleShouldSucceed() { + @Test + void notGeneratingACycleShouldSucceed() { final CRDGenerator generator = new CRDGenerator() - .customResourceClasses(NoCyclic.class) - .forCRDVersions("v1", "v1beta1") - .withOutput(output); + .customResourceClasses(NoCyclic.class) + .forCRDVersions("v1", "v1beta1") + .withOutput(output); CRDGenerationInfo info = generator.detailedGenerate(); assertEquals(2, info.numberOfGeneratedCRDs()); @@ -239,11 +242,12 @@ private void outputCRDIfFailed(Class> customResou throw e; } } + @Test void simplestCRDShouldWork() { outputCRDIfFailed(Simplest.class, (customResource) -> { final CustomResourceDefinitionVersion version = checkCRD(customResource, "Simplest", "simplests", - Scope.CLUSTER, SimplestSpec.class, SimplestStatus.class); + Scope.CLUSTER, SimplestSpec.class, SimplestStatus.class); assertNotNull(version.getSubresources()); }); @@ -253,10 +257,10 @@ void simplestCRDShouldWork() { void inheritedCRDShouldWork() { outputCRDIfFailed(Child.class, (customResource) -> { final CustomResourceDefinitionVersion version = checkCRD(customResource, "Child", "children", - Scope.NAMESPACED, ChildSpec.class, ChildStatus.class, BaseSpec.class, BaseStatus.class); + Scope.NAMESPACED, ChildSpec.class, ChildStatus.class, BaseSpec.class, BaseStatus.class); assertNotNull(version.getSubresources()); final Map specProps = version.getSchema().getOpenAPIV3Schema() - .getProperties().get("spec").getProperties(); + .getProperties().get("spec").getProperties(); assertEquals(4, specProps.size()); assertEquals("integer", specProps.get("baseInt").getType()); checkMapProp(specProps, "unsupported", "object"); @@ -269,38 +273,45 @@ void inheritedCRDShouldWork() { void mapPropertyShouldHaveCorrectValueType() { outputCRDIfFailed(ContainingMaps.class, (customResource) -> { final CustomResourceDefinitionVersion version = checkCRD(customResource, "ContainingMaps", "containingmaps", - Scope.CLUSTER, ContainingMapsSpec.class); + Scope.CLUSTER, ContainingMapsSpec.class); assertNotNull(version.getSchema()); final Map specProps = version.getSchema().getOpenAPIV3Schema() - .getProperties().get("spec").getProperties(); + .getProperties().get("spec").getProperties(); - assertEquals(2, specProps.size()); + assertEquals(9, specProps.size()); - checkMapProp(specProps, "test", "array"); - String arrayType = specProps.get("test").getAdditionalProperties().getSchema().getItems().getSchema().getType(); - assertEquals("string", arrayType); + JSONSchemaProps testSchema = checkMapProp(specProps, "test", "array"); + assertEquals("string", testSchema.getItems().getSchema().getType()); - checkMapProp(specProps, "test2", "object"); - JSONSchemaProps valueSchema = specProps.get("test2").getAdditionalProperties().getSchema().getAdditionalProperties().getSchema(); + JSONSchemaProps test2Schema = checkMapProp(specProps, "test2", "object"); + JSONSchemaProps valueSchema = test2Schema.getAdditionalProperties().getSchema(); String valueType = valueSchema.getType(); assertEquals("array", valueType); - assertEquals("boolean", valueSchema.getItems().getSchema().getType()); + + for (int i = 1; i <= 7; i++) { + String name = "stringToIntMultiMap" + i; + JSONSchemaProps schema = checkMapProp(specProps, name, "array"); + assertEquals("integer", schema.getItems().getSchema().getType(), name + "'s array item type should be integer"); + } }); } - private void checkMapProp(Map specProps, String name, String valueType) { + private JSONSchemaProps checkMapProp(Map specProps, String name, String valueType) { final JSONSchemaProps props = specProps.get(name); - assertEquals("object", props.getType()); - assertEquals(valueType, props.getAdditionalProperties().getSchema().getType()); + assertNotNull(props, name + " should be contained in spec"); + assertEquals("object", props.getType(), name + "'s type should be object"); + assertEquals(valueType, props.getAdditionalProperties().getSchema().getType(), + name + "'s value type should be " + valueType); + return props.getAdditionalProperties().getSchema(); } @Test void jokeCRDShouldWork() { outputCRDIfFailed(Joke.class, (customResource) -> { CustomResourceDefinitionVersion version = checkCRD(Joke.class, "Joke", "jokes", - Scope.NAMESPACED); + Scope.NAMESPACED); assertNull(version.getSubresources()); }); } @@ -309,10 +320,11 @@ void jokeCRDShouldWork() { void jokerequestCRDShouldWork() { outputCRDIfFailed(JokeRequest.class, (customResource) -> { final CustomResourceDefinitionSpec spec = checkSpec(customResource, Scope.NAMESPACED, - JokeRequestSpec.class, JokeRequestStatus.class, JokeRequestSpec.Category.class, JokeRequestSpec.ExcludedTopic.class, JokeRequestStatus.State.class); + JokeRequestSpec.class, JokeRequestStatus.class, JokeRequestSpec.Category.class, JokeRequestSpec.ExcludedTopic.class, + JokeRequestStatus.State.class); final CustomResourceDefinitionNames names = checkNames("JokeRequest", - "jokerequests", spec); + "jokerequests", spec); assertEquals(1, names.getShortNames().size()); assertTrue(names.getShortNames().contains("jr")); @@ -320,7 +332,7 @@ void jokerequestCRDShouldWork() { assertNotNull(version.getSubresources()); // printer columns should be ordered in the alphabetical order of their json path final List printerColumns = version - .getAdditionalPrinterColumns(); + .getAdditionalPrinterColumns(); assertEquals(2, printerColumns.size()); CustomResourceColumnDefinition columnDefinition = printerColumns.get(0); assertEquals("string", columnDefinition.getType()); @@ -352,7 +364,7 @@ void jokerequestCRDShouldWork() { void checkCRDGenerator() { outputCRDIfFailed(Basic.class, (customResource) -> { final CustomResourceDefinitionVersion version = checkCRD(customResource, "Basic", "basics", - Scope.NAMESPACED, BasicSpec.class, BasicStatus.class); + Scope.NAMESPACED, BasicSpec.class, BasicStatus.class); assertNotNull(version.getSubresources()); CustomResourceValidation schema = version.getSchema(); assertNotNull(schema); @@ -365,8 +377,9 @@ void checkCRDGenerator() { }); } - private CustomResourceDefinitionVersion checkCRD(Class> customResource, String kind, String plural, - Scope scope, Class... traversedClasses) { + private CustomResourceDefinitionVersion checkCRD(Class> customResource, String kind, + String plural, + Scope scope, Class... traversedClasses) { CustomResourceDefinitionSpec spec = checkSpec(customResource, scope, traversedClasses); checkNames(kind, plural, spec); @@ -388,7 +401,7 @@ private CustomResourceDefinitionNames checkNames(String kind, String plural, Cus } private CustomResourceDefinitionSpec checkSpec( - Class> customResource, Scope scope, Class... mustContainTraversedClasses) { + Class> customResource, Scope scope, Class... mustContainTraversedClasses) { CRDGenerator generator = new CRDGenerator(); // record info to be able to output it if the test fails @@ -397,9 +410,9 @@ private CustomResourceDefinitionSpec checkSpec( output.put(outputName, info); final String v1 = "v1"; final CRDGenerationInfo generatedInfo = generator.withOutput(output) - .forCRDVersions(v1) - .customResources(info) - .detailedGenerate(); + .forCRDVersions(v1) + .customResources(info) + .detailedGenerate(); assertEquals(1, generatedInfo.numberOfGeneratedCRDs()); final String crdName = info.crdName(); final Map crdInfos = generatedInfo.getCRDInfos(crdName); @@ -408,9 +421,10 @@ private CustomResourceDefinitionSpec checkSpec( assertEquals(crdName, crdInfo.getCrdName()); assertEquals(v1, crdInfo.getCrdSpecVersion()); assertTrue(crdInfo.getFilePath().endsWith(CRDGenerator.getOutputName(crdName, v1))); // test output uses the CRD name as URI - if(mustContainTraversedClasses != null && mustContainTraversedClasses.length > 0) { + if (mustContainTraversedClasses != null && mustContainTraversedClasses.length > 0) { final Set dependentClassNames = crdInfo.getDependentClassNames(); - Arrays.stream(mustContainTraversedClasses).map(Class::getCanonicalName).forEach(c -> assertTrue(dependentClassNames.contains(c), "should contain " + c)); + Arrays.stream(mustContainTraversedClasses).map(Class::getCanonicalName) + .forEach(c -> assertTrue(dependentClassNames.contains(c), "should contain " + c)); } CustomResourceDefinition definition = output.definition(outputName); @@ -431,6 +445,7 @@ private static class TestCRDOutput extends AbstractCRDOutput crdClass = CustomResourceDefinition.class; private final Map infos = new ConcurrentHashMap<>(); + @Override protected ByteArrayOutputStream createStreamFor(String crdName) throws IOException { return new ByteArrayOutputStream(); diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMaps.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMaps.java new file mode 100644 index 00000000000..a0e931a47c1 --- /dev/null +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMaps.java @@ -0,0 +1,26 @@ +/** + * 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.maps; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("map.fabric8.io") +@Version("v1") +public class ContainingMaps extends CustomResource { + +} diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMapsCRDTest.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMapsCRDTest.java new file mode 100644 index 00000000000..9b6c4b54bfd --- /dev/null +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMapsCRDTest.java @@ -0,0 +1,64 @@ +/** + * 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.maps; + +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.client.utils.Serialization; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + +class ContainingMapsCRDTest { + + @Test + void testCrd() { + CustomResourceDefinition d = Serialization.unmarshal(getClass().getClassLoader() + .getResourceAsStream("META-INF/fabric8/containingmaps.map.fabric8.io-v1.yml"), + CustomResourceDefinition.class); + assertNotNull(d); + + CustomResourceDefinitionVersion v1 = d.getSpec().getVersions().get(0); + assertNotNull(v1); + assertEquals("v1", v1.getName()); + Map spec = v1.getSchema().getOpenAPIV3Schema().getProperties().get("spec").getProperties(); + assertNotNull(spec); + + final Map specProps = v1.getSchema().getOpenAPIV3Schema() + .getProperties().get("spec").getProperties(); + + assertEquals(7, specProps.size()); + + for (int i = 1; i <= 7; i++) { + String name = "stringToIntMultiMap" + i; + JSONSchemaProps schema = checkMapProp(specProps, name, "array"); + assertEquals("integer", schema.getItems().getSchema().getType(), name + "'s array item type should be integer"); + } + } + + private JSONSchemaProps checkMapProp(Map specProps, String name, String valueType) { + final JSONSchemaProps props = specProps.get(name); + assertNotNull(props, name + " should be contained in spec"); + assertEquals("object", props.getType(), name + "'s type should be object"); + assertEquals(valueType, props.getAdditionalProperties().getSchema().getType(), + name + "'s value type should be " + valueType); + return props.getAdditionalProperties().getSchema(); + } +} diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMapsSpec.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMapsSpec.java new file mode 100644 index 00000000000..d1fb7afa7f8 --- /dev/null +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/maps/ContainingMapsSpec.java @@ -0,0 +1,51 @@ +/** + * 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.maps; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ContainingMapsSpec { + private MultiHashMap stringToIntMultiMap1; + private MultiMap stringToIntMultiMap2; + private SwappedParametersMap, String> stringToIntMultiMap3; + private RedundantParametersMap> stringToIntMultiMap4; + private RedundantParametersStringToIntMultiMap stringToIntMultiMap5; + private StringKeyedMultiHashMap stringToIntMultiMap6; + private IntValuedMultiMap stringToIntMultiMap7; + + static class MultiHashMap extends HashMap> { + } + + interface MultiMap extends Map> { + } + + interface SwappedParametersMap extends Map { + } + + interface RedundantParametersMap extends Map { + } + + interface RedundantParametersStringToIntMultiMap extends Map> { + } + + static class StringKeyedMultiHashMap extends MultiHashMap { + } + + interface IntValuedMultiMap extends MultiMap { + } +} diff --git a/pom.xml b/pom.xml index a88edc0b440..592ce1ffab9 100644 --- a/pom.xml +++ b/pom.xml @@ -80,7 +80,7 @@ UTF-8 - 0.93.0 + 0.93.1 3.12.12 3.12.1_1 1.15.0