From b9891f6eeae90a1556e6df14753fac8c877c044c Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 24 Apr 2024 12:28:29 -0400 Subject: [PATCH 01/16] feature: add a v2 crd generator based on jackson closes: #5948 also removes v1beta1 from the new code --- crd-generator/api-v2/pom.xml | 81 +++ .../AbstractCustomResourceHandler.java | 72 +++ .../crdv2/generator/AbstractJsonSchema.java | 551 ++++++++++++++++ .../crdv2/generator/CRDGenerationInfo.java | 44 ++ .../fabric8/crdv2/generator/CRDGenerator.java | 257 ++++++++ .../io/fabric8/crdv2/generator/CRDInfo.java | 50 ++ .../crdv2/generator/CustomResourceInfo.java | 231 +++++++ .../crdv2/generator/InternalSchemaSwaps.java | 170 +++++ .../generator/KubernetesJSONSchemaProps.java | 45 ++ .../generator/KubernetesValidationRule.java | 33 + .../crdv2/generator/ResolvingContext.java | 127 ++++ .../io/fabric8/crdv2/generator/Resources.java | 130 ++++ .../crdv2/generator/decorator/Decorator.java | 66 ++ .../decorator/NamedResourceDecorator.java | 136 ++++ .../decorator/ResourceProvidingDecorator.java | 39 ++ .../crdv2/generator/utils/Generics.java | 115 ++++ .../crdv2/generator/utils/Metadata.java | 124 ++++ .../fabric8/crdv2/generator/utils/Types.java | 75 +++ .../generator/v1/CustomResourceHandler.java | 107 ++++ .../crdv2/generator/v1/JsonSchema.java | 109 ++++ .../AddAdditionPrinterColumnDecorator.java | 128 ++++ ...omResourceDefinitionResourceDecorator.java | 87 +++ ...tomResourceDefinitionVersionDecorator.java | 63 ++ .../AddLabelSelectorPathDecorator.java | 45 ++ ...tomResourceDefinitionVersionDecorator.java | 46 ++ .../AddSpecReplicasPathDecorator.java | 45 ++ .../AddStatusReplicasPathDecorator.java | 44 ++ .../AddStatusSubresourceDecorator.java | 43 ++ .../decorator/AddSubresourcesDecorator.java | 44 ++ .../CustomResourceDefinitionDecorator.java | 30 + ...tomResourceDefinitionVersionDecorator.java | 133 ++++ .../EnsureSingleStorageVersionDecorator.java | 61 ++ .../SetDeprecatedVersionDecorator.java | 45 ++ .../decorator/SetServedVersionDecorator.java | 39 ++ .../decorator/SetStorageVersionDecorator.java | 39 ++ ...tomResourceDefinitionVersionDecorator.java | 45 ++ .../SortPrinterColumnsDecorator.java | 81 +++ .../crdv2/example/annotated/Annotated.java | 22 + .../example/annotated/AnnotatedSpec.java | 183 ++++++ .../io/fabric8/crdv2/example/basic/Basic.java | 27 + .../crdv2/example/basic/BasicSpec.java | 63 ++ .../crdv2/example/basic/BasicStatus.java | 28 + .../crdv2/example/complex/Complex.java | 28 + .../crdv2/example/complex/ComplexSpec.java | 79 +++ .../crdv2/example/complex/ComplexStatus.java | 61 ++ .../example/complex/ServiceConfiguration.java | 53 ++ .../complex/StatefulSetConfiguration.java | 52 ++ .../crdv2/example/complex/k8s/ObjectMeta.java | 232 +++++++ .../example/complex/k8s/ServiceSpec.java | 286 +++++++++ .../example/complex/k8s/StatefulSetSpec.java | 126 ++++ .../fabric8/crdv2/example/cyclic/Cyclic.java | 27 + .../crdv2/example/cyclic/CyclicList.java | 27 + .../crdv2/example/cyclic/CyclicListSpec.java | 22 + .../crdv2/example/cyclic/CyclicSpec.java | 20 + .../crdv2/example/cyclic/CyclicStatus.java | 23 + .../io/fabric8/crdv2/example/cyclic/Ref.java | 22 + .../fabric8/crdv2/example/cyclic/RefList.java | 24 + .../deprecated/v1/DeprecationExample.java | 25 + .../deprecated/v1/DeprecationExampleSpec.java | 24 + .../v1beta1/DeprecationExample.java | 25 + .../v1beta1/DeprecationExampleSpec.java | 24 + .../deprecated/v2/DeprecationExample.java | 25 + .../deprecated/v2/DeprecationExampleSpec.java | 24 + .../CollectionCyclicSchemaSwap.java | 40 ++ .../example/extraction/CyclicSchemaSwap.java | 40 ++ .../extraction/DeeplyNestedSchemaSwaps.java | 49 ++ .../crdv2/example/extraction/Extraction.java | 24 + .../example/extraction/ExtractionSpec.java | 29 + .../fabric8/crdv2/example/extraction/Foo.java | 29 + .../example/extraction/FooExtractor.java | 27 + .../extraction/IncorrectExtraction.java | 24 + .../extraction/IncorrectExtraction2.java | 25 + .../extraction/MultipleSchemaSwaps.java | 26 + .../example/extraction/NestedSchemaSwap.java | 37 ++ .../example/extraction/SchemaSwapSpec.java | 35 ++ .../fabric8/crdv2/example/inherited/Base.java | 25 + .../crdv2/example/inherited/BaseSpec.java | 28 + .../crdv2/example/inherited/BaseStatus.java | 23 + .../crdv2/example/inherited/Child.java | 26 + .../crdv2/example/inherited/ChildSpec.java | 24 + .../crdv2/example/inherited/ChildStatus.java | 23 + .../io/fabric8/crdv2/example/joke/Joke.java | 82 +++ .../crdv2/example/joke/JokeRequest.java | 29 + .../crdv2/example/joke/JokeRequestSpec.java | 73 +++ .../crdv2/example/joke/JokeRequestStatus.java | 67 ++ .../crdv2/example/json/ContainingJson.java | 26 + .../example/json/ContainingJsonSpec.java | 40 ++ .../io/fabric8/crdv2/example/json/Foo.java | 38 ++ .../example/k8svalidation/K8sValidation.java | 35 ++ .../k8svalidation/K8sValidationSpec.java | 123 ++++ .../k8svalidation/K8sValidationStatus.java | 23 + .../crdv2/example/map/ContainingMaps.java | 34 + .../crdv2/example/map/ContainingMapsSpec.java | 64 ++ .../crdv2/example/multiple/v1/Multiple.java | 25 + .../example/multiple/v1/MultipleSpec.java | 24 + .../crdv2/example/multiple/v2/Multiple.java | 25 + .../example/multiple/v2/MultipleSpec.java | 24 + .../crdv2/example/nocyclic/NoCyclic.java | 27 + .../crdv2/example/nocyclic/NoCyclicSpec.java | 21 + .../example/nocyclic/NoCyclicStatus.java | 21 + .../fabric8/crdv2/example/nocyclic/Ref.java | 27 + .../fabric8/crdv2/example/person/Address.java | 29 + .../crdv2/example/person/AddressList.java | 21 + .../fabric8/crdv2/example/person/Person.java | 35 ++ .../crdv2/example/simplest/Simplest.java | 26 + .../crdv2/example/simplest/SimplestSpec.java | 20 + .../example/simplest/SimplestStatus.java | 20 + .../example/webserver/WebServerSpec.java | 28 + .../example/webserver/WebServerStatus.java | 27 + .../example/webserver/WebServerWithSpec.java | 28 + .../WebServerWithStatusProperty.java | 31 + .../generator/CRDGeneratorAssertions.java | 138 ++++ .../generator/CRDGeneratorExamplesTest.java | 62 ++ .../crdv2/generator/CRDGeneratorTest.java | 590 ++++++++++++++++++ .../generator/CustomResourceInfoTest.java | 92 +++ .../ParallelCRDGeneratorExamplesTest.java | 22 + .../generator/ParallelCRDGeneratorTest.java | 23 + .../crdv2/generator/ResourcesTest.java | 56 ++ .../crdv2/generator/utils/TypesTest.java | 33 + .../crdv2/generator/v1/JsonSchemaTest.java | 576 +++++++++++++++++ .../generator/v1/SpecReplicasPathTest.java | 46 ++ .../resources/complexkinds.example.com-v1.yml | 215 +++++++ .../complexkinds.example.com-v1beta1.yml | 215 +++++++ .../k8svalidations.samples.fabric8.io-v1.yml | 161 +++++ ...validations.samples.fabric8.io-v1beta1.yml | 161 +++++ .../multiples.sample.fabric8.io-v1.yml | 57 ++ .../multiples.sample.fabric8.io-v1beta1.yml | 57 ++ .../test/resources/simplelogger.properties | 43 ++ .../crd/generator/CRDGeneratorAssertions.java | 1 + crd-generator/pom.xml | 14 +- .../kubernetes/client/CustomResource.java | 10 +- .../client/utils/KubernetesSerialization.java | 2 +- .../kubernetes/api/model/Duration.java | 16 +- pom.xml | 5 + 134 files changed, 9334 insertions(+), 20 deletions(-) create mode 100644 crd-generator/api-v2/pom.xml create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesValidationRule.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Types.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java create mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/Annotated.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/Basic.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/Complex.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ServiceConfiguration.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/StatefulSetConfiguration.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ObjectMeta.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ServiceSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/StatefulSetSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Cyclic.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicList.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicListSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Ref.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/RefList.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExample.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExampleSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExample.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExampleSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExample.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExampleSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CollectionCyclicSchemaSwap.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CyclicSchemaSwap.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/DeeplyNestedSchemaSwaps.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Extraction.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/ExtractionSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Foo.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/FooExtractor.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction2.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/MultipleSchemaSwaps.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/NestedSchemaSwap.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/SchemaSwapSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Base.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Child.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/Joke.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJson.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJsonSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/Foo.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidation.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMaps.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMapsSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/Multiple.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/MultipleSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/Multiple.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/MultipleSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclic.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/Ref.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Address.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/AddressList.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Person.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/Simplest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerStatus.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithSpec.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithStatusProperty.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorExamplesTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CustomResourceInfoTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorExamplesTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java create mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java create mode 100644 crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml create mode 100644 crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml create mode 100644 crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml create mode 100644 crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml create mode 100644 crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml create mode 100644 crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml create mode 100644 crd-generator/api-v2/src/test/resources/simplelogger.properties diff --git a/crd-generator/api-v2/pom.xml b/crd-generator/api-v2/pom.xml new file mode 100644 index 00000000000..3f951ca5897 --- /dev/null +++ b/crd-generator/api-v2/pom.xml @@ -0,0 +1,81 @@ + + + + + crd-generator-parent + io.fabric8 + 6.13-SNAPSHOT + + 4.0.0 + + crd-generator-api-v2 + Fabric8 :: CRD generator :: API V2 + + + + io.fabric8 + kubernetes-client-api + compile + + + + com.fasterxml.jackson.module + jackson-module-jsonSchema + + + + io.fabric8 + generator-annotations + + + + io.fabric8 + kubernetes-model-common + + + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-engine + test + + + org.slf4j + slf4j-simple + test + + + javax.validation + validation-api + test + + + org.projectlombok + lombok + test + + + diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java new file mode 100644 index 00000000000..7d97d9fbf65 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -0,0 +1,72 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.crd.generator.annotation.PrinterColumn; +import io.fabric8.crdv2.generator.AbstractJsonSchema.AnnotationMetadata; +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.client.utils.Utils; + +import java.util.Map; + +/** + * This class encapsulates the common behavior between v1beta1 and v1 CRD generation logic. The + * intent is that each CRD spec version is implemented as a sub-class of this one. + */ +public abstract class AbstractCustomResourceHandler { + + protected final Resources resources; + + protected AbstractCustomResourceHandler(Resources resources) { + this.resources = resources; + } + + public abstract void handle(CustomResourceInfo config); + + protected void handlePrinterColumns(String name, String version, Map additionalPrinterColumns) { + additionalPrinterColumns.forEach((path, property) -> { + PrinterColumn printerColumn = ((PrinterColumn)property.annotation); + String column = printerColumn.name(); + if (Utils.isNullOrEmpty(column)) { + column = path.substring(path.lastIndexOf(".")); + } + String format = printerColumn.format(); + int priority = printerColumn.priority(); + + // TODO: add description to the annotation? The previous logic considered the comments, which are not available here + String description = property.description; + + resources.decorate( + getPrinterColumnDecorator(name, version, path, property.type, column, description, format, priority)); + }); + } + + /** + * Provides the decorator implementation associated with the CRD generation version. + * + * @param name the resource name + * @param version the associated version + * @param path the path from which the printer column is extracted + * @param type the data type of the printer column + * @param column the name of the column + * @param description the description of the column + * @param format the format of the printer column + * @return the concrete decorator implementing the addition of a printer column to the currently built CRD + */ + protected abstract Decorator getPrinterColumnDecorator(String name, String version, String path, + String type, String column, String description, String format, int priority); + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java new file mode 100644 index 00000000000..d9a7c2bf524 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -0,0 +1,551 @@ +/* + * 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.crdv2.generator; + +import com.fasterxml.jackson.annotation.JsonFormat.Value; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.AnnotationIntrospector; +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.module.jsonSchema.JsonSchema; +import com.fasterxml.jackson.module.jsonSchema.types.ArraySchema.Items; +import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; +import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema.SchemaAdditionalProperties; +import com.fasterxml.jackson.module.jsonSchema.types.ReferenceSchema; +import com.fasterxml.jackson.module.jsonSchema.types.StringSchema; +import com.fasterxml.jackson.module.jsonSchema.types.ValueTypeSchema; +import io.fabric8.crd.generator.annotation.PreserveUnknownFields; +import io.fabric8.crd.generator.annotation.PrinterColumn; +import io.fabric8.crd.generator.annotation.SchemaFrom; +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.crdv2.generator.InternalSchemaSwaps.SwapResult; +import io.fabric8.crdv2.generator.ResolvingContext.GeneratorObjectSchema; +import io.fabric8.generator.annotation.Default; +import io.fabric8.generator.annotation.Max; +import io.fabric8.generator.annotation.Min; +import io.fabric8.generator.annotation.Nullable; +import io.fabric8.generator.annotation.Pattern; +import io.fabric8.generator.annotation.Required; +import io.fabric8.generator.annotation.ValidationRule; +import io.fabric8.generator.annotation.ValidationRules; +import io.fabric8.kubernetes.api.model.GenericKubernetesResource; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.api.model.runtime.RawExtension; +import io.fabric8.kubernetes.client.utils.Utils; +import io.fabric8.kubernetes.model.annotation.LabelSelector; +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import io.fabric8.kubernetes.model.annotation.StatusReplicas; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static io.fabric8.crdv2.generator.CRDGenerator.YAML_MAPPER; +import static java.util.Optional.ofNullable; + +/** + * Encapsulates the common logic supporting OpenAPI schema generation for CRD generation. + * + * @param the concrete type of the generated JSON Schema + * @param the concrete type of the validation rule + */ +public abstract class AbstractJsonSchema { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJsonSchema.class); + + private ResolvingContext resolvingContext; + private T root; + + public static class AnnotationMetadata { + public final Annotation annotation; + public final String description; + public final String type; + + public AnnotationMetadata(Annotation annotation, String description, String type) { + this.annotation = annotation; + this.description = description; + this.type = type; + } + } + + private Map, LinkedHashMap> pathMetadata = new HashMap<>(); + + public AbstractJsonSchema(ResolvingContext resolvingContext, Class def) { + this.resolvingContext = resolvingContext; + // TODO: could make this configurable, and could stop looking for single valued ones - or warn + Stream.of(SpecReplicas.class, StatusReplicas.class, LabelSelector.class, PrinterColumn.class) + .forEach(clazz -> pathMetadata.put(clazz, new LinkedHashMap<>())); + + this.root = resolveRoot(def); + } + + public T getSchema() { + return root; + } + + public Optional getSinglePath(Class clazz) { + return ofNullable(pathMetadata.get(clazz)).flatMap(m -> m.keySet().stream().findFirst()); + } + + public Map getAllPaths(Class clazz) { + return ofNullable(pathMetadata.get(clazz)).orElse(new LinkedHashMap<>()); + } + + /** + * Creates the JSON schema for the class. This is template method where + * sub-classes are supposed to provide specific implementations of abstract methods. + * + * @param definition The definition. + * @param ignore a potentially empty list of property names to ignore while generating the schema + * @return The schema. + */ + private T resolveRoot(Class definition) { + InternalSchemaSwaps schemaSwaps = new InternalSchemaSwaps(); + JsonSchema schema = toJsonSchema(definition); + if (schema instanceof GeneratorObjectSchema) { + return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata"); + } + return resolveProperty(new LinkedHashMap<>(), schemaSwaps, null, resolvingContext.serializationConfig.constructType(definition), schema); + } + + /** + * Walks up the class hierarchy to consume the repeating annotation + */ + private static void consumeRepeatingAnnotation(Class beanClass, Class annotation, Consumer consumer) { + while (beanClass != null && beanClass != Object.class) { + Stream.of(beanClass.getAnnotationsByType(annotation)).forEach(consumer); + beanClass = beanClass.getSuperclass(); + } + } + + void collectValidationRules(BeanProperty beanProperty, List validationRules) { + // TODO: the old logic allowed for picking up the annotation from both the getter and the field + // this requires a messy hack by convention because there doesn't seem to be a way to all annotations + // nor does jackson provide the field + if (beanProperty.getMember() instanceof AnnotatedMethod) { + // field first + Method m = ((AnnotatedMethod)beanProperty.getMember()).getMember(); + String name = m.getName(); + if (name.startsWith("get") || name.startsWith("set")) { + name = name.substring(3); + } else if (name.startsWith("is")) { + name = name.substring(2); + } + if (name.length() > 0) { + name = Character.toLowerCase(name.charAt(0)) + name.substring(1); + } + try { + Field f = beanProperty.getMember().getDeclaringClass().getDeclaredField(name); + ofNullable(f.getAnnotation(ValidationRule.class)).map(this::from) + .ifPresent(validationRules::add); + ofNullable(f.getAnnotation(ValidationRules.class)) + .ifPresent(ann -> Stream.of(ann.value()).map(this::from).forEach(validationRules::add)); + } catch (NoSuchFieldException | SecurityException e) { + } + // then method + Stream.of(m.getAnnotationsByType(ValidationRule.class)).map(this::from).forEach(validationRules::add); + return; + } + + // fall back to standard logic + ofNullable(beanProperty.getAnnotation(ValidationRule.class)).map(this::from) + .ifPresent(validationRules::add); + ofNullable(beanProperty.getAnnotation(ValidationRules.class)) + .ifPresent(ann -> Stream.of(ann.value()).map(this::from).forEach(validationRules::add)); + } + + class PropertyMetadata { + + private boolean required; + private String description; + private String defaultValue; + private Double min; + private Double max; + private String pattern; + private boolean nullable; + private String format; + private List validationRules = new ArrayList<>(); + private boolean preserveUnknownFields; + private Class schemaFrom; + + public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) { + required = Boolean.TRUE.equals(value.getRequired()); + + description = beanProperty.getMetadata().getDescription(); + defaultValue = beanProperty.getMetadata().getDefaultValue(); + + schemaFrom = ofNullable(beanProperty.getAnnotation(SchemaFrom.class)).map(SchemaFrom::type).orElse(null); + + if (value.isValueTypeSchema()) { + ValueTypeSchema valueTypeSchema = value.asValueTypeSchema(); + this.format = ofNullable(valueTypeSchema.getFormat()).map(Object::toString).orElse(null); + } + + if (value.isStringSchema()) { + StringSchema stringSchema = value.asStringSchema(); + // only set if ValidationSchemaFactoryWrapper is used + this.pattern = stringSchema.getPattern(); // looks for the Pattern annotation + this.max = ofNullable(stringSchema.getMaxLength()).map(Integer::doubleValue).orElse(null); + this.min = ofNullable(stringSchema.getMinLength()).map(Integer::doubleValue).orElse(null); + } else { + // TODO: process the other schema types for validation values + } + + collectValidationRules(beanProperty, validationRules); + + // TODO: should probably move to a standard annotations + // see ValidationSchemaFactoryWrapper + nullable = beanProperty.getAnnotation(Nullable.class) != null; + max = ofNullable(beanProperty.getAnnotation(Max.class)).map(Max::value).orElse(max); + min = ofNullable(beanProperty.getAnnotation(Min.class)).map(Min::value).orElse(min); + + // TODO: should the following be deprecated? + required = beanProperty.getAnnotation(Required.class) != null; + defaultValue = ofNullable(beanProperty.getAnnotation(Default.class)).map(Default::value).orElse(defaultValue); + pattern = ofNullable(beanProperty.getAnnotation(Pattern.class)).map(Pattern::value).orElse(pattern); + preserveUnknownFields = beanProperty.getAnnotation(PreserveUnknownFields.class) != null; + } + + public void updateSchema(T schema) { + schema.setDescription(description); + + if (defaultValue != null) { + try { + schema.setDefault(YAML_MAPPER.readTree(defaultValue)); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Cannot parse default value: '" + defaultValue + "' as valid YAML."); + } + } + if (nullable) { + schema.setNullable(nullable); + } + schema.setMaximum(max); + schema.setMinimum(min); + schema.setPattern(pattern); + schema.setFormat(format); + if (preserveUnknownFields) { + schema.setXKubernetesPreserveUnknownFields(true); + } + + addToValidationRules(schema, validationRules); + } + } + + private T resolveObject(LinkedHashMap visited, InternalSchemaSwaps schemaSwaps, JsonSchema jacksonSchema, + String... ignore) { + Set ignores = ignore.length > 0 ? new LinkedHashSet<>(Arrays.asList(ignore)) : Collections.emptySet(); + + final T objectSchema = singleProperty("object"); + + schemaSwaps = schemaSwaps.branchAnnotations(); + final InternalSchemaSwaps swaps = schemaSwaps; + + GeneratorObjectSchema gos = (GeneratorObjectSchema)jacksonSchema.asObjectSchema(); + AnnotationIntrospector ai = resolvingContext.serializationConfig.getAnnotationIntrospector(); + BeanDescription bd = resolvingContext.serializationConfig.introspect(gos.javaType); + boolean preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null; + + consumeRepeatingAnnotation(gos.javaType.getRawClass(), SchemaSwap.class, ss -> { + swaps.registerSwap(gos.javaType.getRawClass(), + ss.originalType(), + ss.fieldName(), + ss.targetType(), ss.depth()); + }); + + List required = new ArrayList<>(); + + for (Map.Entry property : new TreeMap<>(gos.getProperties()).entrySet()) { + String name = property.getKey(); + if (ignores.contains(name)) { + continue; + } + schemaSwaps = schemaSwaps.branchDepths(); + SwapResult swapResult = schemaSwaps.lookupAndMark(gos.javaType.getRawClass(), name); + LinkedHashMap savedVisited = visited; + if (swapResult.onGoing) { + visited = new LinkedHashMap<>(); + } + + BeanProperty beanProperty = gos.beanProperties.get(property.getKey()); + Utils.checkNotNull(beanProperty, "CRD generation works only with bean properties"); + + JsonSchema propertySchema = property.getValue(); + PropertyMetadata propertyMetadata = new PropertyMetadata(propertySchema, beanProperty); + + // fallback to the JsonFormat pattern - currently not handled by the Jackson schema logic + if (propertyMetadata.pattern == null) { + propertyMetadata.pattern = ofNullable(ai.findFormat(beanProperty.getMember())).map(Value::getPattern) + .filter(Utils::isNotNullOrEmpty).orElse(null); + } + + if (propertyMetadata.required) { + required.add(name); + } + + JavaType type = beanProperty.getType(); + if (swapResult.classRef != null) { + propertyMetadata.schemaFrom = swapResult.classRef; + } + if (propertyMetadata.schemaFrom != null) { + if (propertyMetadata.schemaFrom == void.class) { + // fully omit - this is a little inconsistent with the NullSchema handling + continue; + } + propertySchema = toJsonSchema(propertyMetadata.schemaFrom); + type = resolvingContext.serializationConfig.constructType(propertyMetadata.schemaFrom); + } + + T schema = resolveProperty(visited, schemaSwaps, name, type, propertySchema); + + if (!swapResult.onGoing) { + for (Entry, LinkedHashMap> entry : pathMetadata.entrySet()) { + ofNullable(beanProperty.getAnnotation(entry.getKey())).ifPresent( + ann -> entry.getValue().put(toFQN(savedVisited, name), + new AnnotationMetadata(ann, propertyMetadata.description, schema.getType()))); + } + } + + propertyMetadata.updateSchema(schema); + + visited = savedVisited; + + addProperty(name, objectSchema, schema); + } + + swaps.throwIfUnmatchedSwaps(); + + objectSchema.setRequired(required); + if (preserveUnknownFields) { + objectSchema.setXKubernetesPreserveUnknownFields(true); + } + List validationRules = new ArrayList<>(); + consumeRepeatingAnnotation(gos.javaType.getRawClass(), ValidationRule.class, + v -> validationRules.add(from(v))); + addToValidationRules(objectSchema, validationRules); + return objectSchema; + } + + static String toFQN(LinkedHashMap visited, String name) { + if (visited.isEmpty()) { + return "." + name; + } + return visited.values().stream().collect(Collectors.joining(".", ".", ".")) + name; + } + + private T resolveProperty(LinkedHashMap visited, InternalSchemaSwaps schemaSwaps, String name, + JavaType type, JsonSchema jacksonSchema) { + + if (jacksonSchema.isArraySchema()) { + Items items = jacksonSchema.asArraySchema().getItems(); + if (items.isArrayItems()) { + throw new IllegalStateException("not yet supported"); + } + JsonSchema arraySchema = jacksonSchema.asArraySchema().getItems().asSingleItems().getSchema(); + final T schema = resolveProperty(visited, schemaSwaps, name, type.getContentType(), arraySchema); + return arrayLikeProperty(schema); + } else if (jacksonSchema.isIntegerSchema()) { + return singleProperty("integer"); + } else if (jacksonSchema.isNumberSchema()) { + return singleProperty("number"); + } else if (jacksonSchema.isBooleanSchema()) { + return singleProperty("boolean"); + } else if (jacksonSchema.isStringSchema()) { + // currently on string enums are supported + StringSchema stringSchema = jacksonSchema.asStringSchema(); + if (!stringSchema.getEnums().isEmpty()) { + Set ignores = type.isEnumType() ? findIngoredEnumConstants(type) : Collections.emptySet(); + final JsonNode[] enumValues = stringSchema.getEnums().stream() + .sorted() + .filter(s -> !ignores.contains(s)) + .map(JsonNodeFactory.instance::textNode) + .toArray(JsonNode[]::new); + return enumProperty(enumValues); + } + return singleProperty("string"); + } else if (jacksonSchema.isNullSchema()) { + return singleProperty("object"); // TODO: this may not be the right choice, but rarely will someone be using Void + } else if (jacksonSchema.isAnySchema()) { + if (type.getRawClass() == IntOrString.class || type.getRawClass() == Quantity.class) { + // TODO: create a serializer for this and remove this override + // - that won't work currently as there's no way to create a UnionSchema from the Jackson api + return intOrString(); + } + if (type.getRawClass() == RawExtension.class) { + return raw(); + } + // TODO: this could optionally take a type restriction + T schema = singleProperty(null); + schema.setXKubernetesPreserveUnknownFields(true); + return schema; + } else if (jacksonSchema.isUnionTypeSchema()) { + throw new IllegalStateException("not yet supported"); + } else if (jacksonSchema instanceof ReferenceSchema) { + // de-reference the reference schema - these can be naturally non-cyclic, for example siblings + ReferenceSchema ref = (ReferenceSchema)jacksonSchema; + GeneratorObjectSchema referenced = resolvingContext.seen.get(ref.get$ref()); + Utils.checkNotNull(referenced, "Could not find previously generated schema"); + jacksonSchema = referenced; + } else if (type.isMapLikeType()) { + final JavaType keyType = type.getKeyType(); + + if (keyType.getRawClass() != String.class) { + LOGGER.warn("Property '{}' with '{}' key type is mapped to 'string' because of CRD schemas limitations", name, keyType); + } + + final JavaType valueType = type.getContentType(); + JsonSchema mapValueSchema = ((SchemaAdditionalProperties)((ObjectSchema)jacksonSchema).getAdditionalProperties()).getJsonSchema(); + T component = resolveProperty(visited, schemaSwaps, name, valueType, mapValueSchema); + return mapLikeProperty(component); + } + + Class def = type.getRawClass(); + + // KubernetesResource is too broad, but we can check for several common subclasses + if (def == GenericKubernetesResource.class + || (def.isInterface() && HasMetadata.class.isAssignableFrom(def))) { + return raw(); + } + + if (visited.put(def.getName(), name) != null) { + throw new IllegalArgumentException( + "Found a cyclic reference involving the field of type " + def.getName() + " starting a field " + + visited.entrySet().stream().map(e -> e.getValue() + " >>\n" + e.getKey()).collect(Collectors.joining(".")) + "." + + name); + } + + T res = resolveObject(visited, schemaSwaps, jacksonSchema); + visited.remove(def.getName()); + return res; + } + + /** + * we've added support for ignoring an enum values, which complicates this processing + * as that is something not supported directly by jackson + */ + private Set findIngoredEnumConstants(JavaType type) { + Field[] fields = type.getRawClass().getFields(); + Set toIgnore = new HashSet<>(); + for (Field field : fields) { + if (field.isEnumConstant() && field.getAnnotation(JsonIgnore.class) != null) { + // hack to figure out the enum constant + try { + Object value = field.get(null); + toIgnore.add(resolvingContext.kubernetesSerialization.unmarshal(resolvingContext.kubernetesSerialization.asJson(value), String.class)); + } catch (IllegalArgumentException | IllegalAccessException e) { + } + } + } + return toIgnore; + } + + V from(ValidationRule validationRule) { + V result = newKubernetesValidationRule(); + result.setRule(validationRule.value()); + result.setReason(mapNotEmpty(validationRule.reason())); + result.setMessage(mapNotEmpty(validationRule.message())); + result.setMessageExpression(mapNotEmpty(validationRule.messageExpression())); + result.setFieldPath(mapNotEmpty(validationRule.fieldPath())); + result.setOptionalOldSelf(validationRule.optionalOldSelf() ? true : null); + return result; + } + + private static String mapNotEmpty(String s) { + return Utils.isNullOrEmpty(s) ? null : s; + } + + private JsonSchema toJsonSchema(Class clazz) { + try { + return resolvingContext.generator.generateSchema(clazz); + } catch (JsonMappingException e) { + throw new RuntimeException(e); + } + } + + protected abstract V newKubernetesValidationRule(); + + /** + * Adds the specified property to the specified builder + * + * @param name the property to add to the currently being built schema + * @param objectSchema the schema being built + * @param schema the built schema for the property being added + */ + protected abstract void addProperty(String name, T objectSchema, T schema); + + /** + * Builds the schema for specifically for intOrString properties + * + * @return the property schema + */ + protected abstract T intOrString(); + + /** + * Builds the schema for array-like properties + * + * @param schema the schema for the extracted element type for this array-like property + * @return the schema for the array-like property + */ + protected abstract T arrayLikeProperty(T schema); + + /** + * Builds the schema for map-like properties + * + * @param schema the schema for the extracted element type for the values of this map-like property + * @return the schema for the map-like property + */ + protected abstract T mapLikeProperty(T schema); + + /** + * Builds the schema for standard, simple (e.g. string) property types + * + * @param typeName the mapped name of the property type + * @return the schema for the property + */ + protected abstract T singleProperty(String typeName); + + protected abstract T enumProperty(JsonNode... enumValues); + + protected abstract void addToValidationRules(T schema, List validationRules); + + protected abstract T raw(); + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java new file mode 100644 index 00000000000..ea85f2ab830 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java @@ -0,0 +1,44 @@ +/* + * 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.crdv2.generator; + +import java.io.File; +import java.net.URI; +import java.util.HashMap; +import java.util.Map; + +public class CRDGenerationInfo { + + static final CRDGenerationInfo EMPTY = new CRDGenerationInfo(); + private final Map> crdNameToVersionToCRDInfoMap = new HashMap<>(); + + public Map getCRDInfos(String crdName) { + return crdNameToVersionToCRDInfoMap.get(crdName); + } + + public Map> getCRDDetailsPerNameAndVersion() { + return crdNameToVersionToCRDInfoMap; + } + + void add(String crdName, String version, URI fileURI) { + crdNameToVersionToCRDInfoMap.computeIfAbsent(crdName, k -> new HashMap<>()) + .put(version, new CRDInfo(crdName, version, new File(fileURI).getAbsolutePath())); + } + + public int numberOfGeneratedCRDs() { + return crdNameToVersionToCRDInfoMap.values().stream().map(Map::size).reduce(Integer::sum).orElse(0); + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java new file mode 100644 index 00000000000..64d259db23d --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -0,0 +1,257 @@ +/* + * 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.crdv2.generator; + +import com.fasterxml.jackson.databind.MapperFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; +import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import io.fabric8.crdv2.generator.v1.CustomResourceHandler; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.client.utils.ApiVersionUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Stream; + +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; +import static com.fasterxml.jackson.annotation.JsonInclude.Value.construct; + +public class CRDGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(CRDGenerator.class); + private final Resources resources; + private final Map handlers = new HashMap<>(2); + private CRDOutput output; + private boolean parallel; + private Map infos; + + // TODO: why not rely on the standard fabric8 yaml mapping + public static final ObjectMapper YAML_MAPPER = JsonMapper.builder(new YAMLFactory() + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .enable(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS) + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)) + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) + .configure(SerializationFeature.INDENT_OUTPUT, true) + .withConfigOverride(Map.class, configOverride -> configOverride.setInclude(construct(NON_NULL, NON_NULL))) + .serializationInclusion(NON_EMPTY) + .build(); + + public CRDGenerator() { + resources = new Resources(); + } + + public CRDGenerator inOutputDir(File outputDir) { + output = new DirCRDOutput(outputDir); + return this; + } + + public CRDGenerator withOutput(CRDOutput output) { + this.output = output; + return this; + } + + public CRDGenerator withParallelGenerationEnabled(boolean parallel) { + this.parallel = parallel; + return this; + } + + public CRDGenerator forCRDVersions(List versions) { + return versions != null && !versions.isEmpty() ? forCRDVersions(versions.toArray(new String[0])) + : this; + } + + public CRDGenerator forCRDVersions(String... versions) { + if (versions != null) { + for (String version : versions) { + if (version != null) { + switch (version) { + case CustomResourceHandler.VERSION: + handlers.computeIfAbsent(CustomResourceHandler.VERSION, + s -> new CustomResourceHandler(resources)); + break; + default: + LOGGER.warn("Ignoring unsupported CRD version: {}", version); + } + } + } + } + return this; + } + + Map getHandlers() { + return handlers; + } + + // this is public API, so we cannot change the signature, so there is no way to prevent the possible heap pollution + // (we also cannot use @SafeVarargs, because that requires the method to be final, which is another signature change) + @SuppressWarnings("unchecked") + public final CRDGenerator customResourceClasses(Class>... crClasses) { + return customResources(Stream.of(crClasses).map(CustomResourceInfo::fromClass).toArray(CustomResourceInfo[]::new)); + } + + public CRDGenerator customResources(CustomResourceInfo... infos) { + if (infos != null && infos.length > 0) { + if (this.infos == null) { + this.infos = new HashMap<>(infos.length); + } + Arrays.stream(infos) + .filter(Objects::nonNull) + // make sure we record all versions of the CR + .forEach(info -> this.infos.put(getOutputName(info.crdName(), info.version()), info)); + } + return this; + } + + Set getCustomResourceInfos() { + return this.infos == null ? Collections.emptySet() : new HashSet<>(infos.values()); + } + + public int generate() { + return detailedGenerate().numberOfGeneratedCRDs(); + } + + public CRDGenerationInfo detailedGenerate() { + if (getCustomResourceInfos().isEmpty()) { + LOGGER.warn("No resources were registered with the 'customResources' method to be generated"); + return CRDGenerationInfo.EMPTY; + } + + if (output == null) { + LOGGER.warn( + "No output option was selected either using 'inOutputDir' or 'withOutput' methods. Skipping generation."); + return CRDGenerationInfo.EMPTY; + } + + // if no CRD version is specified, generate for all supported versions + if (handlers.isEmpty()) { + forCRDVersions(CustomResourceHandler.VERSION); + } + + for (CustomResourceInfo info : infos.values()) { + if (info != null) { + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Generating '{}' version '{}' with {} (spec: {} / status {})...", + info.crdName(), info.version(), info.crClassName(), + info.specClassName().orElse("undetermined"), + info.statusClassName().orElse("undetermined")); + } + handlers.values().forEach(h -> h.handle(info)); + } + } + + final CRDGenerationInfo crdGenerationInfo = new CRDGenerationInfo(); + for (HasMetadata crd : resources.generate().getItems()) { + final String version = ApiVersionUtil.trimVersion(crd.getApiVersion()); + final String crdName = crd.getMetadata().getName(); + try { + final String outputName = getOutputName(crdName, version); + try (final OutputStream outputStream = output.outputFor(outputName)) { + outputStream.write( + "# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n" + .getBytes()); + YAML_MAPPER.writeValue(outputStream, crd); + final URI fileURI = output.crdURI(outputName); + crdGenerationInfo.add(crdName, version, fileURI); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + return crdGenerationInfo; + } + + public static String getOutputName(String crdName, String crdSpecVersion) { + return crdName + "-" + crdSpecVersion; + } + + public interface CRDOutput extends Closeable { + T outputFor(String crdName) throws IOException; + + default URI crdURI(String crdName) { + return URI.create("file:///" + crdName); + } + } + + public abstract static class AbstractCRDOutput implements CRDOutput { + private final Map crds = new HashMap<>(7); + + @Override + public T outputFor(String crdName) throws IOException { + final T outputStream = createStreamFor(crdName); + crds.put(crdName, outputStream); + return outputStream; + } + + protected abstract T createStreamFor(String crdName) throws IOException; + + protected T getStreamFor(String crdName) { + return crds.get(crdName); + } + + @Override + public void close() throws IOException { + for (T stream : crds.values()) { + stream.close(); + } + } + } + + static class DirCRDOutput extends AbstractCRDOutput { + private final File dir; + + public DirCRDOutput(File dir) { + if (!dir.isDirectory() || !dir.canWrite() || !dir.exists()) { + throw new IllegalArgumentException(dir + " must exist, be a writeable output directory"); + } + this.dir = dir; + } + + @Override + protected FileOutputStream createStreamFor(String crdName) throws IOException { + final File file = getCRDFile(crdName); + return new FileOutputStream(file); + } + + private File getCRDFile(String crdName) { + return new File(dir, crdName + ".yml"); + } + + @Override + public URI crdURI(String crdName) { + return getCRDFile(crdName).toURI(); + } + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java new file mode 100644 index 00000000000..25310b5518c --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java @@ -0,0 +1,50 @@ +/* + * 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.crdv2.generator; + +public class CRDInfo { + private final String crdName; + private final String crdSpecVersion; + private final String filePath; + + public CRDInfo(String crdName, String crdSpecVersion, String filePath) { + this.crdName = crdName; + this.crdSpecVersion = crdSpecVersion; + this.filePath = filePath; + } + + public String getCrdName() { + return crdName; + } + + /** + * @deprecated Use {@link #getCrdSpecVersion()} instead + * @return the CRD spec version + */ + @Deprecated + public String getVersion() { + return getCrdSpecVersion(); + } + + public String getCrdSpecVersion() { + return crdSpecVersion; + } + + public String getFilePath() { + return filePath; + } + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java new file mode 100644 index 00000000000..b5eaeac18c3 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java @@ -0,0 +1,231 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.crdv2.generator.utils.Types; +import io.fabric8.crdv2.generator.utils.Types.SpecAndStatus; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.client.KubernetesClientException; +import io.fabric8.kubernetes.client.dsl.base.ResourceDefinitionContext; +import io.fabric8.kubernetes.client.utils.Utils; +import io.fabric8.kubernetes.model.Scope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.lang.reflect.InvocationTargetException; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + +public class CustomResourceInfo { + + private static final Logger LOGGER = LoggerFactory.getLogger(CustomResourceInfo.class); + private final String group; + private final String version; + private final String kind; + private final String singular; + private final String plural; + private final String[] shortNames; + private final boolean storage; + private final boolean served; + private final boolean deprecated; + private final String deprecationWarning; + private final Scope scope; + private final Class definition; + private final String crClassName; + private final String specClassName; + private final String statusClassName; + private final String id; + private final int hash; + + private final String[] annotations; + private final String[] labels; + + public CustomResourceInfo(String group, String version, String kind, String singular, + String plural, String[] shortNames, boolean storage, boolean served, boolean deprecated, String deprecationWarning, + Scope scope, Class definition, String crClassName, + String specClassName, String statusClassName, String[] annotations, String[] labels) { + this.group = group; + this.version = version; + this.kind = kind; + this.singular = singular; + this.plural = plural; + this.shortNames = shortNames; + this.storage = storage; + this.served = served; + this.deprecated = deprecated; + this.deprecationWarning = deprecationWarning; + this.scope = scope; + this.definition = definition; + this.crClassName = crClassName; + this.specClassName = specClassName; + this.statusClassName = statusClassName; + this.id = crdName() + "/" + version; + this.hash = id.hashCode(); + this.annotations = annotations; + this.labels = labels; + } + + public boolean storage() { + return storage; + } + + public boolean served() { + return served; + } + + public boolean deprecated() { + return deprecated; + } + + public String deprecationWarning() { + return deprecationWarning; + } + + public String key() { + return crdName(); + } + + public Scope scope() { + return scope; + } + + public String crdName() { + return plural() + "." + group; + } + + public String[] shortNames() { + return shortNames; + } + + public String singular() { + return singular; + } + + public String plural() { + return plural; + } + + public String kind() { + return kind; + } + + public String version() { + return version; + } + + public String group() { + return group; + } + + public String crClassName() { + return crClassName; + } + + public Optional specClassName() { + return Optional.ofNullable(specClassName); + } + + public Optional statusClassName() { + return Optional.ofNullable(statusClassName); + } + + public Class definition() { + return definition; + } + + public String[] annotations() { + return annotations; + } + + public String[] labels() { + return labels; + } + + public static CustomResourceInfo fromClass(Class customResource) { + try { + final HasMetadata instance = customResource.getDeclaredConstructor().newInstance(); + + final String[] shortNames = CustomResource.getShortNames(customResource); + + final Scope scope = Utils.isResourceNamespaced(customResource) ? Scope.NAMESPACED : Scope.CLUSTER; + + SpecAndStatus specAndStatus = Types.resolveSpecAndStatusTypes(customResource); + if (specAndStatus.isUnreliable()) { + LOGGER.warn( + "Cannot reliably determine status types for {} because it isn't parameterized with only spec and status types. Status replicas detection will be deactivated.", + customResource.getCanonicalName()); + } + + ResourceDefinitionContext rdc = ResourceDefinitionContext.fromResourceType(customResource); + String singular = HasMetadata.getSingular(customResource); + boolean deprecated = CustomResource.getDeprecated(customResource); + String deprecationWarning = CustomResource.getDeprecationWarning(customResource); + boolean storage = CustomResource.getStorage(customResource); + boolean served = CustomResource.getServed(customResource); + + // instance level methods - TODO: deprecate? + if (instance instanceof CustomResource) { + CustomResource cr = (CustomResource)instance; + singular = cr.getSingular(); + deprecated = cr.isDeprecated(); + deprecationWarning = cr.getDeprecationWarning(); + storage = cr.isStorage(); + served = cr.isServed(); + } + + return new CustomResourceInfo(rdc.getGroup(), rdc.getVersion(), rdc.getKind(), + singular, rdc.getPlural(), shortNames, storage, served, + deprecated, deprecationWarning, + scope, customResource, + customResource.getCanonicalName(), specAndStatus.getSpecClassName(), + specAndStatus.getStatusClassName(), toStringArray(instance.getMetadata().getAnnotations()), + toStringArray(instance.getMetadata().getLabels())); + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + throw KubernetesClientException.launderThrowable(e); + } + } + + public static String[] toStringArray(Map map) { + String[] res = new String[map.size()]; + Set> entrySet = map.entrySet(); + int i = 0; + for (Map.Entry e : entrySet) { + res[i] = e.getKey() + "=" + e.getValue(); + i++; + } + return res; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + CustomResourceInfo that = (CustomResourceInfo) o; + return Objects.equals(id, that.id); + } + + @Override + public int hashCode() { + return hash; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java new file mode 100644 index 00000000000..c4bc69af77e --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java @@ -0,0 +1,170 @@ +/* + * 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.crdv2.generator; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +public class InternalSchemaSwaps { + // swaps applicable above this point + private final Map parentSwaps; + // swaps applicable to the current context + private final Map swaps; + // current depths of all swaps + private final Map swapDepths; + + public InternalSchemaSwaps() { + this(new HashMap<>(), new HashMap<>(), new HashMap<>()); + } + + private InternalSchemaSwaps(Map swaps, Map swapDepths, Map parentSwaps) { + this.parentSwaps = parentSwaps; + this.swaps = swaps; + this.swapDepths = swapDepths; + } + + public InternalSchemaSwaps branchDepths() { + InternalSchemaSwaps result = new InternalSchemaSwaps(this.swaps, new HashMap<>(), this.parentSwaps); + result.swapDepths.putAll(this.swapDepths); + return result; + } + + public InternalSchemaSwaps branchAnnotations() { + Map combined = new HashMap<>(swaps); + combined.putAll(parentSwaps); + return new InternalSchemaSwaps(new HashMap<>(), this.swapDepths, combined); + } + + public void registerSwap(Class definitionType, Class originalType, String fieldName, Class targetType, + int depth) { + Value value = new Value(definitionType, originalType, fieldName, targetType, depth); + Key key = new Key(originalType, fieldName); + if (parentSwaps.containsKey(key)) { + // it's simplest for now to just disallow this + throw new IllegalArgumentException("Nested SchemaSwap: " + value); + } + if (swaps.put(key, value) != null) { + throw new IllegalArgumentException("Duplicate SchemaSwap: " + value); + } + } + + static class SwapResult { + final Class classRef; + final boolean onGoing; + + public SwapResult(Class classRef, boolean onGoing) { + this.classRef = classRef; + this.onGoing = onGoing; + } + } + + public SwapResult lookupAndMark(Class originalType, String name) { + Key key = new Key(originalType, name); + Value value = swaps.getOrDefault(key, parentSwaps.get(key)); + if (value != null) { + int currentDepth = swapDepths.getOrDefault(key, 0); + swapDepths.put(key, currentDepth + 1); + value.markUsed(); + if (currentDepth == value.depth) { + return new SwapResult(value.getTargetType(), false); + } + if (currentDepth > value.depth) { + throw new IllegalStateException("Somthing has gone wrong with tracking swap depths, please raise an issue."); + } + return new SwapResult(null, true); + } + return new SwapResult(null, false); + } + + public void throwIfUnmatchedSwaps() { + String unmatchedSchemaSwaps = swaps.values().stream().filter(value -> !value.used) + .map(Object::toString) + .collect(Collectors.joining(", ")); + if (!unmatchedSchemaSwaps.isEmpty()) { + throw new IllegalArgumentException("Unmatched SchemaSwaps: " + unmatchedSchemaSwaps); + } + } + + private static final class Key { + private final Class originalType; + private final String fieldName; + + public Key(Class originalType, String fieldName) { + this.originalType = originalType; + this.fieldName = fieldName; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Key key = (Key) o; + return Objects.equals(originalType, key.originalType) && Objects.equals(fieldName, key.fieldName); + } + + @Override + public int hashCode() { + return Objects.hash(originalType, fieldName); + } + + @Override + public String toString() { + return new StringJoiner(", ", Key.class.getSimpleName() + "[", "]") + .add("originalType=" + originalType.getName()) + .add("fieldName='" + fieldName + "'") + .toString(); + } + } + + private static class Value { + private final Class originalType; + private final String fieldName; + private final Class targetType; + private boolean used; + private final Class definitionType; + private final int depth; + + public Value(Class definitionType, Class originalType, String fieldName, Class targetType, int depth) { + this.definitionType = definitionType; + this.originalType = originalType; + this.fieldName = fieldName; + this.targetType = targetType; + this.depth = depth; + this.used = false; + } + + private void markUsed() { + this.used = true; + } + + public Class getTargetType() { + return targetType; + } + + @Override + public String toString() { + return "@SchemaSwap(originalType=" + originalType.getName() + ", fieldName=\"" + fieldName + "\", targetType=" + targetType.getName() + + ") on " + definitionType.getName(); + } + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java new file mode 100644 index 00000000000..90d39ac63e6 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java @@ -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.crdv2.generator; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.List; + +public interface KubernetesJSONSchemaProps { + + String getType(); + + void setXKubernetesPreserveUnknownFields(Boolean b); + + void setMaximum(Double max); + + void setMinimum(Double min); + + void setPattern(String pattern); + + void setFormat(String format); + + void setNullable(Boolean nullable); + + void setDefault(JsonNode tree); + + void setDescription(String description); + + void setRequired(List required); + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesValidationRule.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesValidationRule.java new file mode 100644 index 00000000000..684138d2b62 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesValidationRule.java @@ -0,0 +1,33 @@ +/* + * 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.crdv2.generator; + +public interface KubernetesValidationRule { + + void setFieldPath(String fieldPath); + + void setMessage(String message); + + void setMessageExpression(String messageExpression); + + void setOptionalOldSelf(Boolean optionalOldSelf); + + void setReason(String reason); + + void setRule(String rule); + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java new file mode 100644 index 00000000000..5af27a8e5e3 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -0,0 +1,127 @@ +/* + * 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.crdv2.generator; + +import com.fasterxml.jackson.databind.BeanProperty; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; +import com.fasterxml.jackson.module.jsonSchema.JsonSchema; +import com.fasterxml.jackson.module.jsonSchema.JsonSchemaGenerator; +import com.fasterxml.jackson.module.jsonSchema.factories.JsonSchemaFactory; +import com.fasterxml.jackson.module.jsonSchema.factories.SchemaFactoryWrapper; +import com.fasterxml.jackson.module.jsonSchema.factories.VisitorContext; +import com.fasterxml.jackson.module.jsonSchema.factories.WrapperFactory; +import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; + +public class ResolvingContext { + + static final class GeneratorObjectSchema extends ObjectSchema { + + JavaType javaType; + Map beanProperties = new LinkedHashMap<>(); + + @Override + public void putOptionalProperty(BeanProperty property, JsonSchema jsonSchema) { + beanProperties.put(property.getName(), property); + super.putOptionalProperty(property, jsonSchema); + } + + @Override + public JsonSchema putProperty(BeanProperty property, JsonSchema value) { + beanProperties.put(property.getName(), property); + return super.putProperty(property, value); + } + + } + + private final class KubernetesSchemaFactoryWrapper extends SchemaFactoryWrapper { + + private KubernetesSchemaFactoryWrapper(SerializerProvider p, WrapperFactory wrapperFactory) { + super(p, wrapperFactory); + this.schemaProvider = new JsonSchemaFactory() { + + @Override + public ObjectSchema objectSchema() { + return new GeneratorObjectSchema(); + } + + }; + } + + @Override + public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) { + // TODO: jackson should pass in directly here if there's an anyGetter / setter + // so that we may directly mark preserve unknown + JsonObjectFormatVisitor result = super.expectObjectFormat(convertedType); + ((GeneratorObjectSchema)schema).javaType = convertedType; + seen.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema)schema); + return result; + } + } + + final JsonSchemaGenerator generator; + final SerializationConfig serializationConfig; + final KubernetesSerialization kubernetesSerialization; + final Map seen = new HashMap<>(); + + private static class AccessibleKubernetesSerialization extends KubernetesSerialization { + + @Override + public ObjectMapper getMapper() { + return super.getMapper(); + }; + + } + + private static AccessibleKubernetesSerialization DEFAULT_KUBERNETES_SERIALIZATION; + + public static ResolvingContext defaultResolvingContext() { + if (DEFAULT_KUBERNETES_SERIALIZATION == null) { + DEFAULT_KUBERNETES_SERIALIZATION = new AccessibleKubernetesSerialization(); + } + return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), DEFAULT_KUBERNETES_SERIALIZATION); + } + + public ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization) { + serializationConfig = mapper.getSerializationConfig(); + this.kubernetesSerialization = kubernetesSerialization; + generator = new JsonSchemaGenerator(mapper, new WrapperFactory() { + + @Override + public SchemaFactoryWrapper getWrapper(SerializerProvider provider) { + return new KubernetesSchemaFactoryWrapper(provider, this); + } + + @Override + public SchemaFactoryWrapper getWrapper(SerializerProvider provider, VisitorContext rvc) { + SchemaFactoryWrapper wrapper = getWrapper(provider); + wrapper.setVisitorContext(rvc); + return wrapper; + } + + }); + } + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java new file mode 100644 index 00000000000..45e671581b9 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java @@ -0,0 +1,130 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.api.model.KubernetesListBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +public class Resources { + private static final Logger LOGGER = LoggerFactory.getLogger(Resources.class); + private static final int SORT_ROUND_LIMIT = 10; + + private final KubernetesListBuilder global = new KubernetesListBuilder(); + private final Set> globalDecorators = new TreeSet<>(); + + /** + * Get the global builder + * + * @return The groups map. + */ + public KubernetesListBuilder global() { + return this.global; + } + + /** + * Get the Decorator Set. + * The method is visible for testing purposes. + * + * @return the Set of registed Decorators. + */ + protected Set> getDecorators() { + return globalDecorators; + } + + /** + * Add a {@link Decorator}. + * + * @param decorator The decorator. + */ + public void decorate(Decorator decorator) { + globalDecorators.add(decorator); + } + + /** + * Add a resource to all groups. + * + * @param metadata the resource to add to this Resources + */ + public void add(HasMetadata metadata) { + global.addToItems(metadata); + } + + /** + * Generate all resources. + * + * @return A map of {@link KubernetesList} by group name. + */ + public KubernetesList generate() { + for (Decorator decorator : applyConstraints(globalDecorators)) { + this.global.accept(decorator); + } + return this.global.build(); + } + + public List> applyConstraints(Set> decorators) { + Decorator[] array = decorators.toArray(new Decorator[0]); + // We can't guarantee that `when `decorator a < b and b < c then a < c``. + // Why? + // Because our comparators express constraints on particular pairs and can't express the global order. + // So, in order to be accurate we need to compare each decorator, with ALL OTHER decorators. + // In other words we need bubble sort. + // We also might need it more than once. So, we'll do it as many times as we have to, till there are not more transformations. + // But hey, let's have an upper limit just to prevent infinite loops. + for (int i = 0; i < SORT_ROUND_LIMIT && bubbleSort(array); i++) { + LOGGER.debug("Sorting again: {}", i + 1); + } + + List> result = Collections.unmodifiableList(Arrays.asList(array)); + + if (LOGGER.isTraceEnabled()) { + result.forEach(decorator -> LOGGER.trace("{}", decorator)); + } + + return result; + } + + /** + * Bubble sort for decorators. + * + * @param decorators the {@link Decorator} array to be sorted + */ + private boolean bubbleSort(Decorator[] decorators) { + boolean swapped = false; + int n = decorators.length; + Decorator temp; + for (int i = 0; i < n; i++) { + for (int j = 1; j < (n - i); j++) { + if (decorators[j].compareTo(decorators[j - 1]) < 0) { + swapped = true; + temp = decorators[j - 1]; + decorators[j - 1] = decorators[j]; + decorators[j] = temp; + } + } + } + return swapped; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java new file mode 100644 index 00000000000..4de4def88c0 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java @@ -0,0 +1,66 @@ +/* + * 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.crdv2.generator.decorator; + +import io.fabric8.kubernetes.api.builder.TypedVisitor; + +public abstract class Decorator extends TypedVisitor implements Comparable { + + public Class[] after() { + return new Class[0]; + } + + public Class[] before() { + return new Class[0]; + } + + @Override + public int compareTo(Decorator o) { + //We only want to return 0 if decorators are equal. + if (this.equals(o)) { + return 0; + } + Class c = o.getClass(); + //1st pass: ours + for (Class b : before()) { + if (b.isAssignableFrom(c)) { + return -1; + } + } + for (Class a : after()) { + if (a.isAssignableFrom(c)) { + return 1; + } + } + //2nd pass: their + for (Class b : o.before()) { + if (b.isAssignableFrom(getClass())) { + return 1; + } + } + for (Class a : o.after()) { + if (a.isAssignableFrom(getClass())) { + return -1; + } + } + //Reproducible order every single time + int result = getClass().getName().compareTo(o.getClass().getName()); + if (result == 0) { + result = hashCode() - o.hashCode(); + } + return result; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java new file mode 100644 index 00000000000..9c1eb3a5291 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java @@ -0,0 +1,136 @@ +/* + * 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.crdv2.generator.decorator; + +import io.fabric8.crdv2.generator.utils.Generics; +import io.fabric8.crdv2.generator.utils.Metadata; +import io.fabric8.kubernetes.api.builder.TypedVisitor; +import io.fabric8.kubernetes.api.builder.VisitableBuilder; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.client.utils.Utils; + +import java.util.Optional; + +public abstract class NamedResourceDecorator extends Decorator { + + /** + * For resource name null acts as a wildcards. + * Let's use a constant instead, for clarity's shake + */ + public static final String ANY = null; + + protected final String kind; + protected final String name; + + private final ResourceVisitor visitor = new ResourceVisitor(null, null); + + public NamedResourceDecorator() { + this(ANY, ANY); + } + + public NamedResourceDecorator(String name) { + this(ANY, name); + } + + public NamedResourceDecorator(String kind, String name) { + this.kind = kind; + this.name = name; + } + + public String getKind() { + return kind; + } + + public String getName() { + return name; + } + + @Override + public void visit(VisitableBuilder builder) { + Optional resourceKind = Metadata.getKind(builder); + Optional objectMeta = Metadata.getMetadata(builder); + if (!resourceKind.isPresent() || !objectMeta.isPresent()) { + return; + } + if (Utils.isNullOrEmpty(kind)) { + if (Utils.isNullOrEmpty(name)) { + builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); + } else if (objectMeta.map(ObjectMeta::getName).filter(s -> s.equals(name)).isPresent()) { + builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); + } + } else if (resourceKind.filter(k -> k.equals(kind)).isPresent()) { + if (Utils.isNullOrEmpty(name)) { + builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); + } else if (objectMeta.map(ObjectMeta::getName).filter(s -> s.equals(name)).isPresent()) { + builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); + } + } + } + + /** + * Visit a part of a Resource. + * + * @param item the visited item + * @param resourceMeta the {@link ObjectMeta} of the current resource. + */ + public abstract void andThenVisit(T item, ObjectMeta resourceMeta); + + /** + * Visit a part of a Resource. + * + * @param item the visited item + * @param kind the resource kind + * @param resourceMeta the {@link ObjectMeta} of the current resource. + */ + public void andThenVisit(T item, String kind, ObjectMeta resourceMeta) { + andThenVisit(item, resourceMeta); + } + + private class ResourceVisitor extends TypedVisitor { + + private final String kind; + private final ObjectMeta metadata; + + public ResourceVisitor(String kind, ObjectMeta metadata) { + this.kind = kind; + this.metadata = metadata; + } + + @Override + public void visit(T item) { + andThenVisit(item, kind, metadata); + } + + public ResourceVisitor withKind(String kind) { + return new ResourceVisitor(kind, this.metadata); + } + + public ResourceVisitor withMetadata(ObjectMeta metadata) { + return new ResourceVisitor(this.kind, metadata); + } + + public Class getType() { + return (Class) Generics + .getTypeArguments(NamedResourceDecorator.class, NamedResourceDecorator.this.getClass()) + .get(0); + } + } + + @Override + public Class[] after() { + return new Class[] { ResourceProvidingDecorator.class }; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java new file mode 100644 index 00000000000..8e5b661867e --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java @@ -0,0 +1,39 @@ +/* + * 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.crdv2.generator.decorator; + +import java.util.HashMap; +import java.util.Map; + +public abstract class ResourceProvidingDecorator extends Decorator { + + protected Map toMap(String[] arr) { + Map res = new HashMap<>(); + if (arr != null) { + for (String e : arr) { + String[] splitted = e.split("\\="); + if (splitted.length >= 2) { + res.put(splitted[0], e.substring(splitted[0].length() + 1)); + } else { + throw new IllegalArgumentException( + "Invalid value: " + e + " cannot be parsed as a key-value pair. Expected format is 'key=value'."); + } + } + } + return res; + } + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java new file mode 100644 index 00000000000..977a1771464 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java @@ -0,0 +1,115 @@ +/* + * 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.crdv2.generator.utils; + +import java.lang.reflect.Array; +import java.lang.reflect.GenericArrayType; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.lang.reflect.TypeVariable; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Generics utilities borrowed from sundrio's TypedVisitor class. + */ +public class Generics { + + /** + * Get the underlying class for a type, or null if the type is a variable type. + * + * @param type the type + * @return the underlying class + */ + private Generics() { + throw new IllegalStateException("Utility class"); + } + + public static Class getClass(Type type) { + if (type instanceof Class) { + return (Class) type; + } else if (type instanceof ParameterizedType) { + return getClass(((ParameterizedType) type).getRawType()); + } else if (type instanceof GenericArrayType) { + Type componentType = ((GenericArrayType) type).getGenericComponentType(); + Class componentClass = getClass(componentType); + if (componentClass != null) { + return Array.newInstance(componentClass, 0).getClass(); + } else { + return null; + } + } else { + return null; + } + } + + /** + * Get the actual type arguments a child class has used to extend a generic base class. + * + * @param baseClass the base class + * @param childClass the child class + * @param the type of the base class + * @return a list of the raw classes for the actual type arguments + */ + public static List getTypeArguments(Class baseClass, + Class childClass) { + Map resolvedTypes = new LinkedHashMap<>(); + Type type = childClass; + // start walking up the inheritance hierarchy until we hit baseClass + while (true) { + final Class clazz = getClass(type); + if (clazz == null || clazz.equals(baseClass)) + break; + if (type instanceof Class) { + // there is no useful information for us in raw types, so just keep going. + type = ((Class) type).getGenericSuperclass(); + } else { + ParameterizedType parameterizedType = (ParameterizedType) type; + Class rawType = (Class) parameterizedType.getRawType(); + + Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); + TypeVariable[] typeParameters = rawType.getTypeParameters(); + for (int i = 0; i < actualTypeArguments.length; i++) { + resolvedTypes.put(typeParameters[i], actualTypeArguments[i]); + } + + if (!rawType.equals(baseClass)) { + type = rawType.getGenericSuperclass(); + } + } + } + + // finally, for each actual type argument provided to baseClass, determine (if possible) + // the raw class for that type argument. + Type[] actualTypeArguments; + if (type instanceof Class) { + actualTypeArguments = ((Class) type).getTypeParameters(); + } else { + actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); + } + List typeArgumentsAsClasses = new ArrayList<>(); + // resolve types by chasing down type variables. + for (Type baseType : actualTypeArguments) { + while (resolvedTypes.containsKey(baseType)) { + baseType = resolvedTypes.get(baseType); + } + typeArgumentsAsClasses.add(getClass(baseType)); + } + return typeArgumentsAsClasses; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java new file mode 100644 index 00000000000..6554e3e488e --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java @@ -0,0 +1,124 @@ +/* + * 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.crdv2.generator.utils; + +import io.fabric8.kubernetes.api.builder.Builder; +import io.fabric8.kubernetes.api.builder.VisitableBuilder; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.ObjectMetaFluent; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Optional; +import java.util.function.Predicate; + +public class Metadata { + private Metadata() { + throw new IllegalStateException("Utility class"); + } + + public static Optional getKind(Builder builder) { + try { + Method method = builder.getClass().getMethod("getKind"); + Object o = method.invoke(builder); + if (o instanceof String) { + return Optional.of((String) o); + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + //ignore + } + return Optional.empty(); + } + + public static Optional getMetadata(Builder builder) { + try { + Method method = builder.getClass().getMethod("buildMetadata"); + Object o = method.invoke(builder); + if (o instanceof ObjectMeta) { + return Optional.of((ObjectMeta) o); + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + //ignore + } + return Optional.empty(); + } + + public static boolean addToLabels(Builder builder, String key, String value) { + try { + Method editMethod = builder.getClass().getMethod("editOrNewMetadata"); + Object o = editMethod.invoke(builder); + if (o instanceof ObjectMetaFluent) { + ObjectMetaFluent fluent = (ObjectMetaFluent) o; + fluent.addToLabels(key, value); + Method endMethod = fluent.getClass().getMethod("endMetadata"); + endMethod.invoke(fluent); + return true; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + //ignore + } + return false; + } + + public static boolean removeFromLabels(Builder builder, String key) { + try { + Method editMethod = builder.getClass().getMethod("editOrNewMetadata"); + Object o = editMethod.invoke(builder); + if (o instanceof ObjectMetaFluent) { + ObjectMetaFluent fluent = (ObjectMetaFluent) o; + fluent.removeFromLabels(key); + Method endMethod = fluent.getClass().getMethod("endMetadata"); + endMethod.invoke(fluent); + return true; + } + } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + //ignore + } + return false; + } + + /** + * Create a {@link Predicate} that checks that a resource builder doesn't match the name and kind. + * + * @param candidate The specified resource. + * @return The predicate. + */ + public static Predicate> matching( + HasMetadata candidate) { + return matching(candidate.getApiVersion(), candidate.getKind(), + candidate.getMetadata().getName()); + } + + /** + * Create a {@link Predicate} that checks that a resource builder doesn't match the name and kind. + * + * @param apiVersion the API version the resources must match + * @param kind The specified kind. + * @param name The specified name. + * @return The predicate. + */ + public static Predicate> matching(String apiVersion, + String kind, String name) { + return builder -> { + HasMetadata item = builder.build(); + ObjectMeta metadata = item.getMetadata(); + return apiVersion.equals(item.getApiVersion()) && + kind != null && kind.equals(item.getKind()) && + name != null && name.equals(metadata.getName()); + }; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Types.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Types.java new file mode 100644 index 00000000000..5f02a4fc486 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Types.java @@ -0,0 +1,75 @@ +/* + * 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.crdv2.generator.utils; + +import com.fasterxml.jackson.databind.BeanDescription; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationConfig; +import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; + +public class Types { + private Types() { + throw new IllegalStateException("Utility class"); + } + + public static class SpecAndStatus { + + private final String specClassName; + private final String statusClassName; + private final boolean unreliable; + + public SpecAndStatus(String specClassName, String statusClassName) { + this.specClassName = specClassName; + this.statusClassName = statusClassName; + this.unreliable = specClassName == null || statusClassName == null; + } + + public String getSpecClassName() { + return specClassName; + } + + public String getStatusClassName() { + return statusClassName; + } + + public boolean isUnreliable() { + return unreliable; + } + } + + /** + * Determine the spec and status types via convention by looking for the + * spec and status properties. + * + * If we support eventually support spec and status interfaces or some other mechanism + * then this logic will need to change + */ + public static SpecAndStatus resolveSpecAndStatusTypes(Class definition) { + SerializationConfig config = new ObjectMapper().getSerializationConfig(); + BeanDescription description = config.introspect(config.constructType(definition)); + String specClassName = null; + String statusClassName = null; + for (BeanPropertyDefinition bpd : description.findProperties()) { + if (bpd.getName().equals("spec") && bpd.getRawPrimaryType() != Void.class) { + specClassName = bpd.getRawPrimaryType().getName(); + } else if (bpd.getName().equals("status") && bpd.getRawPrimaryType() != Void.class) { + statusClassName = bpd.getRawPrimaryType().getName(); + } + } + return new SpecAndStatus(specClassName, statusClassName); + } + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java new file mode 100644 index 00000000000..42e4868ec60 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -0,0 +1,107 @@ +/* + * 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.crdv2.generator.v1; + +import io.fabric8.crd.generator.annotation.PrinterColumn; +import io.fabric8.crdv2.generator.AbstractCustomResourceHandler; +import io.fabric8.crdv2.generator.CustomResourceInfo; +import io.fabric8.crdv2.generator.ResolvingContext; +import io.fabric8.crdv2.generator.Resources; +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.crdv2.generator.v1.decorator.AddAdditionPrinterColumnDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddCustomResourceDefinitionResourceDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddCustomResourceDefinitionVersionDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddLabelSelectorPathDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddSchemaToCustomResourceDefinitionVersionDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddSpecReplicasPathDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddStatusReplicasPathDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddStatusSubresourceDecorator; +import io.fabric8.crdv2.generator.v1.decorator.AddSubresourcesDecorator; +import io.fabric8.crdv2.generator.v1.decorator.EnsureSingleStorageVersionDecorator; +import io.fabric8.crdv2.generator.v1.decorator.SetDeprecatedVersionDecorator; +import io.fabric8.crdv2.generator.v1.decorator.SetServedVersionDecorator; +import io.fabric8.crdv2.generator.v1.decorator.SetStorageVersionDecorator; +import io.fabric8.crdv2.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator; +import io.fabric8.crdv2.generator.v1.decorator.SortPrinterColumnsDecorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.model.annotation.LabelSelector; +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import io.fabric8.kubernetes.model.annotation.StatusReplicas; + +public class CustomResourceHandler extends AbstractCustomResourceHandler { + + public static final String VERSION = "v1"; + + public CustomResourceHandler(Resources resources) { + super(resources); + } + + @Override + protected Decorator getPrinterColumnDecorator(String name, + String version, String path, + String type, String column, String description, String format, int priority) { + return new AddAdditionPrinterColumnDecorator(name, version, type, column, path, format, + description, priority); + } + + @Override + public void handle(CustomResourceInfo config) { + final String name = config.crdName(); + final String version = config.version(); + resources.decorate( + new AddCustomResourceDefinitionResourceDecorator(name, config.group(), config.kind(), + config.scope().value(), config.shortNames(), config.plural(), config.singular(), config.annotations(), + config.labels())); + + resources.decorate(new AddCustomResourceDefinitionVersionDecorator(name, version)); + + JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), config.definition()); + JSONSchemaProps schema = resolver.getSchema(); + + resources.decorate(new AddSchemaToCustomResourceDefinitionVersionDecorator(name, version, + schema)); + + resolver.getSinglePath(SpecReplicas.class).ifPresent(path -> { + resources.decorate(new AddSubresourcesDecorator(name, version)); + resources.decorate(new AddSpecReplicasPathDecorator(name, version, path)); + }); + + resolver.getSinglePath(StatusReplicas.class).ifPresent(path -> { + resources.decorate(new AddSubresourcesDecorator(name, version)); + resources.decorate(new AddStatusReplicasPathDecorator(name, version, path)); + }); + + resolver.getSinglePath(LabelSelector.class).ifPresent(path -> { + resources.decorate(new AddSubresourcesDecorator(name, version)); + resources.decorate(new AddLabelSelectorPathDecorator(name, version, path)); + }); + + handlePrinterColumns(name, version, resolver.getAllPaths(PrinterColumn.class)); + + if (config.statusClassName().isPresent()) { + resources.decorate(new AddSubresourcesDecorator(name, version)); + resources.decorate(new AddStatusSubresourceDecorator(name, version)); + } + + resources.decorate(new SetServedVersionDecorator(name, version, config.served())); + 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)); + } + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java new file mode 100644 index 00000000000..b5fa2c60167 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java @@ -0,0 +1,109 @@ +/* + * 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.crdv2.generator.v1; + +import com.fasterxml.jackson.databind.JsonNode; +import io.fabric8.crdv2.generator.AbstractJsonSchema; +import io.fabric8.crdv2.generator.KubernetesJSONSchemaProps; +import io.fabric8.crdv2.generator.KubernetesValidationRule; +import io.fabric8.crdv2.generator.ResolvingContext; +import io.fabric8.crdv2.generator.v1.JsonSchema.V1JSONSchemaProps; +import io.fabric8.crdv2.generator.v1.JsonSchema.V1ValidationRule; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsOrArray; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsOrBool; +import io.fabric8.kubernetes.api.model.apiextensions.v1.ValidationRule; + +import java.util.Arrays; +import java.util.List; + +public class JsonSchema extends AbstractJsonSchema { + + public static class V1ValidationRule extends ValidationRule implements KubernetesValidationRule { + + } + + public static class V1JSONSchemaProps extends JSONSchemaProps implements KubernetesJSONSchemaProps { + + } + + public static JSONSchemaProps from(Class definition) { + return new JsonSchema(ResolvingContext.defaultResolvingContext(), definition).getSchema(); + } + + public JsonSchema(ResolvingContext resolvingContext, Class definition) { + super(resolvingContext, definition); + } + + @Override + protected V1ValidationRule newKubernetesValidationRule() { + return new V1ValidationRule(); + } + + @Override + protected void addToValidationRules(V1JSONSchemaProps schema, List validationRules) { + schema.getXKubernetesValidations().addAll(validationRules); + } + + @Override + protected void addProperty(String name, V1JSONSchemaProps objectSchema, V1JSONSchemaProps schema) { + objectSchema.getProperties().put(name, schema); + } + + @Override + protected V1JSONSchemaProps arrayLikeProperty(V1JSONSchemaProps schema) { + V1JSONSchemaProps result = singleProperty("array"); + result.setItems(new JSONSchemaPropsOrArray(null, schema)); + return result; + } + + @Override + protected V1JSONSchemaProps mapLikeProperty(V1JSONSchemaProps schema) { + V1JSONSchemaProps result = singleProperty("object"); + result.setAdditionalProperties(new JSONSchemaPropsOrBool(null, schema)); + return result; + } + + @Override + protected V1JSONSchemaProps singleProperty(String typeName) { + V1JSONSchemaProps result = new V1JSONSchemaProps(); + result.setType(typeName); + return result; + } + + @Override + protected V1JSONSchemaProps intOrString() { + V1JSONSchemaProps result = new V1JSONSchemaProps(); + result.setXKubernetesIntOrString(true); + result.setAnyOf(Arrays.asList(singleProperty("integer"), singleProperty("string"))); + return result; + } + + @Override + protected V1JSONSchemaProps enumProperty(JsonNode... enumValues) { + V1JSONSchemaProps result = singleProperty("string"); + result.setEnum(Arrays.asList(enumValues)); + return result; + } + + @Override + protected V1JSONSchemaProps raw() { + V1JSONSchemaProps result = singleProperty(null); + result.setXKubernetesEmbeddedResource(true); + return result; + } + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java new file mode 100644 index 00000000000..aded91f2c96 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java @@ -0,0 +1,128 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceColumnDefinitionBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; +import io.fabric8.kubernetes.client.utils.Utils; + +import java.util.function.Predicate; + +public class AddAdditionPrinterColumnDecorator extends + CustomResourceDefinitionVersionDecorator> { + + private final String type; + private final String columnName; + private final String path; + private final String format; + private final String description; + private final int priority; + + public AddAdditionPrinterColumnDecorator(String name, String version, String type, String columnName, String path, + String format, String description, int priority) { + super(name, version); + this.type = type; + this.columnName = columnName; + this.path = path; + this.format = format; + this.description = description; + this.priority = priority; + } + + @Override + public void andThenVisit(CustomResourceDefinitionVersionFluent spec) { + Predicate matchingColumn = col -> col.getName() != null + && col.getName().equals(columnName) && col.getJsonPath() != null && col.getJsonPath().equals(path); + spec.removeMatchingFromAdditionalPrinterColumns(matchingColumn); + + spec.addNewAdditionalPrinterColumn() + .withType(type) + .withName(columnName) + .withJsonPath(path) + .withFormat(Utils.isNotNullOrEmpty(format) ? format : null) + .withDescription(Utils.isNotNullOrEmpty(description) ? description : null) + .withPriority(priority) + .endAdditionalPrinterColumn(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = super.hashCode(); + result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); + result = prime * result + ((getVersion() == null) ? 0 : getVersion().hashCode()); + result = prime * result + ((columnName == null) ? 0 : columnName.hashCode()); + result = prime * result + ((type == null) ? 0 : type.hashCode()); + result = prime * result + ((path == null) ? 0 : path.hashCode()); + result = prime * result + ((format == null) ? 0 : format.hashCode()); + result = prime * result + ((description == null) ? 0 : description.hashCode()); + result = prime * result + priority; + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!super.equals(obj)) + return false; + if (getClass() != obj.getClass()) + return false; + AddAdditionPrinterColumnDecorator other = (AddAdditionPrinterColumnDecorator) obj; + if (getName() == null) { + if (other.getName() != null) + return false; + } else if (!getName().equals(other.getName())) + return false; + if (getVersion() == null) { + if (other.getVersion() != null) + return false; + } else if (!getVersion().equals(other.getVersion())) + return false; + if (description == null) { + if (other.description != null) + return false; + } else if (!description.equals(other.description)) + return false; + if (format == null) { + if (other.format != null) + return false; + } else if (!format.equals(other.format)) + return false; + if (columnName == null) { + if (other.columnName != null) + return false; + } else if (!columnName.equals(other.columnName)) + return false; + if (path == null) { + if (other.path != null) + return false; + } else if (!path.equals(other.path)) + return false; + if (type == null) { + if (other.type != null) + return false; + } else if (!type.equals(other.type)) + return false; + return priority == other.priority; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "column:" + columnName + "priority:" + + priority + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java new file mode 100644 index 00000000000..56cb53e051e --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java @@ -0,0 +1,87 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.crdv2.generator.decorator.ResourceProvidingDecorator; +import io.fabric8.kubernetes.api.model.KubernetesListBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder; +import io.fabric8.kubernetes.client.utils.ApiVersionUtil; + +public class AddCustomResourceDefinitionResourceDecorator extends ResourceProvidingDecorator { + + private String name; + private String apiGroup; + private String kind; + + private String scope; + private String[] shortNames; + private String plural; + private String singular; + private String[] annotations; + private String[] labels; + + public AddCustomResourceDefinitionResourceDecorator(String name, String apiGroup, String kind, + String scope, String[] shortNames, String plural, String singular, String[] annotations, String[] labels) { + this.name = name; + this.apiGroup = apiGroup; + this.kind = kind; + this.scope = scope; + this.shortNames = shortNames; + this.plural = plural; + this.singular = singular; + this.annotations = annotations; + this.labels = labels; + } + + @Override + public void visit(KubernetesListBuilder list) { + boolean exists = list.buildItems().stream().anyMatch(i -> i.getKind().equals("CustomResourceDefinition") + && i.getMetadata().getName().equals(name) + && ApiVersionUtil.trimVersion(i.getApiVersion()).equals("v1")); + + if (!exists) { + list.addToItems(new CustomResourceDefinitionBuilder() + .withNewMetadata() + .withName(name) + .withAnnotations(toMap(annotations)) + .withLabels(toMap(labels)) + .endMetadata() + .withNewSpec() + .withScope(scope) + .withGroup(apiGroup) + .withNewNames() + .withKind(kind) + .withShortNames(shortNames) + .withPlural(plural) + .withSingular(singular) + .endNames() + .endSpec() + .build()); + } + } + + @Override + public Class[] before() { + return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, CustomResourceDefinitionDecorator.class }; + } + + @Override + public String toString() { + return getClass().getName() + " [apiGroup=" + apiGroup + ", kind=" + kind + ", name=" + name + ", plural=" + plural + + ", scope=" + scope + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java new file mode 100644 index 00000000000..5362427e01f --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java @@ -0,0 +1,63 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.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.CustomResourceDefinitionVersionBuilder; + +import java.util.function.Predicate; + +public class AddCustomResourceDefinitionVersionDecorator extends + CustomResourceDefinitionDecorator> { + + private final String version; + + public AddCustomResourceDefinitionVersionDecorator(String name, String version) { + super(name); + this.version = version; + } + + public String getName() { + return this.name; + } + + public String getVersion() { + return this.version; + } + + @Override + public void andThenVisit(CustomResourceDefinitionSpecFluent spec, ObjectMeta resourceMeta) { + Predicate predicate = candidate -> candidate.getName() + .equals(version); + + spec.removeMatchingFromVersions(predicate); + spec.addNewVersion() + .withName(version) + .endVersion(); + } + + @Override + public Class[] after() { + return new Class[] { AddCustomResourceDefinitionResourceDecorator.class }; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + version + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java new file mode 100644 index 00000000000..ac47673f099 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java @@ -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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; + +public class AddLabelSelectorPathDecorator + extends CustomResourceDefinitionVersionDecorator> { + + private final String path; + + public AddLabelSelectorPathDecorator(String name, String version, String path) { + super(name, version); + this.path = path; + } + + @Override + public void andThenVisit(CustomResourceSubresourcesFluent subresources) { + if (subresources.hasScale()) { + subresources.editScale().withLabelSelectorPath(path).endScale(); + } else { + subresources.withNewScale().withLabelSelectorPath(path).endScale(); + } + } + + @Override + public Class[] after() { + return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, + AddSubresourcesDecorator.class }; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java new file mode 100644 index 00000000000..d3aa120c50f --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java @@ -0,0 +1,46 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; + +public class AddSchemaToCustomResourceDefinitionVersionDecorator + extends CustomResourceDefinitionVersionDecorator> { + + private JSONSchemaProps schema; + + public AddSchemaToCustomResourceDefinitionVersionDecorator(String name, String version, JSONSchemaProps schema) { + super(name, version); + this.schema = schema; + } + + @Override + public void andThenVisit(CustomResourceDefinitionVersionFluent version) { + version.withNewSchema().withOpenAPIV3Schema(schema).endSchema(); + } + + @Override + public Class[] after() { + return new Class[] { AddCustomResourceDefinitionVersionDecorator.class }; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java new file mode 100644 index 00000000000..f0fa4f29d28 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java @@ -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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; + +public class AddSpecReplicasPathDecorator + extends CustomResourceDefinitionVersionDecorator> { + + private final String path; + + public AddSpecReplicasPathDecorator(String name, String version, String path) { + super(name, version); + this.path = path; + } + + @Override + public void andThenVisit(CustomResourceSubresourcesFluent subresources) { + if (subresources.hasScale()) { + subresources.editScale().withSpecReplicasPath(path).endScale(); + } else { + subresources.withNewScale().withSpecReplicasPath(path).endScale(); + } + } + + @Override + public Class[] after() { + return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, + AddSubresourcesDecorator.class }; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java new file mode 100644 index 00000000000..e6a920f76c2 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java @@ -0,0 +1,44 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; + +public class AddStatusReplicasPathDecorator + extends CustomResourceDefinitionVersionDecorator> { + + private final String path; + + public AddStatusReplicasPathDecorator(String name, String version, String path) { + super(name, version); + this.path = path; + } + + @Override + public void andThenVisit(CustomResourceSubresourcesFluent subresources) { + if (subresources.hasScale()) { + subresources.editScale().withStatusReplicasPath(path).endScale(); + } else { + subresources.withNewScale().withStatusReplicasPath(path).endScale(); + } + } + + @Override + public Class[] after() { + return new Class[] { AddSubresourcesDecorator.class }; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java new file mode 100644 index 00000000000..1636df8f225 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java @@ -0,0 +1,43 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; + +public class AddStatusSubresourceDecorator + extends CustomResourceDefinitionVersionDecorator> { + + public AddStatusSubresourceDecorator(String name, String version) { + super(name, version); + } + + @Override + public void andThenVisit(CustomResourceSubresourcesFluent subresources) { + subresources.withNewStatus().endStatus(); + } + + @Override + public Class[] after() { + return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, + AddSubresourcesDecorator.class }; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java new file mode 100644 index 00000000000..ae092e2c07e --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java @@ -0,0 +1,44 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; + +public class AddSubresourcesDecorator + extends CustomResourceDefinitionVersionDecorator> { + + public AddSubresourcesDecorator(String name, String version) { + super(name, version); + } + + @Override + public void andThenVisit(CustomResourceDefinitionVersionFluent spec) { + if (!spec.hasSubresources()) { + spec.withNewSubresources().endSubresources(); + } + } + + @Override + public Class[] after() { + return new Class[] { AddCustomResourceDefinitionVersionDecorator.class }; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java new file mode 100644 index 00000000000..0ffb7d4db60 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java @@ -0,0 +1,30 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.NamedResourceDecorator; +import io.fabric8.kubernetes.api.model.ObjectMeta; + +public class CustomResourceDefinitionDecorator extends NamedResourceDecorator { + + public CustomResourceDefinitionDecorator(String name) { + super("CustomResourceDefinition", name); + } + + @Override + public void andThenVisit(T item, ObjectMeta resourceMeta) { + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java new file mode 100644 index 00000000000..d12584b69cd --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java @@ -0,0 +1,133 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.crdv2.generator.utils.Generics; +import io.fabric8.kubernetes.api.builder.TypedVisitor; +import io.fabric8.kubernetes.api.builder.VisitableBuilder; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionBuilder; +import io.fabric8.kubernetes.client.utils.Utils; + +import java.util.Optional; + +import static io.fabric8.crdv2.generator.utils.Metadata.getMetadata; + +public abstract class CustomResourceDefinitionVersionDecorator extends Decorator { + + protected static final String ANY = null; + + private final String name; + private final String version; + + private final CustomResourceDefinitionVersionVisitor versionSelector = new CustomResourceDefinitionVersionVisitor(); + private final VersionVisitor versionVisitor = new VersionVisitor(); + + public CustomResourceDefinitionVersionDecorator(String name, String version) { + this.name = name; + this.version = version; + } + + public String getName() { + return this.name; + } + + public String getVersion() { + return this.version; + } + + @Override + public void visit(VisitableBuilder builder) { + Optional objectMeta = getMetadata(builder); + if (!objectMeta.isPresent()) { + return; + } + if (Utils.isNullOrEmpty(name) || objectMeta.map(ObjectMeta::getName).filter(s -> s.equals(name)).isPresent()) { + builder.accept(versionSelector); + } + } + + public abstract void andThenVisit(T version); + + private class CustomResourceDefinitionVersionVisitor extends TypedVisitor { + + @Override + public void visit(CustomResourceDefinitionVersionBuilder builder) { + if (Utils.isNullOrEmpty(version) || builder.getName().equals(version)) { + builder.accept(versionVisitor); + } + } + } + + private class VersionVisitor extends TypedVisitor { + + @Override + public void visit(T version) { + andThenVisit(version); + } + + public Class getType() { + return (Class) Generics + .getTypeArguments(CustomResourceDefinitionVersionDecorator.class, + CustomResourceDefinitionVersionDecorator.this.getClass()) + .get(0); + } + } + + @Override + public Class[] after() { + return new Class[] { + AddCustomResourceDefinitionResourceDecorator.class, + AddCustomResourceDefinitionVersionDecorator.class }; + } + + @Override + public Class[] before() { + return new Class[] {}; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((version == null) ? 0 : version.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CustomResourceDefinitionVersionDecorator other = (CustomResourceDefinitionVersionDecorator) obj; + if (name == null) { + if (other.name != null) + return false; + } else if (!name.equals(other.name)) + return false; + if (version == null) { + if (other.version != null) + return false; + } else if (!version.equals(other.version)) + return false; + return true; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java new file mode 100644 index 00000000000..fb787ba31ab --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java @@ -0,0 +1,61 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.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 java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class EnsureSingleStorageVersionDecorator + extends CustomResourceDefinitionDecorator> { + + public EnsureSingleStorageVersionDecorator(String name) { + super(name); + } + + @Override + public void andThenVisit(CustomResourceDefinitionSpecFluent spec, ObjectMeta resourceMeta) { + List versions = spec.buildVersions(); + + List storageVersions = versions.stream() + .filter(v -> Optional.ofNullable(v.getStorage()).orElse(true)) + .map(CustomResourceDefinitionVersion::getName) + .collect(Collectors.toList()); + + if (storageVersions.size() > 1) { + throw new IllegalStateException(String.format( + "'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.", + resourceMeta.getName(), storageVersions)); + } + } + + @Override + public Class[] after() { + return new Class[] { + CustomResourceDefinitionVersionDecorator.class + }; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java new file mode 100644 index 00000000000..a32183066d5 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java @@ -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.crdv2.generator.v1.decorator; + +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; + +public class SetDeprecatedVersionDecorator + extends CustomResourceDefinitionVersionDecorator> { + + private final boolean deprecated; + private final String deprecationWarning; + + public SetDeprecatedVersionDecorator(String name, String version, boolean deprecated, String deprecationWarning) { + super(name, version); + this.deprecated = deprecated; + this.deprecationWarning = deprecationWarning; + } + + @Override + public void andThenVisit(CustomResourceDefinitionVersionFluent version) { + if (deprecated) { + version.withDeprecated(true); + version.withDeprecationWarning(deprecationWarning); + } + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + + ", deprecated:" + deprecated + ", deprecationWarning:" + deprecationWarning + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java new file mode 100644 index 00000000000..51c25a8c972 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java @@ -0,0 +1,39 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; + +public class SetServedVersionDecorator + extends CustomResourceDefinitionVersionDecorator> { + + private final boolean served; + + public SetServedVersionDecorator(String name, String version, boolean served) { + super(name, version); + this.served = served; + } + + @Override + public void andThenVisit(CustomResourceDefinitionVersionFluent version) { + version.withServed(served); + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + ", served:" + served + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java new file mode 100644 index 00000000000..0e12aac7cf9 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java @@ -0,0 +1,39 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; + +public class SetStorageVersionDecorator + extends CustomResourceDefinitionVersionDecorator> { + + private final boolean storage; + + public SetStorageVersionDecorator(String name, String version, boolean storage) { + super(name, version); + this.storage = storage; + } + + @Override + public void andThenVisit(CustomResourceDefinitionVersionFluent version) { + version.withStorage(storage); + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + ", storage:" + storage + "]"; + } +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java new file mode 100644 index 00000000000..0ab0c9776fe --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java @@ -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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.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> { + 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[] after() { + return new Class[] { EnsureSingleStorageVersionDecorator.class }; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + "]"; + } + +} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java new file mode 100644 index 00000000000..942483fd8da --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java @@ -0,0 +1,81 @@ +/* + * 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.crdv2.generator.v1.decorator; + +import io.fabric8.crdv2.generator.decorator.Decorator; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceColumnDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; + +import java.util.List; + +public class SortPrinterColumnsDecorator + extends CustomResourceDefinitionVersionDecorator> { + + public SortPrinterColumnsDecorator(String name, String version) { + super(name, version); + } + + @Override + public void andThenVisit(CustomResourceDefinitionVersionFluent version) { + List columns = version.buildAdditionalPrinterColumns(); + if (columns != null && !columns.isEmpty()) { + columns.sort((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getJsonPath(), o2.getJsonPath())); + } + version.withAdditionalPrinterColumns(columns); + } + + @Override + public Class[] after() { + return new Class[] { CustomResourceDefinitionVersionDecorator.class, AddAdditionPrinterColumnDecorator.class }; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); + result = prime * result + ((getVersion() == null) ? 0 : getVersion().hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + CustomResourceDefinitionVersionDecorator other = (CustomResourceDefinitionVersionDecorator) obj; + if (getName() == null) { + if (other.getName() != null) + return false; + } else if (!getName().equals(other.getName())) + return false; + if (getVersion() == null) { + if (other.getVersion() != null) + return false; + } else if (!getVersion().equals(other.getVersion())) + return false; + return true; + } + + @Override + public String toString() { + return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/Annotated.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/Annotated.java new file mode 100644 index 00000000000..47be09ae621 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/Annotated.java @@ -0,0 +1,22 @@ +/* + * 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.crdv2.example.annotated; + +import io.fabric8.kubernetes.client.CustomResource; + +public class Annotated extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java new file mode 100644 index 00000000000..39b77d0a5fd --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java @@ -0,0 +1,183 @@ +/* + * 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.crdv2.example.annotated; + +import com.fasterxml.jackson.annotation.JsonFormat; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import io.fabric8.generator.annotation.Default; +import io.fabric8.generator.annotation.Max; +import io.fabric8.generator.annotation.Min; +import io.fabric8.generator.annotation.Nullable; +import io.fabric8.generator.annotation.Pattern; +import io.fabric8.generator.annotation.Required; +import io.fabric8.generator.annotation.ValidationRule; +import lombok.Data; + +import java.time.ZonedDateTime; + +@Data +public class AnnotatedSpec { + @JsonProperty("from-field") + @JsonPropertyDescription("from-field-description") + private String field; + private int foo; + @JsonProperty + private String unnamed; + private int min; + private int max; + private String singleDigit; + private String nullable; + private String defaultValue; + @Default("my-value2") + private String defaultValue2; + @Required + private boolean emptySetter; + @Required + private boolean emptySetter2; + private AnnotatedEnum anEnum; + @javax.validation.constraints.Min(0) // a non-string value attribute + private int sizedField; + private String bool; + private String num; + private String numInt; + private String numFloat; + private ZonedDateTime issuedAt; + + @JsonIgnore + private int ignoredFoo; + + private boolean ignoredBar; + + @ValidationRule(value = "self.startwith('prefix-')", message = "kubernetesValidationRule must start with prefix 'prefix-'") + private String kubernetesValidationRule; + + @ValidationRule("first.rule") + @ValidationRule("second.rule") + @ValidationRule(value = "third.rule", reason = "FieldValueForbidden") + private String kubernetesValidationRules; + + @JsonProperty("from-getter") + @JsonPropertyDescription("from-getter-description") + @Required + public int getFoo() { + return foo; + } + + public int getIgnoredFoo() { + return ignoredFoo; + } + + @JsonIgnore + public boolean getIgnoredBar() { + return ignoredBar; + } + + @Max(5.0) + public int getMax() { + return 1; + } + + @Min(-5) + public int getMin() { + return 1; + } + + @Pattern("\\b[1-9]\\b") + public String getSingleDigit() { + return "1"; + } + + @Nullable + public String getNullable() { + return null; + } + + @Default("my-value") + public String getDefaultValue() { + return "foo"; + } + + @JsonProperty + public void setEmptySetter(boolean emptySetter) { + this.emptySetter = emptySetter; + } + + @JsonProperty + public void setEmptySetter2(boolean emptySetter2) { + this.emptySetter2 = emptySetter2; + } + + @JsonFormat(shape = JsonFormat.Shape.BOOLEAN) + public String getBool() { + return bool; + } + + @JsonFormat(shape = JsonFormat.Shape.NUMBER) + public String getNum() { + return num; + } + + @JsonFormat(shape = JsonFormat.Shape.NUMBER_FLOAT) + public String getNumFloat() { + return numFloat; + } + + @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) + public String getNumInt() { + return numInt; + } + + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ssVV") + public java.time.ZonedDateTime getIssuedAt() { + return issuedAt; + } + + public enum AnnotatedEnum { + non("N"), + @JsonProperty("oui") + es("O"), + @JsonIgnore + Maybe("Maybe"); + + private final String abbreviation; + + AnnotatedEnum(String abbreviation) { + this.abbreviation = abbreviation; + } + + public String getAbbreviation() { + return abbreviation; + } + + public static AnnotatedEnum SIM = es; + + public AnotherEnum one = AnotherEnum.ONE; + + public AnotherEnum getOne() { + return one; + } + + public void setOne(AnotherEnum one) { + this.one = one; + } + } + + public enum AnotherEnum { + ONE + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/Basic.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/Basic.java new file mode 100644 index 00000000000..578efa9377b --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/Basic.java @@ -0,0 +1,27 @@ +/* + * 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.crdv2.example.basic; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v1alpha1") +public class Basic extends CustomResource implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicSpec.java new file mode 100644 index 00000000000..3784b2cc57e --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicSpec.java @@ -0,0 +1,63 @@ +/* + * 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.crdv2.example.basic; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +public class BasicSpec { + private int myInt; + + public int getMyInt() { + return myInt; + } + + public void setMyInt(int myInt) { + this.myInt = myInt; + } + + private long myLong; + + public long getMyLong() { + return myLong; + } + + public void setMyLong(long myLong) { + this.myLong = myLong; + } + + private double myDouble; + + public double getMyDouble() { + return myDouble; + } + + public void setMyDouble(long myDouble) { + this.myDouble = myDouble; + } + + private float myFloat; + + public float getMyFloat() { + return myFloat; + } + + public void setMyFloat(long myFloat) { + this.myFloat = myFloat; + } + + @JsonIgnore + public Class clazz; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicStatus.java new file mode 100644 index 00000000000..1f5d2484b61 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/basic/BasicStatus.java @@ -0,0 +1,28 @@ +/* + * 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.crdv2.example.basic; + +public class BasicStatus { + private String message; + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/Complex.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/Complex.java new file mode 100644 index 00000000000..607711e2dd6 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/Complex.java @@ -0,0 +1,28 @@ +/* + * 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.crdv2.example.complex; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Kind; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("example.com") +@Version("v1") +@Kind("ComplexKind") +public class Complex extends CustomResource implements Namespaced { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexSpec.java new file mode 100644 index 00000000000..fa126870446 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexSpec.java @@ -0,0 +1,79 @@ +/* + * 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.crdv2.example.complex; + +import java.util.ArrayList; +import java.util.List; + +@SuppressWarnings("LombokGetterMayBeUsed") +public class ComplexSpec { + private StatefulSetConfiguration statefulSet = new StatefulSetConfiguration(); + private List services = new ArrayList<>(); + + private String configMapName = "example-configuration"; + + private int actuatorPort; + private int metricsPort; + private String metricsPath = "/"; + + public StatefulSetConfiguration getStatefulSet() { + return statefulSet; + } + + public void setStatefulSet(StatefulSetConfiguration statefulSet) { + this.statefulSet = statefulSet; + } + + public List getServices() { + return services; + } + + public void setServices(List services) { + this.services = services; + } + + public String getConfigMapName() { + return configMapName; + } + + public void setConfigMapName(String configMapName) { + this.configMapName = configMapName; + } + + public int getActuatorPort() { + return actuatorPort; + } + + public void setActuatorPort(int actuatorPort) { + this.actuatorPort = actuatorPort; + } + + public int getMetricsPort() { + return metricsPort; + } + + public void setMetricsPort(int metricsPort) { + this.metricsPort = metricsPort; + } + + public String getMetricsPath() { + return metricsPath; + } + + public void setMetricsPath(String metricsPath) { + this.metricsPath = metricsPath; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java new file mode 100644 index 00000000000..daa35af8451 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java @@ -0,0 +1,61 @@ +/* + * 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.crdv2.example.complex; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.fabric8.crd.generator.annotation.PrinterColumn; + +@SuppressWarnings("LombokGetterMayBeUsed") +public class ComplexStatus { + + public enum State { + CREATED, + STARTING, + RUNNING, + ROLLING_UPDATE, + SCALING, + ERROR + } + + public ComplexStatus() { + this.state = State.CREATED; + this.message = "Deployment was created"; + } + + @JsonProperty("state") + @PrinterColumn(name = "State") + private State state; + + @JsonProperty("message") + @PrinterColumn(name = "Message") + private String message; + + public State getState() { + return state; + } + + public void setState(final State state) { + this.state = state; + } + + public String getMessage() { + return message; + } + + public void setMessage(final String message) { + this.message = message; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ServiceConfiguration.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ServiceConfiguration.java new file mode 100644 index 00000000000..fc205a7f455 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ServiceConfiguration.java @@ -0,0 +1,53 @@ +/* + * 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.crdv2.example.complex; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import io.fabric8.crdv2.example.complex.k8s.ObjectMeta; +import io.fabric8.crdv2.example.complex.k8s.ServiceSpec; +import io.fabric8.generator.annotation.Nullable; + +@SuppressWarnings("LombokGetterMayBeUsed") +public class ServiceConfiguration { + + @JsonProperty("metadata") + @JsonPropertyDescription("The metadata of this Service") + private ObjectMeta metadata = new ObjectMeta(); + + @JsonProperty("spec") + @JsonPropertyDescription("The spec of this Service") + private @Nullable ServiceSpec spec; + + public ServiceConfiguration() { + } + + public ObjectMeta getMetadata() { + return metadata; + } + + public void setMetadata(final ObjectMeta metadata) { + this.metadata = metadata; + } + + public @Nullable ServiceSpec getSpec() { + return spec; + } + + public void setSpec(final ServiceSpec spec) { + this.spec = spec; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/StatefulSetConfiguration.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/StatefulSetConfiguration.java new file mode 100644 index 00000000000..b36ccce05b7 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/StatefulSetConfiguration.java @@ -0,0 +1,52 @@ +/* + * 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.crdv2.example.complex; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import io.fabric8.crdv2.example.complex.k8s.ObjectMeta; +import io.fabric8.crdv2.example.complex.k8s.StatefulSetSpec; + +@SuppressWarnings("LombokGetterMayBeUsed") +public class StatefulSetConfiguration { + + @JsonProperty("metadata") + @JsonPropertyDescription("The metadata of this StatefulSet") + private ObjectMeta metadata = new ObjectMeta(); + + @JsonProperty("spec") + @JsonPropertyDescription("The spec of this StatefulSet") + private StatefulSetSpec spec = new StatefulSetSpec(); + + public StatefulSetConfiguration() { + } + + public ObjectMeta getMetadata() { + return metadata; + } + + public void setMetadata(final ObjectMeta metadata) { + this.metadata = metadata; + } + + public StatefulSetSpec getSpec() { + return spec; + } + + public void setSpec(final StatefulSetSpec spec) { + this.spec = spec; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ObjectMeta.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ObjectMeta.java new file mode 100644 index 00000000000..13a1fff379a --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ObjectMeta.java @@ -0,0 +1,232 @@ +/* + * 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.crdv2.example.complex.k8s; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.fabric8.kubernetes.api.model.KubernetesResource; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Simplified version of the K8s ObjectMeta. + * + * The purpose of this class is to create a complex, but stable CRD, that doesn't change when the generated ObjectMeta class is + * changed. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "apiVersion", + "kind", + "metadata", + "annotations", + "creationTimestamp", + "deletionGracePeriodSeconds", + "deletionTimestamp", + "finalizers", + "generateName", + "generation", + "labels", + "name", + "namespace", + "resourceVersion", + "selfLink", + "uid" +}) +public class ObjectMeta implements KubernetesResource { + + @JsonProperty("annotations") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map annotations = new LinkedHashMap<>(); + @JsonProperty("creationTimestamp") + private String creationTimestamp; + @JsonProperty("deletionGracePeriodSeconds") + private Long deletionGracePeriodSeconds; + @JsonProperty("deletionTimestamp") + private String deletionTimestamp; + @JsonProperty("finalizers") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List finalizers = new ArrayList<>(); + @JsonProperty("generateName") + private String generateName; + @JsonProperty("generation") + private Long generation; + @JsonProperty("labels") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map labels = new LinkedHashMap<>(); + @JsonProperty("name") + private String name; + @JsonProperty("namespace") + private String namespace; + @JsonProperty("resourceVersion") + private String resourceVersion; + @JsonProperty("selfLink") + private String selfLink; + @JsonProperty("uid") + private String uid; + @JsonIgnore + private final Map additionalProperties = new LinkedHashMap<>(); + + public ObjectMeta() { + } + + @JsonProperty("annotations") + public Map getAnnotations() { + return annotations; + } + + @JsonProperty("annotations") + public void setAnnotations(Map annotations) { + this.annotations = annotations; + } + + @JsonProperty("creationTimestamp") + public String getCreationTimestamp() { + return creationTimestamp; + } + + @JsonProperty("creationTimestamp") + public void setCreationTimestamp(String creationTimestamp) { + this.creationTimestamp = creationTimestamp; + } + + @JsonProperty("deletionGracePeriodSeconds") + public Long getDeletionGracePeriodSeconds() { + return deletionGracePeriodSeconds; + } + + @JsonProperty("deletionGracePeriodSeconds") + public void setDeletionGracePeriodSeconds(Long deletionGracePeriodSeconds) { + this.deletionGracePeriodSeconds = deletionGracePeriodSeconds; + } + + @JsonProperty("deletionTimestamp") + public String getDeletionTimestamp() { + return deletionTimestamp; + } + + @JsonProperty("deletionTimestamp") + public void setDeletionTimestamp(String deletionTimestamp) { + this.deletionTimestamp = deletionTimestamp; + } + + @JsonProperty("finalizers") + public List getFinalizers() { + return finalizers; + } + + @JsonProperty("finalizers") + public void setFinalizers(List finalizers) { + this.finalizers = finalizers; + } + + @JsonProperty("generateName") + public String getGenerateName() { + return generateName; + } + + @JsonProperty("generateName") + public void setGenerateName(String generateName) { + this.generateName = generateName; + } + + @JsonProperty("generation") + public Long getGeneration() { + return generation; + } + + @JsonProperty("generation") + public void setGeneration(Long generation) { + this.generation = generation; + } + + @JsonProperty("labels") + public Map getLabels() { + return labels; + } + + @JsonProperty("labels") + public void setLabels(Map labels) { + this.labels = labels; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + @JsonProperty("namespace") + public String getNamespace() { + return namespace; + } + + @JsonProperty("namespace") + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + @JsonProperty("resourceVersion") + public String getResourceVersion() { + return resourceVersion; + } + + @JsonProperty("resourceVersion") + public void setResourceVersion(String resourceVersion) { + this.resourceVersion = resourceVersion; + } + + @JsonProperty("selfLink") + public String getSelfLink() { + return selfLink; + } + + @JsonProperty("selfLink") + public void setSelfLink(String selfLink) { + this.selfLink = selfLink; + } + + @JsonProperty("uid") + public String getUid() { + return uid; + } + + @JsonProperty("uid") + public void setUid(String uid) { + this.uid = uid; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ServiceSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ServiceSpec.java new file mode 100644 index 00000000000..586cf675e08 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/ServiceSpec.java @@ -0,0 +1,286 @@ +/* + * 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.crdv2.example.complex.k8s; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.fabric8.kubernetes.api.model.KubernetesResource; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Simplified version of the K8s ServiceSpec. + * + * The purpose of this class is to create a complex, but stable CRD, that doesn't change when the generated ServiceSpec class is + * changed. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "apiVersion", + "kind", + "metadata", + "allocateLoadBalancerNodePorts", + "clusterIP", + "clusterIPs", + "externalIPs", + "externalName", + "externalTrafficPolicy", + "healthCheckNodePort", + "internalTrafficPolicy", + "ipFamilies", + "ipFamilyPolicy", + "loadBalancerClass", + "loadBalancerIP", + "loadBalancerSourceRanges", + "publishNotReadyAddresses", + "selector", + "sessionAffinityConfig", + "type" +}) +public class ServiceSpec implements KubernetesResource { + + @JsonProperty("allocateLoadBalancerNodePorts") + private Boolean allocateLoadBalancerNodePorts; + @JsonProperty("clusterIP") + private String clusterIP; + @JsonProperty("clusterIPs") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List clusterIPs = new ArrayList<>(); + @JsonProperty("externalIPs") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List externalIPs = new ArrayList<>(); + @JsonProperty("externalName") + private String externalName; + @JsonProperty("externalTrafficPolicy") + private String externalTrafficPolicy; + @JsonProperty("healthCheckNodePort") + private Integer healthCheckNodePort; + @JsonProperty("internalTrafficPolicy") + private String internalTrafficPolicy; + @JsonProperty("ipFamilies") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List ipFamilies = new ArrayList<>(); + @JsonProperty("ipFamilyPolicy") + private String ipFamilyPolicy; + @JsonProperty("loadBalancerClass") + private String loadBalancerClass; + @JsonProperty("loadBalancerIP") + private String loadBalancerIP; + @JsonProperty("loadBalancerSourceRanges") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List loadBalancerSourceRanges = new ArrayList<>(); + @JsonProperty("publishNotReadyAddresses") + private Boolean publishNotReadyAddresses; + @JsonProperty("selector") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Map selector = new LinkedHashMap<>(); + @JsonProperty("sessionAffinity") + private String sessionAffinity; + @JsonProperty("type") + private String type; + @JsonIgnore + private final Map additionalProperties = new LinkedHashMap<>(); + + public ServiceSpec() { + } + + @JsonProperty("allocateLoadBalancerNodePorts") + public Boolean getAllocateLoadBalancerNodePorts() { + return allocateLoadBalancerNodePorts; + } + + @JsonProperty("allocateLoadBalancerNodePorts") + public void setAllocateLoadBalancerNodePorts(Boolean allocateLoadBalancerNodePorts) { + this.allocateLoadBalancerNodePorts = allocateLoadBalancerNodePorts; + } + + @JsonProperty("clusterIP") + public String getClusterIP() { + return clusterIP; + } + + @JsonProperty("clusterIP") + public void setClusterIP(String clusterIP) { + this.clusterIP = clusterIP; + } + + @JsonProperty("clusterIPs") + public List getClusterIPs() { + return clusterIPs; + } + + @JsonProperty("clusterIPs") + public void setClusterIPs(List clusterIPs) { + this.clusterIPs = clusterIPs; + } + + @JsonProperty("externalIPs") + public List getExternalIPs() { + return externalIPs; + } + + @JsonProperty("externalIPs") + public void setExternalIPs(List externalIPs) { + this.externalIPs = externalIPs; + } + + @JsonProperty("externalName") + public String getExternalName() { + return externalName; + } + + @JsonProperty("externalName") + public void setExternalName(String externalName) { + this.externalName = externalName; + } + + @JsonProperty("externalTrafficPolicy") + public String getExternalTrafficPolicy() { + return externalTrafficPolicy; + } + + @JsonProperty("externalTrafficPolicy") + public void setExternalTrafficPolicy(String externalTrafficPolicy) { + this.externalTrafficPolicy = externalTrafficPolicy; + } + + @JsonProperty("healthCheckNodePort") + public Integer getHealthCheckNodePort() { + return healthCheckNodePort; + } + + @JsonProperty("healthCheckNodePort") + public void setHealthCheckNodePort(Integer healthCheckNodePort) { + this.healthCheckNodePort = healthCheckNodePort; + } + + @JsonProperty("internalTrafficPolicy") + public String getInternalTrafficPolicy() { + return internalTrafficPolicy; + } + + @JsonProperty("internalTrafficPolicy") + public void setInternalTrafficPolicy(String internalTrafficPolicy) { + this.internalTrafficPolicy = internalTrafficPolicy; + } + + @JsonProperty("ipFamilies") + public List getIpFamilies() { + return ipFamilies; + } + + @JsonProperty("ipFamilies") + public void setIpFamilies(List ipFamilies) { + this.ipFamilies = ipFamilies; + } + + @JsonProperty("ipFamilyPolicy") + public String getIpFamilyPolicy() { + return ipFamilyPolicy; + } + + @JsonProperty("ipFamilyPolicy") + public void setIpFamilyPolicy(String ipFamilyPolicy) { + this.ipFamilyPolicy = ipFamilyPolicy; + } + + @JsonProperty("loadBalancerClass") + public String getLoadBalancerClass() { + return loadBalancerClass; + } + + @JsonProperty("loadBalancerClass") + public void setLoadBalancerClass(String loadBalancerClass) { + this.loadBalancerClass = loadBalancerClass; + } + + @JsonProperty("loadBalancerIP") + public String getLoadBalancerIP() { + return loadBalancerIP; + } + + @JsonProperty("loadBalancerIP") + public void setLoadBalancerIP(String loadBalancerIP) { + this.loadBalancerIP = loadBalancerIP; + } + + @JsonProperty("loadBalancerSourceRanges") + public List getLoadBalancerSourceRanges() { + return loadBalancerSourceRanges; + } + + @JsonProperty("loadBalancerSourceRanges") + public void setLoadBalancerSourceRanges(List loadBalancerSourceRanges) { + this.loadBalancerSourceRanges = loadBalancerSourceRanges; + } + + @JsonProperty("publishNotReadyAddresses") + public Boolean getPublishNotReadyAddresses() { + return publishNotReadyAddresses; + } + + @JsonProperty("publishNotReadyAddresses") + public void setPublishNotReadyAddresses(Boolean publishNotReadyAddresses) { + this.publishNotReadyAddresses = publishNotReadyAddresses; + } + + @JsonProperty("selector") + public Map getSelector() { + return selector; + } + + @JsonProperty("selector") + public void setSelector(Map selector) { + this.selector = selector; + } + + @JsonProperty("sessionAffinity") + public String getSessionAffinity() { + return sessionAffinity; + } + + @JsonProperty("sessionAffinity") + public void setSessionAffinity(String sessionAffinity) { + this.sessionAffinity = sessionAffinity; + } + + @JsonProperty("type") + public String getType() { + return type; + } + + @JsonProperty("type") + public void setType(String type) { + this.type = type; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/StatefulSetSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/StatefulSetSpec.java new file mode 100644 index 00000000000..488154ec429 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/k8s/StatefulSetSpec.java @@ -0,0 +1,126 @@ +/* + * 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.crdv2.example.complex.k8s; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import io.fabric8.kubernetes.api.model.KubernetesResource; + +import java.util.LinkedHashMap; +import java.util.Map; + +/** + * Simplified version of the K8s StatefulSetSpec. + * + * The purpose of this class is to create a complex, but stable CRD, that doesn't change when the generated StatefulSetSpec + * class is changed. + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "apiVersion", + "kind", + "metadata", + "minReadySeconds", + "podManagementPolicy", + "replicas", + "revisionHistoryLimit", + "serviceName" +}) +public class StatefulSetSpec implements KubernetesResource { + @JsonProperty("minReadySeconds") + private Integer minReadySeconds; + @JsonProperty("podManagementPolicy") + private String podManagementPolicy; + @JsonProperty("replicas") + private Integer replicas; + @JsonProperty("revisionHistoryLimit") + private Integer revisionHistoryLimit; + @JsonProperty("serviceName") + private String serviceName; + @JsonIgnore + private final Map additionalProperties = new LinkedHashMap<>(); + + /** + * No args constructor for use in serialization + * + */ + public StatefulSetSpec() { + } + + @JsonProperty("minReadySeconds") + public Integer getMinReadySeconds() { + return minReadySeconds; + } + + @JsonProperty("minReadySeconds") + public void setMinReadySeconds(Integer minReadySeconds) { + this.minReadySeconds = minReadySeconds; + } + + @JsonProperty("podManagementPolicy") + public String getPodManagementPolicy() { + return podManagementPolicy; + } + + @JsonProperty("podManagementPolicy") + public void setPodManagementPolicy(String podManagementPolicy) { + this.podManagementPolicy = podManagementPolicy; + } + + @JsonProperty("replicas") + public Integer getReplicas() { + return replicas; + } + + @JsonProperty("replicas") + public void setReplicas(Integer replicas) { + this.replicas = replicas; + } + + @JsonProperty("revisionHistoryLimit") + public Integer getRevisionHistoryLimit() { + return revisionHistoryLimit; + } + + @JsonProperty("revisionHistoryLimit") + public void setRevisionHistoryLimit(Integer revisionHistoryLimit) { + this.revisionHistoryLimit = revisionHistoryLimit; + } + + @JsonProperty("serviceName") + public String getServiceName() { + return serviceName; + } + + @JsonProperty("serviceName") + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Cyclic.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Cyclic.java new file mode 100644 index 00000000000..cf579912d08 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Cyclic.java @@ -0,0 +1,27 @@ +/* + * 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.crdv2.example.cyclic; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v1alpha1") +public class Cyclic extends CustomResource implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicList.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicList.java new file mode 100644 index 00000000000..31ccaf07168 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicList.java @@ -0,0 +1,27 @@ +/* + * 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.crdv2.example.cyclic; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v1alpha1") +public class CyclicList extends CustomResource implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicListSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicListSpec.java new file mode 100644 index 00000000000..156e66f51c5 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicListSpec.java @@ -0,0 +1,22 @@ +/* + * 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.crdv2.example.cyclic; + +import java.util.List; + +public class CyclicListSpec { + public List ref; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicSpec.java new file mode 100644 index 00000000000..f96c8f30a54 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicSpec.java @@ -0,0 +1,20 @@ +/* + * 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.crdv2.example.cyclic; + +public class CyclicSpec { + public Ref ref; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicStatus.java new file mode 100644 index 00000000000..6b171e510e4 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/CyclicStatus.java @@ -0,0 +1,23 @@ +/* + * 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.crdv2.example.cyclic; + +import lombok.Data; + +@Data +public class CyclicStatus { + private String message; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Ref.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Ref.java new file mode 100644 index 00000000000..94323032218 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/Ref.java @@ -0,0 +1,22 @@ +/* + * 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.crdv2.example.cyclic; + +public class Ref { + + public Ref ref; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/RefList.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/RefList.java new file mode 100644 index 00000000000..97baa76d71e --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/cyclic/RefList.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.cyclic; + +import java.util.List; + +public class RefList { + + public List ref; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExample.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExample.java new file mode 100644 index 00000000000..7603ce771ae --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExample.java @@ -0,0 +1,25 @@ +/* + * 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.crdv2.example.deprecated.v1; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version(value = "v1", storage = false, deprecated = true) +public class DeprecationExample extends CustomResource { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExampleSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExampleSpec.java new file mode 100644 index 00000000000..024ef1ae4bb --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1/DeprecationExampleSpec.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.deprecated.v1; + +public class DeprecationExampleSpec { + private String v1; + + public String getV1() { + return v1; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExample.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExample.java new file mode 100644 index 00000000000..d637a94407c --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExample.java @@ -0,0 +1,25 @@ +/* + * 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.crdv2.example.deprecated.v1beta1; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version(value = "v1beta1", storage = false, deprecated = true, deprecationWarning = "sample.fabric8.io/v1beta1 DeprecationExample is deprecated; Migrate to sample.fabric8.io/v2") +public class DeprecationExample extends CustomResource { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExampleSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExampleSpec.java new file mode 100644 index 00000000000..3deec8afa35 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v1beta1/DeprecationExampleSpec.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.deprecated.v1beta1; + +public class DeprecationExampleSpec { + private String v1beta1; + + public String getV1() { + return v1beta1; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExample.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExample.java new file mode 100644 index 00000000000..7387de90f79 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExample.java @@ -0,0 +1,25 @@ +/* + * 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.crdv2.example.deprecated.v2; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v2") +public class DeprecationExample extends CustomResource { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExampleSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExampleSpec.java new file mode 100644 index 00000000000..2911ad92374 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/deprecated/v2/DeprecationExampleSpec.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.deprecated.v2; + +public class DeprecationExampleSpec { + private String v2; + + public String getV2() { + return v2; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CollectionCyclicSchemaSwap.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CollectionCyclicSchemaSwap.java new file mode 100644 index 00000000000..ebd3e4e2cde --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CollectionCyclicSchemaSwap.java @@ -0,0 +1,40 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.kubernetes.api.model.AnyType; +import io.fabric8.kubernetes.client.CustomResource; + +import java.util.List; + +@SchemaSwap(originalType = CollectionCyclicSchemaSwap.Level.class, fieldName = "levels", targetType = AnyType.class, depth = 2) +public class CollectionCyclicSchemaSwap extends CustomResource { + + public static class Spec { + public MyObject myObject; + public List levels; + } + + public static class Level { + public MyObject myObject; + public List levels; + } + + public static class MyObject { + public int value; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CyclicSchemaSwap.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CyclicSchemaSwap.java new file mode 100644 index 00000000000..2044c0efeb2 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/CyclicSchemaSwap.java @@ -0,0 +1,40 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.kubernetes.client.CustomResource; + +import java.util.List; + +@SchemaSwap(originalType = CyclicSchemaSwap.Level.class, fieldName = "level", depth = 1) +public class CyclicSchemaSwap extends CustomResource { + + public static class Spec { + public MyObject myObject; + public Level root; + public List roots; // should not interfere with the rendering depth of level of its sibling + } + + public static class Level { + public MyObject myObject; + public Level level; + } + + public static class MyObject { + public int value; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/DeeplyNestedSchemaSwaps.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/DeeplyNestedSchemaSwaps.java new file mode 100644 index 00000000000..5182cf769df --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/DeeplyNestedSchemaSwaps.java @@ -0,0 +1,49 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.kubernetes.client.CustomResource; + +@SchemaSwap(originalType = DeeplyNestedSchemaSwaps.MyObject.class, fieldName = "shouldBeString", targetType = String.class) +public class DeeplyNestedSchemaSwaps extends CustomResource { + + public static class Spec { + public MyObject myObject; + public Level1 level1; + } + + private static class Level1 { + public Level2 level2a; + public MyObject myObject; + public Level2 level2b; + } + + private static class Level2 { + public MyObject myObject1; + public Level3 level3; + public MyObject myObject2; + } + + private static class Level3 { + public MyObject myObject1; + public MyObject myObject2; + } + + public static class MyObject { + public int shouldBeString; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Extraction.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Extraction.java new file mode 100644 index 00000000000..11564e37750 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Extraction.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.kubernetes.client.CustomResource; + +@SchemaSwap(originalType = ExtractionSpec.class, fieldName = "bar", targetType = FooExtractor.class) +public class Extraction extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/ExtractionSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/ExtractionSpec.java new file mode 100644 index 00000000000..4592a6a2049 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/ExtractionSpec.java @@ -0,0 +1,29 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.PreserveUnknownFields; +import io.fabric8.crd.generator.annotation.SchemaFrom; + +public class ExtractionSpec { + + @SchemaFrom(type = FooExtractor.class) + public Foo foo; + + @PreserveUnknownFields + public Foo bar; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Foo.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Foo.java new file mode 100644 index 00000000000..1fab59e0142 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/Foo.java @@ -0,0 +1,29 @@ +/* + * 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.crdv2.example.extraction; + +import com.fasterxml.jackson.annotation.JsonAlias; + +import java.util.Optional; + +public class Foo { + + @JsonAlias({ "BAZ" }) + public Optional bar; + + public String baz; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/FooExtractor.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/FooExtractor.java new file mode 100644 index 00000000000..fb35493c9c5 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/FooExtractor.java @@ -0,0 +1,27 @@ +/* + * 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.crdv2.example.extraction; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.fabric8.generator.annotation.Required; + +public class FooExtractor { + + @JsonProperty("BAZ") + @Required + public int bar; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction.java new file mode 100644 index 00000000000..e9109f33161 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.kubernetes.client.CustomResource; + +@SchemaSwap(originalType = ExtractionSpec.class, fieldName = "FOO", targetType = FooExtractor.class) +public class IncorrectExtraction extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction2.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction2.java new file mode 100644 index 00000000000..5d877ba7d0a --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/IncorrectExtraction2.java @@ -0,0 +1,25 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.crdv2.example.basic.BasicSpec; +import io.fabric8.kubernetes.client.CustomResource; + +@SchemaSwap(originalType = BasicSpec.class, fieldName = "bar", targetType = FooExtractor.class) +public class IncorrectExtraction2 extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/MultipleSchemaSwaps.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/MultipleSchemaSwaps.java new file mode 100644 index 00000000000..0d0d226e41e --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/MultipleSchemaSwaps.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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.kubernetes.client.CustomResource; + +@SchemaSwap(originalType = SchemaSwapSpec.SomeObject.class, fieldName = "shouldBeString", targetType = String.class) +@SchemaSwap(originalType = SchemaSwapSpec.AnotherObject.class, fieldName = "shouldBeInt", targetType = Integer.class) +@SchemaSwap(originalType = SchemaSwapSpec.YetAnotherObject.class, fieldName = "shouldBeSkipped") +public class MultipleSchemaSwaps extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/NestedSchemaSwap.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/NestedSchemaSwap.java new file mode 100644 index 00000000000..d8ca9a74dd0 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/NestedSchemaSwap.java @@ -0,0 +1,37 @@ +/* + * 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.crdv2.example.extraction; + +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.kubernetes.client.CustomResource; + +public class NestedSchemaSwap extends CustomResource { + + @SchemaSwap(originalType = End.class, fieldName = "value", targetType = String.class) + public static class Spec { + public Intermediate one; + public Intermediate another; + } + + @SchemaSwap(originalType = End.class, fieldName = "value", targetType = Void.class) + public static class Intermediate { + public End one; + } + + public static class End { + public int value; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/SchemaSwapSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/SchemaSwapSpec.java new file mode 100644 index 00000000000..a42b0f16b8c --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/extraction/SchemaSwapSpec.java @@ -0,0 +1,35 @@ +/* + * 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.crdv2.example.extraction; + +public class SchemaSwapSpec { + public SomeObject first; + public SomeObject second; + public AnotherObject third; + public YetAnotherObject fourth; + + static class SomeObject { + public int shouldBeString; + } + + static class AnotherObject { + public String shouldBeInt; + } + + static class YetAnotherObject { + public String shouldBeSkipped; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Base.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Base.java new file mode 100644 index 00000000000..e19390d36ef --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Base.java @@ -0,0 +1,25 @@ +/* + * 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.crdv2.example.inherited; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; + +public abstract class Base + extends CustomResource + implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseSpec.java new file mode 100644 index 00000000000..df0ae3544ae --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseSpec.java @@ -0,0 +1,28 @@ +/* + * 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.crdv2.example.inherited; + +public class BaseSpec { + private int baseInt; + + public int getBaseInt() { + return baseInt; + } + + public void setBaseInt(int baseInt) { + this.baseInt = baseInt; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseStatus.java new file mode 100644 index 00000000000..65a2a63ab22 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/BaseStatus.java @@ -0,0 +1,23 @@ +/* + * 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.crdv2.example.inherited; + +/** + * @author Christophe Laprun + */ +public class BaseStatus { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Child.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Child.java new file mode 100644 index 00000000000..a58f8a8b5a5 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/Child.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.crdv2.example.inherited; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Version("v1alpha1") +@Group("acme.com") +public class Child extends Base implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildSpec.java new file mode 100644 index 00000000000..5a408d51990 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildSpec.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.inherited; + +import java.util.Map; + +public class ChildSpec extends BaseSpec { + public Map unsupported; + public Map supported; + public Map unsupported2; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildStatus.java new file mode 100644 index 00000000000..39013bd7df4 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/inherited/ChildStatus.java @@ -0,0 +1,23 @@ +/* + * 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.crdv2.example.inherited; + +/** + * @author Christophe Laprun + */ +public class ChildStatus extends BaseStatus { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/Joke.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/Joke.java new file mode 100644 index 00000000000..a92b0d069ea --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/Joke.java @@ -0,0 +1,82 @@ +/* + * 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.crdv2.example.joke; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonInclude.Include; +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("samples.javaoperatorsdk.io") +@Version("v1alpha1") +@JsonInclude(Include.NON_NULL) +public class Joke extends CustomResource implements Namespaced { + private String joke; + private String category; + private boolean safe; + private String lang; + private int id; + + public Joke() { + } + + public Joke(int id, String joke, String category, boolean safe, String lang) { + this.id = id; + getMetadata().setName("" + id); + this.joke = joke; + this.category = category; + this.safe = safe; + this.lang = lang; + } + + public int getId() { + return id; + } + + public String getJoke() { + return joke; + } + + public void setJoke(String joke) { + this.joke = joke; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public boolean isSafe() { + return safe; + } + + public void setSafe(boolean safe) { + this.safe = safe; + } + + public String getLang() { + return lang; + } + + public void setLang(String lang) { + this.lang = lang; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequest.java new file mode 100644 index 00000000000..6dd72d30d7d --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequest.java @@ -0,0 +1,29 @@ +/* + * 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.crdv2.example.joke; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("samples.javaoperatorsdk.io") +@Version("v1alpha1") +@ShortNames("jr") +public class JokeRequest extends CustomResource implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestSpec.java new file mode 100644 index 00000000000..d8ed0f6e134 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestSpec.java @@ -0,0 +1,73 @@ +/* + * 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.crdv2.example.joke; + +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import io.fabric8.crd.generator.annotation.PrinterColumn; + +public class JokeRequestSpec { + + public enum Category { + Any, + Misc, + Programming, + Dark, + Pun, + Spooky, + Christmas + } + + public enum ExcludedTopic { + nsfw, + religious, + political, + racist, + sexist, + explicit + } + + @PrinterColumn(name = "jokeCategory", priority = 1) + @JsonPropertyDescription("category-description") + private Category category = Category.Any; + @PrinterColumn(name = "excludedTopics") + private ExcludedTopic[] excluded = new ExcludedTopic[] { ExcludedTopic.nsfw, ExcludedTopic.racist, + ExcludedTopic.sexist }; + private boolean safe; + + public Category getCategory() { + return category; + } + + public void setCategory(Category category) { + this.category = category; + } + + public ExcludedTopic[] getExcluded() { + return excluded; + } + + public void setExcluded(ExcludedTopic[] excluded) { + this.excluded = excluded; + } + + public boolean isSafe() { + return safe; + } + + public void setSafe(boolean safe) { + this.safe = safe; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestStatus.java new file mode 100644 index 00000000000..70268644788 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/joke/JokeRequestStatus.java @@ -0,0 +1,67 @@ +/* + * 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.crdv2.example.joke; + +import io.fabric8.crd.generator.annotation.PrinterColumn; + +public class JokeRequestStatus { + public enum State { + CREATED, + ALREADY_PRESENT, + PROCESSING, + ERROR, + UNKNOWN + } + + private State state = State.UNKNOWN; + private boolean error; + private String message; + + @PrinterColumn(name = "jokeCategory") + private JokeRequestSpec.Category category = JokeRequestSpec.Category.Any; + + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public boolean isError() { + return error; + } + + public void setError(boolean error) { + this.error = error; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public JokeRequestSpec.Category getCategory() { + return category; + } + + public void setCategory(JokeRequestSpec.Category category) { + this.category = category; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJson.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJson.java new file mode 100644 index 00000000000..ed2d918607c --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJson.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.crdv2.example.json; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("containingjson.fabric8.io") +@Version("v1alpha1") +public class ContainingJson extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJsonSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJsonSpec.java new file mode 100644 index 00000000000..a357ba2d4ae --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/ContainingJsonSpec.java @@ -0,0 +1,40 @@ +/* + * 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.crdv2.example.json; + +import com.fasterxml.jackson.databind.JsonNode; + +public class ContainingJsonSpec { + + private int field; + + public int getField() { + return field; + } + + private JsonNode free; + + public JsonNode getFree() { + return free; + } + + private Foo foo; + + public Foo getFoo() { + return foo; + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/Foo.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/Foo.java new file mode 100644 index 00000000000..2ad1c1d58e5 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/json/Foo.java @@ -0,0 +1,38 @@ +/* + * 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.crdv2.example.json; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; + +import java.util.HashMap; +import java.util.Map; + +public class Foo { + + private Map configAsMap = new HashMap<>(); + + @JsonAnyGetter + public Map getConfigAsMap() { + return configAsMap; + } + + @JsonAnySetter + public void setConfigAsMap(String name, Object value) { + this.configAsMap.put(name, value); + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidation.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidation.java new file mode 100644 index 00000000000..13680faed51 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidation.java @@ -0,0 +1,35 @@ +/* + * 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.crdv2.example.k8svalidation; + +import io.fabric8.generator.annotation.ValidationRule; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("samples.fabric8.io") +@Version("v1alpha1") +@ValidationRule(value = "self.metadata.name.startsWith(self.spec.namePrefix)", messageExpression = "'name must start with ' + self.spec.namePrefix", reason = "FieldValueForbidden") +@ValidationRule(value = "self.status.availableReplicas >= self.spec.minReplicas", message = "updates not allowed in degraded state") +public class K8sValidation extends CustomResource { + + @Override + @ValidationRule(value = "self.minReplicas <= self.replicas", message = "replicas must be greater than or equal to minReplicas") + @ValidationRule(value = "self.replicas <= self.maxReplicas", message = "replicas must be smaller than or equal to maxReplicas") + public K8sValidationSpec getSpec() { + return super.getSpec(); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationSpec.java new file mode 100644 index 00000000000..cd8a67c8b26 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationSpec.java @@ -0,0 +1,123 @@ +/* + * 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.crdv2.example.k8svalidation; + +import io.fabric8.generator.annotation.Required; +import io.fabric8.generator.annotation.ValidationRule; +import lombok.Data; + +@Data +@ValidationRule(value = "self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas", fieldPath = ".replicas") +public class K8sValidationSpec { + @Required + private String namePrefix; + @Required + private Integer replicas; + @Required + private Integer minReplicas; + @Required + private Integer maxReplicas; + + @Required + @ValidationRule("self.startsWith('simple-')") + private String simple; + + // see getter + private String onGetter; + + @Required + @ValidationRule("self.startsWith('start-')") + @ValidationRule("self.endsWith('-end')") + private String multiple; + + @Required + @ValidationRule("self.startsWith('start-')") + private String onAttributeAndGetter; + + @Required + @ValidationRule(value = "self.valueL1 == self.deepLevel2.valueL2", messageExpression = "'valueL1 (' + self.valueL1 + ') must be equal to deepLevel2.valueL2 (' + self.deepLevel2.valueL2 + ')'") + private DeepLevel1 deepLevel1; + + @Required + @ValidationRule("self.dummy.startsWith('on-attr-')") + private OnClass onAttributeAndClass; + + @Required + private ClassWithValidationsFromAbstractClass onAbstractClass; + + // transition rules + @ValidationRule(value = "self == oldSelf", message = "cannot be changed once set") + private String immutable; + @Required + @ValidationRule(value = "!(self == 'high' && oldSelf == 'low') && !(self == 'low' && oldSelf == 'high')", message = "cannot transition directly between 'low' and 'high'") + private Priority priority; + @ValidationRule(value = "self >= oldSelf", message = "cannot decrease value once set", reason = "FieldValueForbidden") + private Integer monotonicCounter; + + @Required + @ValidationRule("self.startsWith('on-getter-')") + public String getOnGetter() { + return onGetter; + } + + @ValidationRule("self.endsWith('-end')") + public String getOnAttributeAndGetter() { + return onAttributeAndGetter; + } + + enum Priority { + low, + medium, + high + } + + @Data + static class DeepLevel1 { + @Required + private String valueL1; + + @Required + private DeepLevel2 deepLevel2; + } + + @Data + static class DeepLevel2 { + @Required + private String valueL2; + + @ValidationRule("self.startsWith('deep-')") + private String simple; + + } + + @Data + @ValidationRule("self.dummy.startsWith('on-class-')") + static class OnClass { + @Required + private String dummy; + } + + static class ClassWithValidationsFromAbstractClass extends AbstractBase { + + } + + @Data + @ValidationRule("self.dummy.startsWith('abstract-')") + static abstract class AbstractBase { + @Required + private String dummy; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationStatus.java new file mode 100644 index 00000000000..c522c9c8d67 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/k8svalidation/K8sValidationStatus.java @@ -0,0 +1,23 @@ +/* + * 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.crdv2.example.k8svalidation; + +import lombok.Data; + +@Data +public class K8sValidationStatus { + Integer availableReplicas; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMaps.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMaps.java new file mode 100644 index 00000000000..1873b2144e3 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMaps.java @@ -0,0 +1,34 @@ +/* + * 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.crdv2.example.map; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +import java.util.EnumMap; + +@Group("map.fabric8.io") +@Version("v1alpha1") +public class ContainingMaps extends CustomResource { + + public enum Foo { + BAR + } + + public EnumMap enumToStringMap; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMapsSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMapsSpec.java new file mode 100644 index 00000000000..11611a51e96 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/map/ContainingMapsSpec.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.crdv2.example.map; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ContainingMapsSpec { + + private Map> test = null; + + public Map> getTest() { + return test; + } + + private Map>> test2 = null; + + public Map>> getTest2() { + return test2; + } + + public MultiHashMap stringToIntMultiMap1; + public MultiMap stringToIntMultiMap2; + public SwappedParametersMap, String> stringToIntMultiMap3; + public RedundantParametersMap> stringToIntMultiMap4; + public RedundantParametersStringToIntMultiMap stringToIntMultiMap5; + public StringKeyedMultiHashMap stringToIntMultiMap6; + public 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-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/Multiple.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/Multiple.java new file mode 100644 index 00000000000..b0e238029b8 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/Multiple.java @@ -0,0 +1,25 @@ +/* + * 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.crdv2.example.multiple.v1; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version(value = "v1", storage = false) +public class Multiple extends CustomResource { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/MultipleSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/MultipleSpec.java new file mode 100644 index 00000000000..f384b0dface --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/MultipleSpec.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.multiple.v1; + +public class MultipleSpec { + private String v1; + + public String getV1() { + return v1; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/Multiple.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/Multiple.java new file mode 100644 index 00000000000..3eeb2099e90 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/Multiple.java @@ -0,0 +1,25 @@ +/* + * 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.crdv2.example.multiple.v2; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v2") +public class Multiple extends CustomResource { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/MultipleSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/MultipleSpec.java new file mode 100644 index 00000000000..f77ac2a1542 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/MultipleSpec.java @@ -0,0 +1,24 @@ +/* + * 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.crdv2.example.multiple.v2; + +public class MultipleSpec { + private String v2; + + public String getV2() { + return v2; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclic.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclic.java new file mode 100644 index 00000000000..2abdd702407 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclic.java @@ -0,0 +1,27 @@ +/* + * 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.crdv2.example.nocyclic; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v1alpha1") +public class NoCyclic extends CustomResource implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicSpec.java new file mode 100644 index 00000000000..0bcf423c3f4 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicSpec.java @@ -0,0 +1,21 @@ +/* + * 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.crdv2.example.nocyclic; + +public class NoCyclicSpec { + public Ref ref1; + public Ref ref2; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicStatus.java new file mode 100644 index 00000000000..d485c7b2152 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicStatus.java @@ -0,0 +1,21 @@ +/* + * 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.crdv2.example.nocyclic; + +public class NoCyclicStatus { + private String message; + private Ref ref1; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/Ref.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/Ref.java new file mode 100644 index 00000000000..e901d51f641 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/Ref.java @@ -0,0 +1,27 @@ +/* + * 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.crdv2.example.nocyclic; + +public class Ref { + + public int ref; + + public Inner inner; + + public static class Inner { + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Address.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Address.java new file mode 100644 index 00000000000..1404898266d --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Address.java @@ -0,0 +1,29 @@ +/* + * 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.crdv2.example.person; + +public class Address { + public String street; + public int number; + public String zip; + public String country; + public Type type; + + public enum Type { + home, + work + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/AddressList.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/AddressList.java new file mode 100644 index 00000000000..98fd75a475d --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/AddressList.java @@ -0,0 +1,21 @@ +/* + * 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.crdv2.example.person; + +import java.util.ArrayList; + +public class AddressList extends ArrayList
{ +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Person.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Person.java new file mode 100644 index 00000000000..72c57dd688f --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Person.java @@ -0,0 +1,35 @@ +/* + * 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.crdv2.example.person; + +import java.util.List; +import java.util.Optional; + +public class Person { + + public String firstName; + public Optional middleName; + public String lastName; + public int birthYear; + public List hobbies; + public AddressList addresses; + public Type type; + + public enum Type { + crazy, + crazier + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/Simplest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/Simplest.java new file mode 100644 index 00000000000..a875696430c --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/Simplest.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.crdv2.example.simplest; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("samples.fabric8.io") +@Version("v1alpha1") +public class Simplest extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestSpec.java new file mode 100644 index 00000000000..4516a828baf --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestSpec.java @@ -0,0 +1,20 @@ +/* + * 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.crdv2.example.simplest; + +public class SimplestSpec { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestStatus.java new file mode 100644 index 00000000000..b0238a84741 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestStatus.java @@ -0,0 +1,20 @@ +/* + * 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.crdv2.example.simplest; + +public class SimplestStatus { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerSpec.java new file mode 100644 index 00000000000..b24dba42f36 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerSpec.java @@ -0,0 +1,28 @@ +/* + * 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.crdv2.example.webserver; + +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import lombok.Data; + +@Data +public class WebServerSpec { + + private int port; + + @SpecReplicas + private int replicas; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerStatus.java new file mode 100644 index 00000000000..a64d35ba505 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerStatus.java @@ -0,0 +1,27 @@ +/* + * 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.crdv2.example.webserver; + +import io.fabric8.kubernetes.model.annotation.StatusReplicas; +import lombok.Data; + +@Data +public class WebServerStatus { + + @StatusReplicas + int replicas; + private boolean running; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithSpec.java new file mode 100644 index 00000000000..e46c6af131b --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithSpec.java @@ -0,0 +1,28 @@ +/* + * 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.crdv2.example.webserver; + +import lombok.Data; + +@Data +public class WebServerWithSpec { + + private String name; + + private WebServerSpec spec; + + private WebServerStatus status; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithStatusProperty.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithStatusProperty.java new file mode 100644 index 00000000000..7f85996b4ac --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithStatusProperty.java @@ -0,0 +1,31 @@ +/* + * 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.crdv2.example.webserver; + +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import lombok.Data; + +@Data +public class WebServerWithStatusProperty { + + private String name; + private int port; + + @SpecReplicas + private int replicas; + + private WebServerStatus status; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java new file mode 100644 index 00000000000..426eb2fcdfa --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java @@ -0,0 +1,138 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.kubernetes.client.CustomResource; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class CRDGeneratorAssertions { + + private CRDGeneratorAssertions() { + } + + /** + * Generates CRD files for v1 and v1beta1 and compares them with files in classpath. + * + * @param crClasses custom resource classes under test + * @param crdGenerator a CRDGenerator instance + */ + @SafeVarargs + public static void assertCRDOutputEquals(CRDGenerator crdGenerator, + Class>... crClasses) { + + assertCRDOutputEquals(crdGenerator, null, crClasses); + } + + /** + * Generates CRD files for v1 and v1beta1 and compares them with files in classpath. + * + * @param crClasses custom resource classes under test + * @param crdGenerator a CRDGenerator instance + * @param classPathExpectedCRDsNullable the class path to the directory which contains the expected CRDs. Defaults to "/" if + * null. + */ + @SafeVarargs + public static void assertCRDOutputEquals(CRDGenerator crdGenerator, + String classPathExpectedCRDsNullable, + Class>... crClasses) { + assertNotNull(crClasses); + assertTrue(crClasses.length > 0); + assertEquals(1, Arrays.stream(crClasses) + .map(CustomResource::getCRDName).distinct().count(), + "all crClasses must be of the same kind"); + + final String crdName = CustomResource.getCRDName(crClasses[0]); + final File outputDir; + try { + outputDir = Files.createTempDirectory("crd-").toFile(); + } catch (IOException e) { + fail("Could not create temp directory", e); + throw new RuntimeException(e); + } + + final String classPathExpectedCRDs = Optional.ofNullable(classPathExpectedCRDsNullable) + .map(s -> s.endsWith("/") ? s : s + "/") + .orElse("/"); + + // generate actual CRDs + final CRDGenerationInfo crdInfo = crdGenerator + .inOutputDir(outputDir) + .customResourceClasses(crClasses) + .forCRDVersions("v1", "v1beta1") + .detailedGenerate(); + final File actualCRDFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); + + // expected CRDs + final URL expectedCRDResource = CRDGeneratorTest.class.getResource(classPathExpectedCRDs + actualCRDFile.getName()); + assertNotNull(expectedCRDResource); + final File expectedCrdFile = new File(expectedCRDResource.getFile()); + + // compare + assertFileEquals(expectedCrdFile, actualCRDFile); + + // only delete the generated files if the test is successful + assertTrue(actualCRDFile.delete()); + assertTrue(outputDir.delete()); + } + + /** + * Compares two YAML files and fails if they are not equal. + * Comments and empty lines are ignored. + * + * @param expectedFile the file which contains the expected content + * @param actualFile the file which contains the content to test + */ + public static void assertFileEquals(final File expectedFile, final File actualFile) { + try (final BufferedReader expectedReader = new BufferedReader(new FileReader(expectedFile)); + final BufferedReader actualReader = new BufferedReader(new FileReader(actualFile))) { + // skip license headers + String expectedLine = skipCommentsAndEmptyLines(expectedReader); + String actualLine = skipCommentsAndEmptyLines(actualReader); + + // compare both files + final String message = String.format("Expected %s and actual %s files are not equal", expectedFile, actualFile); + while (expectedLine != null || actualLine != null) { + assertEquals(expectedLine, actualLine, message); + expectedLine = expectedReader.readLine(); + actualLine = actualReader.readLine(); + } + } catch (final IOException e) { + fail(String.format("Cannot compare files %s and %s: %s", expectedFile, actualFile, e.getMessage())); + } + } + + private static String skipCommentsAndEmptyLines(final BufferedReader reader) throws IOException { + String line = reader.readLine(); + while (line.startsWith("#") || line.isEmpty()) { + line = reader.readLine(); + } + return line; + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorExamplesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorExamplesTest.java new file mode 100644 index 00000000000..608297cbf0f --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorExamplesTest.java @@ -0,0 +1,62 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.crdv2.example.k8svalidation.K8sValidation; +import io.fabric8.crdv2.example.multiple.v2.MultipleSpec; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.fabric8.crdv2.generator.CRDGeneratorAssertions.assertCRDOutputEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CRDGeneratorExamplesTest { + + protected boolean parallelCRDGeneration; + + @Test + void multiple() throws IOException { + assertCRDOutputEquals(newCRDGenerator(), + io.fabric8.crdv2.example.multiple.v1.Multiple.class, io.fabric8.crdv2.example.multiple.v2.Multiple.class); + } + + @Group("sample.fabric8.io") + @Version(value = "v3") + public static class Multiple extends CustomResource { + } + + @Test + void multipleStorage_thenFail() { + CRDGenerator crdGenerator = newCRDGenerator(); + assertThrows(IllegalStateException.class, () -> assertCRDOutputEquals(crdGenerator, + io.fabric8.crdv2.example.multiple.v2.Multiple.class, Multiple.class)); + } + + @Test + void k8sValidation() throws IOException { + assertCRDOutputEquals(newCRDGenerator(), K8sValidation.class); + } + + private CRDGenerator newCRDGenerator() { + return new CRDGenerator() + .withParallelGenerationEnabled(parallelCRDGeneration); + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java new file mode 100644 index 00000000000..a9b7459e576 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java @@ -0,0 +1,590 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.crdv2.example.basic.Basic; +import io.fabric8.crdv2.example.complex.Complex; +import io.fabric8.crdv2.example.cyclic.Cyclic; +import io.fabric8.crdv2.example.cyclic.CyclicList; +import io.fabric8.crdv2.example.deprecated.v2.DeprecationExample; +import io.fabric8.crdv2.example.inherited.Child; +import io.fabric8.crdv2.example.joke.Joke; +import io.fabric8.crdv2.example.joke.JokeRequest; +import io.fabric8.crdv2.example.k8svalidation.K8sValidation; +import io.fabric8.crdv2.example.map.ContainingMaps; +import io.fabric8.crdv2.example.multiple.v1.Multiple; +import io.fabric8.crdv2.example.nocyclic.NoCyclic; +import io.fabric8.crdv2.example.simplest.Simplest; +import io.fabric8.crdv2.generator.CRDGenerator.AbstractCRDOutput; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceColumnDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionNames; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionSpec; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceValidation; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +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; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.net.URL; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static io.fabric8.crdv2.generator.CRDGeneratorAssertions.assertFileEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CRDGeneratorTest { + + private final TestCRDOutput output = new TestCRDOutput(); + protected boolean parallelCRDGeneration; + + @Test + void choosingCRDVersionsShouldWork() { + CRDGenerator generator = newCRDGenerator(); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions(); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions((List) null); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions((String[]) null); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions("v2"); + assertTrue(generator.getHandlers().isEmpty()); + + String version = "v1"; + generator.forCRDVersions(version); + Map handlers = generator.getHandlers(); + assertEquals(1, handlers.size()); + assertTrue(handlers.containsKey(version)); + + // v1beta1 not supported + generator.forCRDVersions(version, "v1beta1", version, "v3", null); + handlers = generator.getHandlers(); + assertEquals(1, handlers.size()); + assertTrue(handlers.containsKey(version)); + } + + @Test + void addingCustomResourceInfosShouldWork() { + CRDGenerator generator = newCRDGenerator(); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResourceClasses(); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResources(); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResources(null); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResources(null, null); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResourceClasses(Simplest.class); + assertEquals(1, generator.getCustomResourceInfos().size()); + assertTrue(generator.getCustomResourceInfos().stream().allMatch(cri -> cri.crClassName().equals(Simplest.class.getName()))); + + generator.customResourceClasses(Child.class); + assertEquals(2, generator.getCustomResourceInfos().size()); + CustomResourceInfo simplest = CustomResourceInfo.fromClass(Simplest.class); + assertTrue(generator.getCustomResourceInfos().contains(simplest)); + CustomResourceInfo child = CustomResourceInfo.fromClass(Child.class); + assertTrue(generator.getCustomResourceInfos().contains(child)); + + generator.customResources(CustomResourceInfo.fromClass(Child.class)); + assertEquals(2, generator.getCustomResourceInfos().size()); + + CustomResourceInfo joke = CustomResourceInfo.fromClass(Joke.class); + CustomResourceInfo jr = CustomResourceInfo.fromClass(JokeRequest.class); + generator.customResources(joke, jr); + Set infos = generator.getCustomResourceInfos(); + assertEquals(4, infos.size()); + assertTrue(infos.contains(simplest)); + assertTrue(infos.contains(child)); + assertTrue(infos.contains(joke)); + assertTrue(infos.contains(jr)); + } + + @Test + void shouldProperlyRecordNumberOfGeneratedCRDs() { + CRDGenerator generator = newCRDGenerator(); + assertEquals(0, generator.generate()); + assertEquals(0, generator.detailedGenerate().numberOfGeneratedCRDs()); + + final List versions = new ArrayList<>(2); + versions.add("v1"); + + final CRDGenerationInfo info = generator + .customResourceClasses(Simplest.class, Child.class, Joke.class, JokeRequest.class) + .forCRDVersions(versions) + .withOutput(output).detailedGenerate(); + + assertEquals(4, info.numberOfGeneratedCRDs()); + final Map> details = info.getCRDDetailsPerNameAndVersion(); + assertEquals(4, details.size()); + assertTrue(details.containsKey(CustomResource.getCRDName(Simplest.class))); + assertTrue(details.containsKey(CustomResource.getCRDName(Child.class))); + assertTrue(details.containsKey(CustomResource.getCRDName(Joke.class))); + final String crdName = CustomResource.getCRDName(JokeRequest.class); + assertTrue(details.containsKey(crdName)); + final Map jokeRequestInfos = info.getCRDInfos(crdName); + assertEquals(1, jokeRequestInfos.size()); + assertTrue(jokeRequestInfos.containsKey("v1")); + } + + @Test + void checkDeprecated() { + CRDGenerator generator = newCRDGenerator(); + final String specVersion = "v1"; + final CRDGenerationInfo info = generator + .customResourceClasses( + io.fabric8.crdv2.example.deprecated.v1beta1.DeprecationExample.class, + io.fabric8.crdv2.example.deprecated.v1.DeprecationExample.class, + DeprecationExample.class) + .forCRDVersions(specVersion) + .withOutput(output) + .detailedGenerate(); + + assertEquals(1, info.numberOfGeneratedCRDs()); + final Map> details = info.getCRDDetailsPerNameAndVersion(); + assertEquals(1, details.size()); + // check multiple versions for same CR + final String crdName = CustomResource.getCRDName(DeprecationExample.class); + assertTrue(details.containsKey(crdName)); + final Map infos = info.getCRDInfos(crdName); + assertEquals(1, infos.size()); + assertTrue(infos.containsKey(specVersion)); + + final String outputName = CRDGenerator.getOutputName(crdName, specVersion); + CustomResourceDefinition definition = output.definition(outputName); + assertNotNull(definition); + assertEquals("apiextensions.k8s.io/" + specVersion, definition.getApiVersion()); + + CustomResourceDefinitionSpec spec = definition.getSpec(); + final List versions = spec.getVersions(); + assertEquals(3, versions.size()); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v1beta1")).count()); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v1")).count()); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v2")).count()); + + CustomResourceDefinitionVersion v1Beta1 = versions.stream().filter(v -> v.getName().equals("v1beta1")).findFirst().get(); + CustomResourceDefinitionVersion v1 = versions.stream().filter(v -> v.getName().equals("v1")).findFirst().get(); + CustomResourceDefinitionVersion v2 = versions.stream().filter(v -> v.getName().equals("v2")).findFirst().get(); + assertTrue(v1Beta1.getDeprecated()); + assertEquals("sample.fabric8.io/v1beta1 DeprecationExample is deprecated; Migrate to sample.fabric8.io/v2", + v1Beta1.getDeprecationWarning()); + assertTrue(v1.getDeprecated()); + assertNull(v1.getDeprecationWarning()); + assertNull(v2.getDeprecated()); + assertNull(v2.getDeprecationWarning()); + } + + @Test + void notDefiningOutputShouldNotGenerateAnything() { + CRDGenerator generator = newCRDGenerator(); + assertEquals(0, generator.generate()); + + CustomResourceInfo joke = CustomResourceInfo.fromClass(Joke.class); + CustomResourceInfo jr = CustomResourceInfo.fromClass(JokeRequest.class); + generator.customResources(joke, jr); + assertEquals(0, generator.generate()); + } + + @Test + void generatingACycleShouldFail() { + final CRDGenerator generator = newCRDGenerator() + .customResourceClasses(Cyclic.class) + .forCRDVersions("v1") + .withOutput(output); + + assertThrows( + IllegalArgumentException.class, + generator::detailedGenerate, + "An IllegalArgument Exception hasn't been thrown when generating a CRD with cyclic references"); + } + + private CRDGenerator newCRDGenerator() { + return new CRDGenerator() + .withParallelGenerationEnabled(parallelCRDGeneration); + } + + @Test + void generatingACycleInListShouldFail() { + final CRDGenerator generator = newCRDGenerator() + .customResourceClasses(CyclicList.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"); + } + + @Test + void notGeneratingACycleShouldSucceed() { + final CRDGenerator generator = newCRDGenerator() + .customResourceClasses(NoCyclic.class) + .forCRDVersions("v1") + .withOutput(output); + + CRDGenerationInfo info = generator.detailedGenerate(); + assertEquals(1, info.numberOfGeneratedCRDs()); + } + + @FunctionalInterface + private interface CRTest { + void test(Class> customResource); + } + + private void outputCRDIfFailed(Class> customResource, CRTest test) { + try { + test.test(customResource); + } catch (AssertionFailedError e) { + // output crd + output.outputCRD(customResource); + throw e; + } + } + + @Test + void simplestCRDShouldWork() { + outputCRDIfFailed(Simplest.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "Simplest", "simplests", + Scope.CLUSTER); + assertNotNull(version.getSubresources()); + }); + + } + + @Test + void inheritedCRDShouldWork() { + outputCRDIfFailed(Child.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "Child", "children", + Scope.NAMESPACED); + assertNotNull(version.getSubresources()); + final Map specProps = version.getSchema().getOpenAPIV3Schema() + .getProperties().get("spec").getProperties(); + assertEquals(4, specProps.size()); + assertEquals("integer", specProps.get("baseInt").getType()); + checkMapProp(specProps, "unsupported", "object"); + checkMapProp(specProps, "unsupported2", "object"); + checkMapProp(specProps, "supported", "string"); + }); + } + + @Test + void mapPropertyShouldHaveCorrectValueType() { + outputCRDIfFailed(ContainingMaps.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "ContainingMaps", "containingmaps", + Scope.CLUSTER); + assertNotNull(version.getSchema()); + + final Map specProps = version.getSchema().getOpenAPIV3Schema() + .getProperties().get("spec").getProperties(); + + assertEquals(9, specProps.size()); + + JSONSchemaProps testSchema = checkMapProp(specProps, "test", "array"); + assertEquals("string", testSchema.getItems().getSchema().getType()); + + 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 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(); + } + + @Test + void jokeCRDShouldWork() { + outputCRDIfFailed(Joke.class, (customResource) -> { + CustomResourceDefinitionVersion version = checkCRD(Joke.class, "Joke", "jokes", + Scope.NAMESPACED); + assertNull(version.getSubresources()); + }); + } + + @Test + void jokerequestCRDShouldWork() { + outputCRDIfFailed(JokeRequest.class, (customResource) -> { + final CustomResourceDefinitionSpec spec = checkSpec(customResource, Scope.NAMESPACED); + + final CustomResourceDefinitionNames names = checkNames("JokeRequest", + "jokerequests", spec); + assertEquals(1, names.getShortNames().size()); + assertTrue(names.getShortNames().contains("jr")); + + final CustomResourceDefinitionVersion version = checkVersion(spec); + assertNotNull(version.getSubresources()); + // printer columns should be ordered in the alphabetical order of their json path + final List printerColumns = version + .getAdditionalPrinterColumns(); + assertEquals(3, printerColumns.size()); + CustomResourceColumnDefinition columnDefinition = printerColumns.get(0); + assertEquals("string", columnDefinition.getType()); + assertEquals(".spec.category", columnDefinition.getJsonPath()); + assertEquals("jokeCategory", columnDefinition.getName()); + assertEquals(1, columnDefinition.getPriority()); + columnDefinition = printerColumns.get(1); + assertEquals("string", columnDefinition.getType()); + assertEquals(".spec.excluded", columnDefinition.getJsonPath()); + assertEquals("excludedTopics", columnDefinition.getName()); + assertEquals(0, columnDefinition.getPriority()); + columnDefinition = printerColumns.get(2); + assertEquals("string", columnDefinition.getType()); + assertEquals(".status.category", columnDefinition.getJsonPath()); + assertEquals("jokeCategory", columnDefinition.getName()); + assertEquals(0, columnDefinition.getPriority()); + CustomResourceValidation schema = version.getSchema(); + assertNotNull(schema); + Map properties = schema.getOpenAPIV3Schema().getProperties(); + assertEquals(2, properties.size()); + Map specProps = properties.get("spec").getProperties(); + assertEquals(3, specProps.size()); + assertEquals("boolean", specProps.get("safe").getType()); + JSONSchemaProps category = specProps.get("category"); + assertEquals("string", category.getType()); + assertEquals(7, category.getEnum().size()); + assertEquals("category-description", category.getDescription()); + JSONSchemaProps excluded = specProps.get("excluded"); + assertEquals("array", excluded.getType()); + assertEquals("string", excluded.getItems().getSchema().getType()); + assertEquals(6, excluded.getItems().getSchema().getEnum().size()); + }); + } + + @Test + void checkCRDGenerator() { + outputCRDIfFailed(Basic.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "Basic", "basics", + Scope.NAMESPACED); + assertNotNull(version.getSubresources()); + CustomResourceValidation schema = version.getSchema(); + assertNotNull(schema); + Map properties = schema.getOpenAPIV3Schema().getProperties(); + assertEquals(2, properties.size()); + Map specProps = properties.get("spec").getProperties(); + assertEquals("integer", specProps.get("myInt").getType()); + Map status = properties.get("status").getProperties(); + assertEquals("string", status.get("message").getType()); + }); + } + + @Test + void checkGenerationIsDeterministic() throws Exception { + // generated CRD + final File outputDir = Files.createTempDirectory("crd-").toFile(); + 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()); + + // expected CRD + final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName()); + + assertNotNull(crdResource); + final File expectedCrdFile = new File(crdResource.getFile()); + assertFileEquals(expectedCrdFile, crdFile); + + // only delete the generated files if the test is successful + assertTrue(crdFile.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.crdv2.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.crdv2.example.multiple.v2.Multiple.class) + .forCRDVersions("v1", "v1beta1") + .detailedGenerate(); + + final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); + + // expected CRD + final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName()); + assertNotNull(crdResource); + + final File expectedCrdFile = new File(crdResource.getFile()); + assertFileEquals(expectedCrdFile, crdFile); + + // only delete the generated files if the test is successful + assertTrue(crdFile.delete()); + assertTrue(outputDir.delete()); + } + + @Test + void checkK8sValidationRules() throws Exception { + // generated CRD + final File outputDir = Files.createTempDirectory("crd-").toFile(); + final String crdName = CustomResourceInfo.fromClass(K8sValidation.class).crdName(); + + final CRDGenerationInfo crdInfo = newCRDGenerator() + .inOutputDir(outputDir) + .customResourceClasses(K8sValidation.class) + .forCRDVersions("v1", "v1beta1") + .detailedGenerate(); + + final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); + + // expected CRD + final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName()); + assertNotNull(crdResource); + + final File expectedCrdFile = new File(crdResource.getFile()); + assertFileEquals(expectedCrdFile, crdFile); + + // only delete the generated files if the test is successful + assertTrue(crdFile.delete()); + assertTrue(outputDir.delete()); + } + + private CustomResourceDefinitionVersion checkCRD(Class> customResource, String kind, + String plural, + Scope scope) { + CustomResourceDefinitionSpec spec = checkSpec(customResource, scope); + checkNames(kind, plural, spec); + + return checkVersion(spec); + } + + private CustomResourceDefinitionVersion checkVersion(CustomResourceDefinitionSpec spec) { + CustomResourceDefinitionVersion version = spec.getVersions().get(0); + assertTrue(version.getServed()); + assertTrue(version.getStorage()); + return version; + } + + private CustomResourceDefinitionNames checkNames(String kind, String plural, CustomResourceDefinitionSpec spec) { + CustomResourceDefinitionNames names = spec.getNames(); + assertEquals(kind, names.getKind()); + assertEquals(plural, names.getPlural()); + return names; + } + + private CustomResourceDefinitionSpec checkSpec( + Class> customResource, Scope scope) { + CRDGenerator generator = newCRDGenerator(); + + // record info to be able to output it if the test fails + final String outputName = keyFor(customResource); + final CustomResourceInfo info = CustomResourceInfo.fromClass(customResource); + output.put(outputName, info); + final String v1 = "v1"; + final CRDGenerationInfo generatedInfo = generator.withOutput(output) + .forCRDVersions(v1) + .customResources(info) + .detailedGenerate(); + assertEquals(1, generatedInfo.numberOfGeneratedCRDs()); + final String crdName = info.crdName(); + final Map crdInfos = generatedInfo.getCRDInfos(crdName); + assertEquals(1, crdInfos.size()); + final CRDInfo crdInfo = crdInfos.get(v1); + assertEquals(crdName, crdInfo.getCrdName()); + assertEquals(v1, crdInfo.getCrdSpecVersion()); + assertTrue(crdInfo.getFilePath().endsWith(CRDGenerator.getOutputName(crdName, v1))); // test output uses the CRD name as URI + + CustomResourceDefinition definition = output.definition(outputName); + assertNotNull(definition); + assertEquals("apiextensions.k8s.io/v1", definition.getApiVersion()); + + CustomResourceDefinitionSpec spec = definition.getSpec(); + assertEquals(scope.toString(), spec.getScope()); + return spec; + } + + private static String keyFor(Class> customResource) { + return CRDGenerator.getOutputName(CustomResource.getCRDName(customResource), "v1"); + } + + private static class TestCRDOutput extends AbstractCRDOutput { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestCRDOutput.class); + private final static Class crdClass = CustomResourceDefinition.class; + private final Map infos = new ConcurrentHashMap<>(); + + @Override + protected ByteArrayOutputStream createStreamFor(String crdName) { + return new ByteArrayOutputStream(); + } + + CustomResourceDefinition definition(String outputName) { + return Serialization.unmarshal(new ByteArrayInputStream(getStreamFor(outputName).toByteArray()), crdClass); + } + + void put(String outputName, CustomResourceInfo info) { + infos.put(outputName, info); + } + + CustomResourceInfo get(String outputName) { + return infos.get(outputName); + } + + void outputCRD(Class> customResource) { + String s = getStreamFor(keyFor(customResource)).toString(); + LOGGER.debug(s); + } + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CustomResourceInfoTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CustomResourceInfoTest.java new file mode 100644 index 00000000000..4dc8a0fd9dc --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CustomResourceInfoTest.java @@ -0,0 +1,92 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.Scope; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CustomResourceInfoTest { + + public static class Spec { + } + + public static class Status { + } + + private static final String GROUP = "sample.fabric8.io"; + private static final String VERSION = "v1"; + + @Group(GROUP) + @Version(VERSION) + @ShortNames("s") + public static class ClusteredCR extends CustomResource { + } + + @Group(GROUP) + @Version(VERSION) + public static class NamespacedCR extends CustomResource implements Namespaced { + } + + @Test + void shouldBeProperlyScoped() { + CustomResourceInfo info = CustomResourceInfo.fromClass(ClusteredCR.class); + assertEquals(GROUP, info.group()); + assertEquals(VERSION, info.version()); + assertEquals(Scope.CLUSTER, info.scope()); + + info = CustomResourceInfo.fromClass(NamespacedCR.class); + assertEquals(GROUP, info.group()); + assertEquals(VERSION, info.version()); + assertEquals(Scope.NAMESPACED, info.scope()); + } + + @Test + void shouldProperlyCreateCustomResourceInfo() { + CustomResourceInfo info = CustomResourceInfo.fromClass(ClusteredCR.class); + assertEquals(GROUP, info.group()); + assertEquals(VERSION, info.version()); + assertEquals(Scope.CLUSTER, info.scope()); + assertEquals(ClusteredCR.class.getCanonicalName(), info.crClassName()); // todo: should we actually use the type name here? + assertTrue(info.specClassName().isPresent()); + String specClassName = info.specClassName().get(); + assertEquals(Spec.class.getName(), specClassName); + // todo: check that we can load and instantiate class from the returned class name + /* + * Class specClass = Class.forName(specClassName); + * Object o = specClass.getDeclaredConstructor().newInstance(); + * assertNotNull(o); + */ + assertTrue(info.statusClassName().isPresent()); + assertEquals(Status.class.getName(), info.statusClassName().get()); + assertEquals(HasMetadata.getSingular(ClusteredCR.class), info.singular()); + assertEquals(HasMetadata.getPlural(ClusteredCR.class), info.plural()); + assertEquals(CustomResource.getCRDName(ClusteredCR.class), info.crdName()); + assertArrayEquals(CustomResource.getShortNames(ClusteredCR.class), info.shortNames()); + assertTrue(info.served()); + assertTrue(info.storage()); + assertEquals(HasMetadata.getKind(ClusteredCR.class), info.kind()); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorExamplesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorExamplesTest.java new file mode 100644 index 00000000000..1f391be0c6a --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorExamplesTest.java @@ -0,0 +1,22 @@ +/* + * 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.crdv2.generator; + +public class ParallelCRDGeneratorExamplesTest extends CRDGeneratorExamplesTest { + public ParallelCRDGeneratorExamplesTest() { + parallelCRDGeneration = true; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorTest.java new file mode 100644 index 00000000000..30ca61c8a8f --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorTest.java @@ -0,0 +1,23 @@ +/* + * 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.crdv2.generator; + +class ParallelCRDGeneratorTest extends CRDGeneratorTest { + + public ParallelCRDGeneratorTest() { + parallelCRDGeneration = false; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java new file mode 100644 index 00000000000..6d745e697fd --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java @@ -0,0 +1,56 @@ +/* + * 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.crdv2.generator; + +import io.fabric8.crdv2.generator.Resources; +import io.fabric8.crdv2.generator.v1.decorator.AddAdditionPrinterColumnDecorator; +import io.fabric8.crdv2.generator.v1.decorator.SortPrinterColumnsDecorator; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ResourcesTest { + + @Test + public void shouldSupportMultiplePrinterColumns() { + Resources r = new Resources(); + + AddAdditionPrinterColumnDecorator dec1 = new AddAdditionPrinterColumnDecorator("resource", "v1", "string", "replicas", + ".replicas", null, null, 0); + AddAdditionPrinterColumnDecorator dec2 = new AddAdditionPrinterColumnDecorator("resource", "v1", "boolean", "enabled", + ".replicas", null, null, 0); + + r.decorate(dec1); + r.decorate(dec2); + + assertEquals(2, r.getDecorators().size()); + assertTrue(r.getDecorators().contains(dec1)); + assertTrue(r.getDecorators().contains(dec2)); + } + + @Test + public void shouldSupportMultipleSortPrinterColumns() { + Resources r = new Resources(); + SortPrinterColumnsDecorator dec1 = new SortPrinterColumnsDecorator("my-crd", "v1"); + SortPrinterColumnsDecorator dec2 = new SortPrinterColumnsDecorator("my-crd", "v2"); + r.decorate(dec1); + r.decorate(dec2); + assertEquals(2, r.getDecorators().size()); + assertTrue(r.getDecorators().contains(dec1)); + assertTrue(r.getDecorators().contains(dec2)); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java new file mode 100644 index 00000000000..3f56a578589 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java @@ -0,0 +1,33 @@ +/* + * 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.crdv2.generator.utils; + +import io.fabric8.crdv2.example.inherited.Child; +import io.fabric8.crdv2.generator.utils.Types.SpecAndStatus; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TypesTest { + + @Test + void shouldFindInheritedStatusProperty() { + final SpecAndStatus specAndStatus = Types.resolveSpecAndStatusTypes(Child.class); + assertEquals("io.fabric8.crdv2.example.inherited.ChildStatus", specAndStatus.getStatusClassName()); + assertEquals("io.fabric8.crdv2.example.inherited.ChildSpec", specAndStatus.getSpecClassName()); + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java new file mode 100644 index 00000000000..b3c85f1b980 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java @@ -0,0 +1,576 @@ +/* + * 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.crdv2.generator.v1; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.TextNode; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.crdv2.example.annotated.Annotated; +import io.fabric8.crdv2.example.basic.Basic; +import io.fabric8.crdv2.example.extraction.CollectionCyclicSchemaSwap; +import io.fabric8.crdv2.example.extraction.CyclicSchemaSwap; +import io.fabric8.crdv2.example.extraction.DeeplyNestedSchemaSwaps; +import io.fabric8.crdv2.example.extraction.Extraction; +import io.fabric8.crdv2.example.extraction.IncorrectExtraction; +import io.fabric8.crdv2.example.extraction.IncorrectExtraction2; +import io.fabric8.crdv2.example.extraction.MultipleSchemaSwaps; +import io.fabric8.crdv2.example.extraction.NestedSchemaSwap; +import io.fabric8.crdv2.example.json.ContainingJson; +import io.fabric8.crdv2.example.person.Person; +import io.fabric8.kubernetes.api.model.AnyType; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.ValidationRule; +import io.fabric8.kubernetes.api.model.coordination.v1.LeaseSpec; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Node; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class JsonSchemaTest { + + @Test + void shouldCreateAnyTypeWithoutProperties() { + JSONSchemaProps schema = JsonSchema.from(AnyType.class); + assertSchemaHasNumberOfProperties(schema, 0); + assertTrue(schema.getXKubernetesPreserveUnknownFields()); + } + + @Test + void shouldCreateJsonSchemaFromClass() { + JSONSchemaProps schema = JsonSchema.from(Person.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 7); + final List personTypes = properties.get("type").getEnum().stream().map(JsonNode::asText) + .collect(Collectors.toList()); + assertEquals(2, personTypes.size()); + assertTrue(personTypes.contains("crazy")); + assertTrue(personTypes.contains("crazier")); + final Map addressProperties = properties.get("addresses").getItems() + .getSchema().getProperties(); + assertEquals(5, addressProperties.size()); + final List addressTypes = addressProperties.get("type").getEnum().stream() + .map(JsonNode::asText) + .collect(Collectors.toList()); + assertEquals(2, addressTypes.size()); + assertTrue(addressTypes.contains("home")); + assertTrue(addressTypes.contains("work")); + + schema = JsonSchema.from(Basic.class); + assertNotNull(schema); + properties = schema.getProperties(); + assertNotNull(properties); + assertEquals(2, properties.size()); + Map spec = properties.get("spec").getProperties(); + assertEquals("integer", spec.get("myInt").getType()); + assertEquals("integer", spec.get("myLong").getType()); + assertEquals("number", spec.get("myDouble").getType()); + assertEquals("number", spec.get("myFloat").getType()); + Map status = properties.get("status").getProperties(); + assertEquals("string", status.get("message").getType()); + } + + @Test + void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingException { + JSONSchemaProps schema = JsonSchema.from(Annotated.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 20); + + // check descriptions are present + assertTrue(spec.containsKey("from-field")); + JSONSchemaProps prop = spec.get("from-field"); + assertEquals("from-field-description", prop.getDescription()); + assertTrue(spec.containsKey("from-getter")); + prop = spec.get("from-getter"); + assertEquals("from-getter-description", prop.getDescription()); + + // fields without description annotation shouldn't have them + assertTrue(spec.containsKey("unnamed")); + assertNull(spec.get("unnamed").getDescription()); + assertTrue(spec.containsKey("emptySetter")); + assertNull(spec.get("emptySetter").getDescription()); + assertTrue(spec.containsKey("anEnum")); + + Function type = t -> new JSONSchemaPropsBuilder().withType(t); + assertEquals(type.apply("integer").withMinimum(-5.0).build(), spec.get("min")); + assertEquals(type.apply("integer").withMaximum(5.0).build(), spec.get("max")); + assertEquals(type.apply("string").withPattern("\\b[1-9]\\b").build(), spec.get("singleDigit")); + assertEquals(type.apply("string").withNullable(true).build(), spec.get("nullable")); + assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value")).build(), spec.get("defaultValue")); + assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value2")).build(), spec.get("defaultValue2")); + assertEquals(type.apply("string").withEnum(TextNode.valueOf("non"), TextNode.valueOf("oui")).build(), spec.get("anEnum")); + assertEquals(type.apply("string").withFormat("date-time").withPattern("yyyy-MM-dd'T'HH:mm:ssVV").build(), spec.get("issuedAt")); + // the shape does not influence the type in these cases + assertEquals(type.apply("string").build(), spec.get("bool")); + assertEquals(type.apply("string").build(), spec.get("num")); + assertEquals(type.apply("string").build(), spec.get("numFloat")); + assertEquals(type.apply("string").build(), spec.get("numInt")); + + // check required list, should register properties with their modified name if needed + final List required = specSchema.getRequired(); + assertEquals(3, required.size()); + assertTrue(required.contains("emptySetter")); + assertTrue(required.contains("emptySetter2")); + assertTrue(required.contains("from-getter")); + + // check ignored fields + assertFalse(spec.containsKey("ignoredFoo")); + assertFalse(spec.containsKey("ignoredBar")); + + final JSONSchemaProps k8sValidationProps = spec.get("kubernetesValidationRule"); + final List k8sValidationRulesSingle = k8sValidationProps.getXKubernetesValidations(); + assertNotNull(k8sValidationRulesSingle); + assertEquals(1, k8sValidationRulesSingle.size()); + assertEquals("self.startwith('prefix-')", k8sValidationRulesSingle.get(0).getRule()); + assertEquals("kubernetesValidationRule must start with prefix 'prefix-'", k8sValidationRulesSingle.get(0).getMessage()); + assertNull(k8sValidationRulesSingle.get(0).getMessageExpression()); + assertNull(k8sValidationRulesSingle.get(0).getReason()); + assertNull(k8sValidationRulesSingle.get(0).getFieldPath()); + assertNull(k8sValidationRulesSingle.get(0).getOptionalOldSelf()); + + final JSONSchemaProps kubernetesValidationsRepeated = spec.get("kubernetesValidationRules"); + final List kubernetesValidationsRepeatedRules = kubernetesValidationsRepeated.getXKubernetesValidations(); + assertNotNull(kubernetesValidationsRepeatedRules); + assertEquals(3, kubernetesValidationsRepeatedRules.size()); + assertEquals("first.rule", kubernetesValidationsRepeatedRules.get(0).getRule()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getFieldPath()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getReason()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getMessage()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getMessageExpression()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getOptionalOldSelf()); + assertEquals("second.rule", kubernetesValidationsRepeatedRules.get(1).getRule()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getFieldPath()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getReason()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getMessage()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getMessageExpression()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getOptionalOldSelf()); + } + + @Test + void shouldProduceKubernetesPreserveFields() { + JSONSchemaProps schema = JsonSchema.from(ContainingJson.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 3); + + // check preserve unknown fields is present + assertTrue(spec.containsKey("free")); + JSONSchemaProps freeField = spec.get("free"); + + assertNull(freeField.getType()); + assertTrue(freeField.getXKubernetesPreserveUnknownFields()); + + assertTrue(spec.containsKey("field")); + JSONSchemaProps field = spec.get("field"); + + assertEquals("integer", field.getType()); + assertNull(field.getXKubernetesPreserveUnknownFields()); + + assertTrue(spec.containsKey("foo")); + JSONSchemaProps fooField = spec.get("foo"); + + assertEquals("object", fooField.getType()); + assertTrue(fooField.getXKubernetesPreserveUnknownFields()); + } + + @Test + void shouldExtractPropertiesSchemaFromExtractValueAnnotation() { + JSONSchemaProps schema = JsonSchema.from(Extraction.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 2); + + // check typed SchemaFrom + JSONSchemaProps foo = spec.get("foo"); + Map fooProps = foo.getProperties(); + assertNotNull(fooProps); + + // you can change everything + assertEquals("integer", fooProps.get("BAZ").getType()); + assertTrue(foo.getRequired().contains("BAZ")); + + // you can exclude fields + assertNull(fooProps.get("baz")); + + // check typed SchemaSwap + JSONSchemaProps bar = spec.get("bar"); + Map barProps = bar.getProperties(); + assertNotNull(barProps); + assertTrue(bar.getXKubernetesPreserveUnknownFields()); + + // you can change everything + assertEquals("integer", barProps.get("BAZ").getType()); + assertTrue(bar.getRequired().contains("BAZ")); + + // you can exclude fields + assertNull(barProps.get("baz")); + } + + @Test + void shouldExtractPropertiesSchemaFromSchemaSwapAnnotations() { + JSONSchemaProps schema = JsonSchema.from(MultipleSchemaSwaps.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 4); + + // 'first' is replaced by SchemaSwap from int to string + JSONSchemaProps first = spec.get("first"); + assertPropertyHasType(first, "shouldBeString", "string"); + + // 'second' is replaced by the same SchemaSwap that is applied multiple times + JSONSchemaProps second = spec.get("second"); + assertPropertyHasType(second, "shouldBeString", "string"); + + // 'third' is replaced by another SchemaSwap + JSONSchemaProps third = spec.get("third"); + assertPropertyHasType(third, "shouldBeInt", "integer"); + + // 'fourth' is replaced by another SchemaSwap and its property deleted + JSONSchemaProps fourth = spec.get("fourth"); + Map properties4 = fourth.getProperties(); + assertNotNull(properties); + assertTrue(properties4.isEmpty()); + } + + @Test + void shouldApplySchemaSwapsMultipleTimesInDeepClassHierarchy() { + JSONSchemaProps schema = JsonSchema.from(DeeplyNestedSchemaSwaps.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + Map spec = assertSchemaHasNumberOfProperties(properties.get("spec"), 2); + + assertPropertyHasType(spec.get("myObject"), "shouldBeString", "string"); + Map level1 = assertSchemaHasNumberOfProperties(spec.get("level1"), 3); + + assertPropertyHasType(level1.get("myObject"), "shouldBeString", "string"); + List> levels2 = new ArrayList<>(); + levels2.add(assertSchemaHasNumberOfProperties(level1.get("level2a"), 3)); + levels2.add(assertSchemaHasNumberOfProperties(level1.get("level2b"), 3)); + + for (Map level2 : levels2) { + assertPropertyHasType(level2.get("myObject1"), "shouldBeString", "string"); + assertPropertyHasType(level2.get("myObject2"), "shouldBeString", "string"); + + Map level3 = assertSchemaHasNumberOfProperties(level2.get("level3"), 2); + assertPropertyHasType(level3.get("myObject1"), "shouldBeString", "string"); + assertPropertyHasType(level3.get("myObject2"), "shouldBeString", "string"); + } + } + + @Test + void shouldApplyCyclicSchemaSwaps() { + JSONSchemaProps schema = JsonSchema.from(CyclicSchemaSwap.class); + assertNotNull(schema); + + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + Map spec = assertSchemaHasNumberOfProperties(properties.get("spec"), 3); + + // the collection should emit a single level then terminate with void + assertNull(spec.get("roots").getItems().getSchema().getProperties().get("level").getProperties().get("level")); + + assertPropertyHasType(spec.get("myObject"), "value", "integer"); + + // the field should emit a single level then terminate with void + assertNull(spec.get("root").getProperties().get("level").getProperties().get("level")); + } + + public static class PreserveUnknown { + + @JsonIgnore + private Map values = new HashMap<>(); + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.values; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.values.put(name, value); + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } + + } + + @Test + void testPreserveUnknown() { + JSONSchemaProps schema = JsonSchema.from(PreserveUnknown.class); + assertNotNull(schema); + assertEquals(0, schema.getProperties().size()); + assertEquals(Boolean.TRUE, schema.getXKubernetesPreserveUnknownFields()); + } + + // implicitly uses AnySchema + private static class MySerializer extends StdSerializer { + + public MySerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + + } + + } + + public static class AnyTestTypes { + @JsonSerialize(using = MySerializer.class) + public Object value; + public Node nodeValue; + } + + @Test + void testAnySchemaTypes() { + JSONSchemaProps schema = JsonSchema.from(AnyTestTypes.class); + assertNotNull(schema); + assertNull(schema.getProperties().get("nodeValue").getType()); + assertEquals(Boolean.TRUE, schema.getProperties().get("nodeValue").getXKubernetesPreserveUnknownFields()); + assertEquals(Boolean.TRUE, schema.getProperties().get("value").getXKubernetesPreserveUnknownFields()); + } + + @Test + void testLeaseSpec() { + JSONSchemaProps schema = JsonSchema.from(LeaseSpec.class); + assertNotNull(schema); + JSONSchemaProps renewTime = schema.getProperties().get("renewTime"); + assertEquals("string", renewTime.getType()); + assertEquals("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", renewTime.getPattern()); + assertEquals("date-time", renewTime.getFormat()); + } + + private static class NestedHasMetadata { + + public HasMetadata embedded; + + } + + @Test + void testNestedHasMetadata() { + JSONSchemaProps schema = JsonSchema.from(NestedHasMetadata.class); + assertNotNull(schema); + JSONSchemaProps embedded = schema.getProperties().get("embedded"); + assertEquals(true, embedded.getXKubernetesEmbeddedResource()); + assertNull(embedded.getType()); + } + + @Test + void testQuantity() { + JSONSchemaProps schema = JsonSchema.from(Quantity.class); + assertNotNull(schema); + assertEquals(2, schema.getAnyOf().size()); + assertEquals(true, schema.getXKubernetesIntOrString()); + } + + @Test + void testServicePort() { + JSONSchemaProps schema = JsonSchema.from(ServicePort.class); + assertNotNull(schema); + JSONSchemaProps targetPort = schema.getProperties().get("targetPort"); + assertEquals(2, targetPort.getAnyOf().size()); + assertEquals(true, targetPort.getXKubernetesIntOrString()); + } + + private static class MapProperty { + + public Map map; + + } + + @Test + void testMapProperty() { + JSONSchemaProps schema = JsonSchema.from(MapProperty.class); + assertNotNull(schema); + JSONSchemaProps map = schema.getProperties().get("map"); + assertTrue(map.getProperties().isEmpty()); + assertEquals("boolean", map.getAdditionalProperties().getSchema().getType()); + } + + private static class Cyclic1 { + + public Cyclic1 parent; + + } + + private static class Cyclic2 { + + public Cyclic2 parent[]; + + } + + private static class Cyclic3 { + + public Map parent; + + } + + @Test + void testCyclicProperties() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(Cyclic1.class)); + assertEquals("Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1.parent", exception.getMessage()); + + exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(Cyclic2.class)); + assertEquals("Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2.parent", exception.getMessage()); + + exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(Cyclic3.class)); + assertEquals("Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3.parent", exception.getMessage()); + } + + @SchemaSwap(originalType = Cyclic3.class, fieldName = "parent") + private static class Cyclic4 { + + public Cyclic3 parent; + public int value; + + } + + @Test + void testSchemaSwapZeroDepth() { + JSONSchemaProps schema = JsonSchema.from(Cyclic4.class); + assertNotNull(schema); + JSONSchemaProps parent = schema.getProperties().get("parent"); + assertTrue(parent.getProperties().isEmpty()); + } + + @Test + void shouldApplyCollectionCyclicSchemaSwaps() { + JSONSchemaProps schema = JsonSchema.from(CollectionCyclicSchemaSwap.class); + assertNotNull(schema); + + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + Map spec = assertSchemaHasNumberOfProperties(properties.get("spec"), 2); + + assertPropertyHasType(spec.get("myObject"), "value", "integer"); + Map level1 = assertSchemaHasNumberOfProperties(spec.get("levels").getItems().getSchema(), 2); + + assertPropertyHasType(level1.get("myObject"), "value", "integer"); + Map level2 = assertSchemaHasNumberOfProperties(level1.get("levels").getItems().getSchema(), 2); + + assertPropertyHasType(level2.get("myObject"), "value", "integer"); + Map level3 = assertSchemaHasNumberOfProperties(level2.get("levels").getItems().getSchema(), 2); + + assertPropertyHasType(level3.get("myObject"), "value", "integer"); + // should terminate at the 3rd level with any - this is probably not quite the behavior we want + // targeting collection properties with a non-collection terminal seems problematic + JSONSchemaProps terminal = level3.get("levels"); + assertNull(terminal.getItems()); + assertTrue(terminal.getXKubernetesPreserveUnknownFields()); + assertSchemaHasNumberOfProperties(terminal, 0); + } + + @Test + void shouldThrowIfSchemaSwapHasUnmatchedField() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(IncorrectExtraction.class)); + assertEquals( + "Unmatched SchemaSwaps: @SchemaSwap(originalType=io.fabric8.crdv2.example.extraction.ExtractionSpec, fieldName=\"FOO\", targetType=io" + + ".fabric8.crdv2.example.extraction.FooExtractor) on io.fabric8.crdv2.example.extraction.IncorrectExtraction", + exception.getMessage()); + } + + @Test + void shouldThrowIfSchemaSwapHasUnmatchedClass() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(IncorrectExtraction2.class)); + assertEquals( + "Unmatched SchemaSwaps: @SchemaSwap(originalType=io.fabric8.crdv2.example.basic.BasicSpec, fieldName=\"bar\", targetType=io.fabric8.crdv2" + + ".example.extraction.FooExtractor) on io.fabric8.crdv2.example.extraction.IncorrectExtraction2", + exception.getMessage()); + } + + @Test + void shouldThrowIfSchemaSwapNested() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(NestedSchemaSwap.class)); + assertEquals( + "Nested SchemaSwap: @SchemaSwap(originalType=io.fabric8.crdv2.example.extraction.NestedSchemaSwap$End, fieldName=\"value\", targetType=java.lang.Void) " + + "on io.fabric8.crdv2.example.extraction.NestedSchemaSwap$Intermediate", + exception.getMessage()); + } + + @io.fabric8.generator.annotation.ValidationRule(value = "base", messageExpression = "something", reason = "FieldValueForbidden") + private static class Base { + public String value; + } + + @io.fabric8.generator.annotation.ValidationRule(value = "parent", messageExpression = "something else", reason = "FieldValueForbidden") + private static class Parent extends Base { + + } + + @Test + void testValidationRuleHierarchy() { + JSONSchemaProps schema = JsonSchema.from(Parent.class); + assertNotNull(schema); + assertEquals(2, schema.getXKubernetesValidations().size()); + } + + private static Map assertSchemaHasNumberOfProperties(JSONSchemaProps specSchema, int expected) { + Map spec = specSchema.getProperties(); + assertEquals(expected, spec.size()); + return spec; + } + + private static void assertPropertyHasType(JSONSchemaProps spec, String name, String expectedType) { + Map properties = spec.getProperties(); + assertNotNull(properties); + JSONSchemaProps property = properties.get(name); + assertNotNull(property, "Property " + name + " should exist"); + assertEquals(expectedType, property.getType(), "Property " + name + " should have expected type"); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java new file mode 100644 index 00000000000..095d80b28ea --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java @@ -0,0 +1,46 @@ +/* + * 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.crdv2.generator.v1; + +import io.fabric8.crdv2.example.webserver.WebServerWithSpec; +import io.fabric8.crdv2.example.webserver.WebServerWithStatusProperty; +import io.fabric8.crdv2.generator.ResolvingContext; +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SpecReplicasPathTest { + + @Test + public void shoudDetectSpecReplicasPath() throws Exception { + JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), WebServerWithStatusProperty.class); + Optional path = resolver.getSinglePath(SpecReplicas.class); + assertTrue(path.isPresent()); + assertEquals(".replicas", path.get()); + } + + @Test + public void shoudDetectNestedSpecReplicasPath() throws Exception { + JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), WebServerWithSpec.class); + Optional path = resolver.getSinglePath(SpecReplicas.class); + assertTrue(path.isPresent()); + assertEquals(".spec.replicas", path.get()); + } +} diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml new file mode 100644 index 00000000000..a1b7ed14ba5 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml @@ -0,0 +1,215 @@ +# +# 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. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: complexkinds.example.com +spec: + group: example.com + names: + kind: ComplexKind + plural: complexkinds + singular: complexkind + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.message + name: Message + priority: 0 + type: string + - jsonPath: .status.state + name: State + priority: 0 + type: string + name: v1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + actuatorPort: + type: integer + configMapName: + type: string + metricsPath: + type: string + metricsPort: + type: integer + services: + items: + properties: + metadata: + description: The metadata of this Service + properties: + annotations: + additionalProperties: + type: string + type: object + creationTimestamp: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + finalizers: + items: + type: string + type: array + generateName: + type: string + generation: + type: integer + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + resourceVersion: + type: string + selfLink: + type: string + uid: + type: string + type: object + spec: + description: The spec of this Service + nullable: true + properties: + allocateLoadBalancerNodePorts: + type: boolean + clusterIP: + type: string + clusterIPs: + items: + type: string + type: array + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + type: integer + internalTrafficPolicy: + type: string + ipFamilies: + items: + type: string + type: array + ipFamilyPolicy: + type: string + loadBalancerClass: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + type: + type: string + type: object + type: object + type: array + statefulSet: + properties: + metadata: + description: The metadata of this StatefulSet + properties: + annotations: + additionalProperties: + type: string + type: object + creationTimestamp: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + finalizers: + items: + type: string + type: array + generateName: + type: string + generation: + type: integer + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + resourceVersion: + type: string + selfLink: + type: string + uid: + type: string + type: object + spec: + description: The spec of this StatefulSet + properties: + minReadySeconds: + type: integer + podManagementPolicy: + type: string + replicas: + type: integer + revisionHistoryLimit: + type: integer + serviceName: + type: string + type: object + type: object + type: object + status: + properties: + message: + type: string + state: + enum: + - CREATED + - ERROR + - ROLLING_UPDATE + - RUNNING + - SCALING + - STARTING + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml new file mode 100644 index 00000000000..f350e88cc3b --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml @@ -0,0 +1,215 @@ +# +# 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. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: complexkinds.example.com +spec: + additionalPrinterColumns: + - JSONPath: .status.message + name: Message + priority: 0 + type: string + - JSONPath: .status.state + name: State + priority: 0 + type: string + group: example.com + names: + kind: ComplexKind + plural: complexkinds + singular: complexkind + scope: Namespaced + subresources: + status: {} + validation: + openAPIV3Schema: + properties: + spec: + properties: + actuatorPort: + type: integer + configMapName: + type: string + metricsPath: + type: string + metricsPort: + type: integer + services: + items: + properties: + metadata: + description: The metadata of this Service + properties: + annotations: + additionalProperties: + type: string + type: object + creationTimestamp: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + finalizers: + items: + type: string + type: array + generateName: + type: string + generation: + type: integer + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + resourceVersion: + type: string + selfLink: + type: string + uid: + type: string + type: object + spec: + description: The spec of this Service + nullable: true + properties: + allocateLoadBalancerNodePorts: + type: boolean + clusterIP: + type: string + clusterIPs: + items: + type: string + type: array + externalIPs: + items: + type: string + type: array + externalName: + type: string + externalTrafficPolicy: + type: string + healthCheckNodePort: + type: integer + internalTrafficPolicy: + type: string + ipFamilies: + items: + type: string + type: array + ipFamilyPolicy: + type: string + loadBalancerClass: + type: string + loadBalancerIP: + type: string + loadBalancerSourceRanges: + items: + type: string + type: array + publishNotReadyAddresses: + type: boolean + selector: + additionalProperties: + type: string + type: object + sessionAffinity: + type: string + type: + type: string + type: object + type: object + type: array + statefulSet: + properties: + metadata: + description: The metadata of this StatefulSet + properties: + annotations: + additionalProperties: + type: string + type: object + creationTimestamp: + type: string + deletionGracePeriodSeconds: + type: integer + deletionTimestamp: + type: string + finalizers: + items: + type: string + type: array + generateName: + type: string + generation: + type: integer + labels: + additionalProperties: + type: string + type: object + name: + type: string + namespace: + type: string + resourceVersion: + type: string + selfLink: + type: string + uid: + type: string + type: object + spec: + description: The spec of this StatefulSet + properties: + minReadySeconds: + type: integer + podManagementPolicy: + type: string + replicas: + type: integer + revisionHistoryLimit: + type: integer + serviceName: + type: string + type: object + type: object + type: object + status: + properties: + message: + type: string + state: + enum: + - CREATED + - ERROR + - ROLLING_UPDATE + - RUNNING + - SCALING + - STARTING + type: string + type: object + type: object + versions: + - name: v1 + served: true + storage: true diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml new file mode 100644 index 00000000000..5e147ccdcbd --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml @@ -0,0 +1,161 @@ +# +# 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. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: k8svalidations.samples.fabric8.io +spec: + group: samples.fabric8.io + names: + kind: K8sValidation + plural: k8svalidations + singular: k8svalidation + scope: Cluster + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + deepLevel1: + properties: + deepLevel2: + properties: + simple: + type: string + x-kubernetes-validations: + - rule: self.startsWith('deep-') + valueL2: + type: string + required: + - valueL2 + type: object + valueL1: + type: string + required: + - deepLevel2 + - valueL1 + type: object + x-kubernetes-validations: + - messageExpression: '''valueL1 ('' + self.valueL1 + '') must be equal + to deepLevel2.valueL2 ('' + self.deepLevel2.valueL2 + '')''' + rule: self.valueL1 == self.deepLevel2.valueL2 + immutable: + type: string + x-kubernetes-validations: + - message: cannot be changed once set + rule: self == oldSelf + maxReplicas: + type: integer + minReplicas: + type: integer + monotonicCounter: + type: integer + x-kubernetes-validations: + - message: cannot decrease value once set + reason: FieldValueForbidden + rule: self >= oldSelf + multiple: + type: string + x-kubernetes-validations: + - rule: self.startsWith('start-') + - rule: self.endsWith('-end') + namePrefix: + type: string + onAbstractClass: + properties: + dummy: + type: string + required: + - dummy + type: object + x-kubernetes-validations: + - rule: self.dummy.startsWith('abstract-') + onAttributeAndClass: + properties: + dummy: + type: string + required: + - dummy + type: object + x-kubernetes-validations: + - rule: self.dummy.startsWith('on-class-') + - rule: self.dummy.startsWith('on-attr-') + onAttributeAndGetter: + type: string + x-kubernetes-validations: + - rule: self.startsWith('start-') + - rule: self.endsWith('-end') + onGetter: + type: string + x-kubernetes-validations: + - rule: self.startsWith('on-getter-') + priority: + enum: + - high + - low + - medium + type: string + x-kubernetes-validations: + - message: cannot transition directly between 'low' and 'high' + rule: '!(self == ''high'' && oldSelf == ''low'') && !(self == ''low'' + && oldSelf == ''high'')' + replicas: + type: integer + simple: + type: string + x-kubernetes-validations: + - rule: self.startsWith('simple-') + required: + - minReplicas + - onAttributeAndGetter + - replicas + - deepLevel1 + - onAttributeAndClass + - simple + - multiple + - onAbstractClass + - onGetter + - maxReplicas + - priority + - namePrefix + type: object + x-kubernetes-validations: + - fieldPath: .replicas + rule: self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas + - message: replicas must be greater than or equal to minReplicas + rule: self.minReplicas <= self.replicas + - message: replicas must be smaller than or equal to maxReplicas + rule: self.replicas <= self.maxReplicas + status: + properties: + availableReplicas: + type: integer + type: object + type: object + x-kubernetes-validations: + - messageExpression: '''name must start with '' + self.spec.namePrefix' + reason: FieldValueForbidden + rule: self.metadata.name.startsWith(self.spec.namePrefix) + - message: updates not allowed in degraded state + rule: self.status.availableReplicas >= self.spec.minReplicas + served: true + storage: true + subresources: + status: {} diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml new file mode 100644 index 00000000000..868038a1294 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml @@ -0,0 +1,161 @@ +# +# 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. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: k8svalidations.samples.fabric8.io +spec: + group: samples.fabric8.io + names: + kind: K8sValidation + plural: k8svalidations + singular: k8svalidation + scope: Cluster + subresources: + status: {} + validation: + openAPIV3Schema: + properties: + spec: + properties: + deepLevel1: + properties: + deepLevel2: + properties: + simple: + type: string + x-kubernetes-validations: + - rule: self.startsWith('deep-') + valueL2: + type: string + required: + - valueL2 + type: object + valueL1: + type: string + required: + - deepLevel2 + - valueL1 + type: object + x-kubernetes-validations: + - messageExpression: '''valueL1 ('' + self.valueL1 + '') must be equal + to deepLevel2.valueL2 ('' + self.deepLevel2.valueL2 + '')''' + rule: self.valueL1 == self.deepLevel2.valueL2 + immutable: + type: string + x-kubernetes-validations: + - message: cannot be changed once set + rule: self == oldSelf + maxReplicas: + type: integer + minReplicas: + type: integer + monotonicCounter: + type: integer + x-kubernetes-validations: + - message: cannot decrease value once set + reason: FieldValueForbidden + rule: self >= oldSelf + multiple: + type: string + x-kubernetes-validations: + - rule: self.startsWith('start-') + - rule: self.endsWith('-end') + namePrefix: + type: string + onAbstractClass: + properties: + dummy: + type: string + required: + - dummy + type: object + x-kubernetes-validations: + - rule: self.dummy.startsWith('abstract-') + onAttributeAndClass: + properties: + dummy: + type: string + required: + - dummy + type: object + x-kubernetes-validations: + - rule: self.dummy.startsWith('on-class-') + - rule: self.dummy.startsWith('on-attr-') + onAttributeAndGetter: + type: string + x-kubernetes-validations: + - rule: self.startsWith('start-') + - rule: self.endsWith('-end') + onGetter: + type: string + x-kubernetes-validations: + - rule: self.startsWith('on-getter-') + priority: + enum: + - high + - low + - medium + type: string + x-kubernetes-validations: + - message: cannot transition directly between 'low' and 'high' + rule: '!(self == ''high'' && oldSelf == ''low'') && !(self == ''low'' + && oldSelf == ''high'')' + replicas: + type: integer + simple: + type: string + x-kubernetes-validations: + - rule: self.startsWith('simple-') + required: + - minReplicas + - onAttributeAndGetter + - replicas + - deepLevel1 + - onAttributeAndClass + - simple + - multiple + - onAbstractClass + - onGetter + - maxReplicas + - priority + - namePrefix + type: object + x-kubernetes-validations: + - fieldPath: .replicas + rule: self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas + - message: replicas must be greater than or equal to minReplicas + rule: self.minReplicas <= self.replicas + - message: replicas must be smaller than or equal to maxReplicas + rule: self.replicas <= self.maxReplicas + status: + properties: + availableReplicas: + type: integer + type: object + type: object + x-kubernetes-validations: + - messageExpression: '''name must start with '' + self.spec.namePrefix' + reason: FieldValueForbidden + rule: self.metadata.name.startsWith(self.spec.namePrefix) + - message: updates not allowed in degraded state + rule: self.status.availableReplicas >= self.spec.minReplicas + versions: + - name: v1alpha1 + served: true + storage: true diff --git a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml new file mode 100644 index 00000000000..50a58ec3692 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml @@ -0,0 +1,57 @@ +# +# 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. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: multiples.sample.fabric8.io +spec: + group: sample.fabric8.io + names: + kind: Multiple + plural: multiples + singular: multiple + scope: Cluster + versions: + - name: v2 + schema: + openAPIV3Schema: + properties: + spec: + properties: + v2: + type: string + type: object + status: + type: object + type: object + served: true + storage: true + - name: v1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + v1: + type: string + type: object + status: + type: object + type: object + served: true + storage: false diff --git a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml new file mode 100644 index 00000000000..b702b62b8a6 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml @@ -0,0 +1,57 @@ +# +# 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. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: apiextensions.k8s.io/v1beta1 +kind: CustomResourceDefinition +metadata: + name: multiples.sample.fabric8.io +spec: + group: sample.fabric8.io + names: + kind: Multiple + plural: multiples + singular: multiple + scope: Cluster + versions: + - name: v2 + schema: + openAPIV3Schema: + properties: + spec: + properties: + v2: + type: string + type: object + status: + type: object + type: object + served: true + storage: true + - name: v1 + schema: + openAPIV3Schema: + properties: + spec: + properties: + v1: + type: string + type: object + status: + type: object + type: object + served: true + storage: false diff --git a/crd-generator/api-v2/src/test/resources/simplelogger.properties b/crd-generator/api-v2/src/test/resources/simplelogger.properties new file mode 100644 index 00000000000..88095757cf8 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/simplelogger.properties @@ -0,0 +1,43 @@ +# +# 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. +# + +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=debug +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +#org.slf4j.simpleLogger.showDateTime=false +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z +# Set to true if you want to output the current thread name. +# Defaults to true. +#org.slf4j.simpleLogger.showThreadName=true +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +org.slf4j.simpleLogger.showLogName=false +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +#org.slf4j.simpleLogger.showShortLogName=false diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java index 173d31abff8..643d00c7f28 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java @@ -121,6 +121,7 @@ public static void assertFileEquals(final File expectedFile, final File actualFi // skip license headers String expectedLine = skipCommentsAndEmptyLines(expectedReader); String actualLine = skipCommentsAndEmptyLines(actualReader); + // compare both files final String message = String.format("Expected %s and actual %s files are not equal", expectedFile, actualFile); while (expectedLine != null || actualLine != null) { diff --git a/crd-generator/pom.xml b/crd-generator/pom.xml index c86434e7c0f..c1059dddbc0 100644 --- a/crd-generator/pom.xml +++ b/crd-generator/pom.xml @@ -33,17 +33,7 @@ apt api + api-v2 + test - - - - tests - - (,17] - - - test - - - diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java index 5992ca2194a..0d9061925e9 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java @@ -118,22 +118,22 @@ public CustomResource() { this.status = initStatus(); } - public static boolean getServed(Class clazz) { + public static boolean getServed(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation == null || annotation.served(); } - public static boolean getStorage(Class clazz) { + public static boolean getStorage(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation == null || annotation.storage(); } - public static boolean getDeprecated(Class clazz) { + public static boolean getDeprecated(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation == null || annotation.deprecated(); } - public static String getDeprecationWarning(Class clazz) { + public static String getDeprecationWarning(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation != null && Utils.isNotNullOrEmpty(annotation.deprecationWarning()) ? annotation.deprecationWarning() @@ -255,7 +255,7 @@ public String getCRDName() { * @param clazz the CustomResource class which short names we want to retrieve * @return the short names associated with this CustomResource or an empty array if none was provided */ - public static String[] getShortNames(Class clazz) { + public static String[] getShortNames(Class clazz) { return Optional.ofNullable(clazz.getAnnotation(ShortNames.class)) .map(ShortNames::value) .orElse(new String[] {}); diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java index 609bf20a0ef..d3a00fa548f 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java @@ -379,7 +379,7 @@ public UnmatchedFieldTypeModule getUnmatchedFieldTypeModule() { return unmatchedFieldTypeModule; } - ObjectMapper getMapper() { + protected ObjectMapper getMapper() { return mapper; } diff --git a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java index 8f13b7000ce..ad6ff66d1c5 100644 --- a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java +++ b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java @@ -18,10 +18,13 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -178,12 +181,21 @@ public static Duration parse(String duration) throws ParseException { return new Duration(accumulator); } - public static class Serializer extends JsonSerializer { + public static class Serializer extends StdSerializer { + + public Serializer() { + super(Duration.class); + } @Override public void serialize(Duration duration, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(String.format("%sns", duration.getValue())); } + + @Override + public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { + visitor.expectStringFormat(typeHint); + } } private enum TimeUnits { diff --git a/pom.xml b/pom.xml index 602b62a18e5..5d567e948a9 100644 --- a/pom.xml +++ b/pom.xml @@ -254,6 +254,11 @@ crd-generator-api ${project.version} + + io.fabric8 + crd-generator-api-v2 + ${project.version} + io.fabric8 kubernetes-log4j From 3f44bfe4c63e560fa8e66d713dc693d6da302b71 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 24 Apr 2024 12:44:54 -0400 Subject: [PATCH 02/16] fixing formatting --- .../AbstractCustomResourceHandler.java | 2 +- .../crdv2/generator/AbstractJsonSchema.java | 18 +++++++------ .../crdv2/generator/CustomResourceInfo.java | 2 +- .../crdv2/generator/InternalSchemaSwaps.java | 3 ++- .../crdv2/generator/ResolvingContext.java | 4 +-- .../crdv2/generator/ResourcesTest.java | 1 - .../crdv2/generator/v1/JsonSchemaTest.java | 25 ++++++++++++------- 7 files changed, 33 insertions(+), 22 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index 7d97d9fbf65..a0cfa4741db 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -38,7 +38,7 @@ protected AbstractCustomResourceHandler(Resources resources) { protected void handlePrinterColumns(String name, String version, Map additionalPrinterColumns) { additionalPrinterColumns.forEach((path, property) -> { - PrinterColumn printerColumn = ((PrinterColumn)property.annotation); + PrinterColumn printerColumn = ((PrinterColumn) property.annotation); String column = printerColumn.name(); if (Utils.isNullOrEmpty(column)) { column = path.substring(path.lastIndexOf(".")); diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index d9a7c2bf524..6e1236c0c44 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -144,13 +144,15 @@ private T resolveRoot(Class definition) { if (schema instanceof GeneratorObjectSchema) { return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata"); } - return resolveProperty(new LinkedHashMap<>(), schemaSwaps, null, resolvingContext.serializationConfig.constructType(definition), schema); + return resolveProperty(new LinkedHashMap<>(), schemaSwaps, null, + resolvingContext.serializationConfig.constructType(definition), schema); } /** * Walks up the class hierarchy to consume the repeating annotation */ - private static void consumeRepeatingAnnotation(Class beanClass, Class annotation, Consumer consumer) { + private static void consumeRepeatingAnnotation(Class beanClass, Class annotation, + Consumer consumer) { while (beanClass != null && beanClass != Object.class) { Stream.of(beanClass.getAnnotationsByType(annotation)).forEach(consumer); beanClass = beanClass.getSuperclass(); @@ -163,7 +165,7 @@ void collectValidationRules(BeanProperty beanProperty, List validationRules) // nor does jackson provide the field if (beanProperty.getMember() instanceof AnnotatedMethod) { // field first - Method m = ((AnnotatedMethod)beanProperty.getMember()).getMember(); + Method m = ((AnnotatedMethod) beanProperty.getMember()).getMember(); String name = m.getName(); if (name.startsWith("get") || name.startsWith("set")) { name = name.substring(3); @@ -279,7 +281,7 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa schemaSwaps = schemaSwaps.branchAnnotations(); final InternalSchemaSwaps swaps = schemaSwaps; - GeneratorObjectSchema gos = (GeneratorObjectSchema)jacksonSchema.asObjectSchema(); + GeneratorObjectSchema gos = (GeneratorObjectSchema) jacksonSchema.asObjectSchema(); AnnotationIntrospector ai = resolvingContext.serializationConfig.getAnnotationIntrospector(); BeanDescription bd = resolvingContext.serializationConfig.introspect(gos.javaType); boolean preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null; @@ -420,7 +422,7 @@ private T resolveProperty(LinkedHashMap visited, InternalSchemaS throw new IllegalStateException("not yet supported"); } else if (jacksonSchema instanceof ReferenceSchema) { // de-reference the reference schema - these can be naturally non-cyclic, for example siblings - ReferenceSchema ref = (ReferenceSchema)jacksonSchema; + ReferenceSchema ref = (ReferenceSchema) jacksonSchema; GeneratorObjectSchema referenced = resolvingContext.seen.get(ref.get$ref()); Utils.checkNotNull(referenced, "Could not find previously generated schema"); jacksonSchema = referenced; @@ -432,7 +434,8 @@ private T resolveProperty(LinkedHashMap visited, InternalSchemaS } final JavaType valueType = type.getContentType(); - JsonSchema mapValueSchema = ((SchemaAdditionalProperties)((ObjectSchema)jacksonSchema).getAdditionalProperties()).getJsonSchema(); + JsonSchema mapValueSchema = ((SchemaAdditionalProperties) ((ObjectSchema) jacksonSchema).getAdditionalProperties()) + .getJsonSchema(); T component = resolveProperty(visited, schemaSwaps, name, valueType, mapValueSchema); return mapLikeProperty(component); } @@ -469,7 +472,8 @@ private Set findIngoredEnumConstants(JavaType type) { // hack to figure out the enum constant try { Object value = field.get(null); - toIgnore.add(resolvingContext.kubernetesSerialization.unmarshal(resolvingContext.kubernetesSerialization.asJson(value), String.class)); + toIgnore.add(resolvingContext.kubernetesSerialization + .unmarshal(resolvingContext.kubernetesSerialization.asJson(value), String.class)); } catch (IllegalArgumentException | IllegalAccessException e) { } } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java index b5eaeac18c3..63cf08a77e2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java @@ -181,7 +181,7 @@ public static CustomResourceInfo fromClass(Class customRe // instance level methods - TODO: deprecate? if (instance instanceof CustomResource) { - CustomResource cr = (CustomResource)instance; + CustomResource cr = (CustomResource) instance; singular = cr.getSingular(); deprecated = cr.isDeprecated(); deprecationWarning = cr.getDeprecationWarning(); diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java index c4bc69af77e..59621c0b768 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java @@ -163,7 +163,8 @@ public Class getTargetType() { @Override public String toString() { - return "@SchemaSwap(originalType=" + originalType.getName() + ", fieldName=\"" + fieldName + "\", targetType=" + targetType.getName() + return "@SchemaSwap(originalType=" + originalType.getName() + ", fieldName=\"" + fieldName + "\", targetType=" + + targetType.getName() + ") on " + definitionType.getName(); } } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java index 5af27a8e5e3..5c8b9572d0a 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -75,8 +75,8 @@ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) { // TODO: jackson should pass in directly here if there's an anyGetter / setter // so that we may directly mark preserve unknown JsonObjectFormatVisitor result = super.expectObjectFormat(convertedType); - ((GeneratorObjectSchema)schema).javaType = convertedType; - seen.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema)schema); + ((GeneratorObjectSchema) schema).javaType = convertedType; + seen.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema) schema); return result; } } diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java index 6d745e697fd..f11cb52f466 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java @@ -15,7 +15,6 @@ */ package io.fabric8.crdv2.generator; -import io.fabric8.crdv2.generator.Resources; import io.fabric8.crdv2.generator.v1.decorator.AddAdditionPrinterColumnDecorator; import io.fabric8.crdv2.generator.v1.decorator.SortPrinterColumnsDecorator; import org.junit.jupiter.api.Test; diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java index b3c85f1b980..ad00af91735 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java @@ -138,7 +138,8 @@ void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingExcepti assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value")).build(), spec.get("defaultValue")); assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value2")).build(), spec.get("defaultValue2")); assertEquals(type.apply("string").withEnum(TextNode.valueOf("non"), TextNode.valueOf("oui")).build(), spec.get("anEnum")); - assertEquals(type.apply("string").withFormat("date-time").withPattern("yyyy-MM-dd'T'HH:mm:ssVV").build(), spec.get("issuedAt")); + assertEquals(type.apply("string").withFormat("date-time").withPattern("yyyy-MM-dd'T'HH:mm:ssVV").build(), + spec.get("issuedAt")); // the shape does not influence the type in these cases assertEquals(type.apply("string").build(), spec.get("bool")); assertEquals(type.apply("string").build(), spec.get("num")); @@ -323,12 +324,12 @@ public static class PreserveUnknown { @JsonAnyGetter public Map getAdditionalProperties() { - return this.values; + return this.values; } @JsonAnySetter public void setAdditionalProperty(String name, Object value) { - this.values.put(name, value); + this.values.put(name, value); } public Map getValues() { @@ -457,18 +458,24 @@ private static class Cyclic3 { void testCyclicProperties() { IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> JsonSchema.from(Cyclic1.class)); - assertEquals("Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1 starting a field parent >>\n" - + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1.parent", exception.getMessage()); + assertEquals( + "Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1.parent", + exception.getMessage()); exception = assertThrows(IllegalArgumentException.class, () -> JsonSchema.from(Cyclic2.class)); - assertEquals("Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2 starting a field parent >>\n" - + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2.parent", exception.getMessage()); + assertEquals( + "Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2.parent", + exception.getMessage()); exception = assertThrows(IllegalArgumentException.class, () -> JsonSchema.from(Cyclic3.class)); - assertEquals("Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3 starting a field parent >>\n" - + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3.parent", exception.getMessage()); + assertEquals( + "Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3.parent", + exception.getMessage()); } @SchemaSwap(originalType = Cyclic3.class, fieldName = "parent") From 06c0995ce300829777d944d0c03ddc95c36ca154 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 24 Apr 2024 15:16:26 -0400 Subject: [PATCH 03/16] fully removing v1beta1 and ordering required --- .../AbstractCustomResourceHandler.java | 2 +- .../generator/CRDGeneratorAssertions.java | 6 +- .../crdv2/generator/CRDGeneratorTest.java | 10 +- .../complexkinds.example.com-v1beta1.yml | 215 ------------------ .../k8svalidations.samples.fabric8.io-v1.yml | 14 +- ...validations.samples.fabric8.io-v1beta1.yml | 161 ------------- .../multiples.sample.fabric8.io-v1beta1.yml | 57 ----- ...validations.samples.fabric8.io-v1beta1.yml | 14 +- 8 files changed, 23 insertions(+), 456 deletions(-) delete mode 100644 crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml delete mode 100644 crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml delete mode 100644 crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index a0cfa4741db..8eff433feb2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -23,7 +23,7 @@ import java.util.Map; /** - * This class encapsulates the common behavior between v1beta1 and v1 CRD generation logic. The + * This class encapsulates the common behavior between different CRD generation logic. The * intent is that each CRD spec version is implemented as a sub-class of this one. */ public abstract class AbstractCustomResourceHandler { diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java index 426eb2fcdfa..4cd39add755 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java @@ -37,7 +37,7 @@ private CRDGeneratorAssertions() { } /** - * Generates CRD files for v1 and v1beta1 and compares them with files in classpath. + * Generates CRD files for v1 and compares them with files in classpath. * * @param crClasses custom resource classes under test * @param crdGenerator a CRDGenerator instance @@ -50,7 +50,7 @@ public static void assertCRDOutputEquals(CRDGenerator crdGenerator, } /** - * Generates CRD files for v1 and v1beta1 and compares them with files in classpath. + * Generates CRD files for v1 and compares them with files in classpath. * * @param crClasses custom resource classes under test * @param crdGenerator a CRDGenerator instance @@ -84,7 +84,7 @@ public static void assertCRDOutputEquals(CRDGenerator crdGenerator, final CRDGenerationInfo crdInfo = crdGenerator .inOutputDir(outputDir) .customResourceClasses(crClasses) - .forCRDVersions("v1", "v1beta1") + .forCRDVersions("v1") .detailedGenerate(); final File actualCRDFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java index a9b7459e576..1869b67bc75 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java @@ -92,7 +92,7 @@ void choosingCRDVersionsShouldWork() { assertTrue(handlers.containsKey(version)); // v1beta1 not supported - generator.forCRDVersions(version, "v1beta1", version, "v3", null); + generator.forCRDVersions(version, version, "v3", null); handlers = generator.getHandlers(); assertEquals(1, handlers.size()); assertTrue(handlers.containsKey(version)); @@ -247,7 +247,7 @@ private CRDGenerator newCRDGenerator() { void generatingACycleInListShouldFail() { final CRDGenerator generator = newCRDGenerator() .customResourceClasses(CyclicList.class) - .forCRDVersions("v1", "v1beta1") + .forCRDVersions("v1") .withOutput(output); assertThrows( @@ -428,7 +428,7 @@ void checkGenerationIsDeterministic() throws Exception { final String crdName = CustomResourceInfo.fromClass(Complex.class).crdName(); final CRDGenerationInfo crdInfo = newCRDGenerator() .inOutputDir(outputDir) - .forCRDVersions("v1", "v1beta1") + .forCRDVersions("v1") .customResourceClasses(Complex.class) .detailedGenerate(); final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); @@ -458,7 +458,7 @@ void checkGenerationMultipleVersionsOfCRDsIsDeterministic() throws Exception { .inOutputDir(outputDir) .customResourceClasses(Multiple.class, io.fabric8.crdv2.example.multiple.v2.Multiple.class) - .forCRDVersions("v1", "v1beta1") + .forCRDVersions("v1") .detailedGenerate(); final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); @@ -484,7 +484,7 @@ void checkK8sValidationRules() throws Exception { final CRDGenerationInfo crdInfo = newCRDGenerator() .inOutputDir(outputDir) .customResourceClasses(K8sValidation.class) - .forCRDVersions("v1", "v1beta1") + .forCRDVersions("v1") .detailedGenerate(); final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml deleted file mode 100644 index f350e88cc3b..00000000000 --- a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1beta1.yml +++ /dev/null @@ -1,215 +0,0 @@ -# -# 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. -# - -# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: complexkinds.example.com -spec: - additionalPrinterColumns: - - JSONPath: .status.message - name: Message - priority: 0 - type: string - - JSONPath: .status.state - name: State - priority: 0 - type: string - group: example.com - names: - kind: ComplexKind - plural: complexkinds - singular: complexkind - scope: Namespaced - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - actuatorPort: - type: integer - configMapName: - type: string - metricsPath: - type: string - metricsPort: - type: integer - services: - items: - properties: - metadata: - description: The metadata of this Service - properties: - annotations: - additionalProperties: - type: string - type: object - creationTimestamp: - type: string - deletionGracePeriodSeconds: - type: integer - deletionTimestamp: - type: string - finalizers: - items: - type: string - type: array - generateName: - type: string - generation: - type: integer - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - resourceVersion: - type: string - selfLink: - type: string - uid: - type: string - type: object - spec: - description: The spec of this Service - nullable: true - properties: - allocateLoadBalancerNodePorts: - type: boolean - clusterIP: - type: string - clusterIPs: - items: - type: string - type: array - externalIPs: - items: - type: string - type: array - externalName: - type: string - externalTrafficPolicy: - type: string - healthCheckNodePort: - type: integer - internalTrafficPolicy: - type: string - ipFamilies: - items: - type: string - type: array - ipFamilyPolicy: - type: string - loadBalancerClass: - type: string - loadBalancerIP: - type: string - loadBalancerSourceRanges: - items: - type: string - type: array - publishNotReadyAddresses: - type: boolean - selector: - additionalProperties: - type: string - type: object - sessionAffinity: - type: string - type: - type: string - type: object - type: object - type: array - statefulSet: - properties: - metadata: - description: The metadata of this StatefulSet - properties: - annotations: - additionalProperties: - type: string - type: object - creationTimestamp: - type: string - deletionGracePeriodSeconds: - type: integer - deletionTimestamp: - type: string - finalizers: - items: - type: string - type: array - generateName: - type: string - generation: - type: integer - labels: - additionalProperties: - type: string - type: object - name: - type: string - namespace: - type: string - resourceVersion: - type: string - selfLink: - type: string - uid: - type: string - type: object - spec: - description: The spec of this StatefulSet - properties: - minReadySeconds: - type: integer - podManagementPolicy: - type: string - replicas: - type: integer - revisionHistoryLimit: - type: integer - serviceName: - type: string - type: object - type: object - type: object - status: - properties: - message: - type: string - state: - enum: - - CREATED - - ERROR - - ROLLING_UPDATE - - RUNNING - - SCALING - - STARTING - type: string - type: object - type: object - versions: - - name: v1 - served: true - storage: true diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml index 5e147ccdcbd..0ae072eb120 100644 --- a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml +++ b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml @@ -123,18 +123,18 @@ spec: x-kubernetes-validations: - rule: self.startsWith('simple-') required: - - minReplicas - - onAttributeAndGetter - - replicas - deepLevel1 - - onAttributeAndClass - - simple + - maxReplicas + - minReplicas - multiple + - namePrefix - onAbstractClass + - onAttributeAndClass + - onAttributeAndGetter - onGetter - - maxReplicas - priority - - namePrefix + - replicas + - simple type: object x-kubernetes-validations: - fieldPath: .replicas diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml deleted file mode 100644 index 868038a1294..00000000000 --- a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml +++ /dev/null @@ -1,161 +0,0 @@ -# -# 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. -# - -# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: k8svalidations.samples.fabric8.io -spec: - group: samples.fabric8.io - names: - kind: K8sValidation - plural: k8svalidations - singular: k8svalidation - scope: Cluster - subresources: - status: {} - validation: - openAPIV3Schema: - properties: - spec: - properties: - deepLevel1: - properties: - deepLevel2: - properties: - simple: - type: string - x-kubernetes-validations: - - rule: self.startsWith('deep-') - valueL2: - type: string - required: - - valueL2 - type: object - valueL1: - type: string - required: - - deepLevel2 - - valueL1 - type: object - x-kubernetes-validations: - - messageExpression: '''valueL1 ('' + self.valueL1 + '') must be equal - to deepLevel2.valueL2 ('' + self.deepLevel2.valueL2 + '')''' - rule: self.valueL1 == self.deepLevel2.valueL2 - immutable: - type: string - x-kubernetes-validations: - - message: cannot be changed once set - rule: self == oldSelf - maxReplicas: - type: integer - minReplicas: - type: integer - monotonicCounter: - type: integer - x-kubernetes-validations: - - message: cannot decrease value once set - reason: FieldValueForbidden - rule: self >= oldSelf - multiple: - type: string - x-kubernetes-validations: - - rule: self.startsWith('start-') - - rule: self.endsWith('-end') - namePrefix: - type: string - onAbstractClass: - properties: - dummy: - type: string - required: - - dummy - type: object - x-kubernetes-validations: - - rule: self.dummy.startsWith('abstract-') - onAttributeAndClass: - properties: - dummy: - type: string - required: - - dummy - type: object - x-kubernetes-validations: - - rule: self.dummy.startsWith('on-class-') - - rule: self.dummy.startsWith('on-attr-') - onAttributeAndGetter: - type: string - x-kubernetes-validations: - - rule: self.startsWith('start-') - - rule: self.endsWith('-end') - onGetter: - type: string - x-kubernetes-validations: - - rule: self.startsWith('on-getter-') - priority: - enum: - - high - - low - - medium - type: string - x-kubernetes-validations: - - message: cannot transition directly between 'low' and 'high' - rule: '!(self == ''high'' && oldSelf == ''low'') && !(self == ''low'' - && oldSelf == ''high'')' - replicas: - type: integer - simple: - type: string - x-kubernetes-validations: - - rule: self.startsWith('simple-') - required: - - minReplicas - - onAttributeAndGetter - - replicas - - deepLevel1 - - onAttributeAndClass - - simple - - multiple - - onAbstractClass - - onGetter - - maxReplicas - - priority - - namePrefix - type: object - x-kubernetes-validations: - - fieldPath: .replicas - rule: self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas - - message: replicas must be greater than or equal to minReplicas - rule: self.minReplicas <= self.replicas - - message: replicas must be smaller than or equal to maxReplicas - rule: self.replicas <= self.maxReplicas - status: - properties: - availableReplicas: - type: integer - type: object - type: object - x-kubernetes-validations: - - messageExpression: '''name must start with '' + self.spec.namePrefix' - reason: FieldValueForbidden - rule: self.metadata.name.startsWith(self.spec.namePrefix) - - message: updates not allowed in degraded state - rule: self.status.availableReplicas >= self.spec.minReplicas - versions: - - name: v1alpha1 - served: true - storage: true diff --git a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml deleted file mode 100644 index b702b62b8a6..00000000000 --- a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1beta1.yml +++ /dev/null @@ -1,57 +0,0 @@ -# -# 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. -# - -# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1beta1 -kind: CustomResourceDefinition -metadata: - name: multiples.sample.fabric8.io -spec: - group: sample.fabric8.io - names: - kind: Multiple - plural: multiples - singular: multiple - scope: Cluster - versions: - - name: v2 - schema: - openAPIV3Schema: - properties: - spec: - properties: - v2: - type: string - type: object - status: - type: object - type: object - served: true - storage: true - - name: v1 - schema: - openAPIV3Schema: - properties: - spec: - properties: - v1: - type: string - type: object - status: - type: object - type: object - served: true - storage: false diff --git a/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml b/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml index 868038a1294..ec040281662 100644 --- a/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml +++ b/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml @@ -123,18 +123,18 @@ spec: x-kubernetes-validations: - rule: self.startsWith('simple-') required: - - minReplicas - - onAttributeAndGetter - - replicas - deepLevel1 - - onAttributeAndClass - - simple + - maxReplicas + - minReplicas - multiple - onAbstractClass + - onAttributeAndClass + - onAttributeAndGetter - onGetter - - maxReplicas - - priority - namePrefix + - priority + - replicas + - simple type: object x-kubernetes-validations: - fieldPath: .replicas From b4eafa20163968c1384779e9d113ca5eed960f82 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 24 Apr 2024 18:25:41 -0400 Subject: [PATCH 04/16] accepting the change in the printer column type also removing another reference to CustomResource --- .../main/java/io/fabric8/crdv2/generator/CRDGenerator.java | 5 ++--- .../java/io/fabric8/crdv2/generator/CRDGeneratorTest.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index 64d259db23d..0d9bd221393 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -23,7 +23,6 @@ import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import io.fabric8.crdv2.generator.v1.CustomResourceHandler; import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.utils.ApiVersionUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -118,7 +117,7 @@ Map getHandlers() { // this is public API, so we cannot change the signature, so there is no way to prevent the possible heap pollution // (we also cannot use @SafeVarargs, because that requires the method to be final, which is another signature change) @SuppressWarnings("unchecked") - public final CRDGenerator customResourceClasses(Class>... crClasses) { + public final CRDGenerator customResourceClasses(Class... crClasses) { return customResources(Stream.of(crClasses).map(CustomResourceInfo::fromClass).toArray(CustomResourceInfo[]::new)); } @@ -206,7 +205,7 @@ default URI crdURI(String crdName) { } public abstract static class AbstractCRDOutput implements CRDOutput { - private final Map crds = new HashMap<>(7); + private final Map crds = new HashMap<>(); @Override public T outputFor(String crdName) throws IOException { diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java index 1869b67bc75..924fc599559 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java @@ -377,7 +377,7 @@ void jokerequestCRDShouldWork() { assertEquals("jokeCategory", columnDefinition.getName()); assertEquals(1, columnDefinition.getPriority()); columnDefinition = printerColumns.get(1); - assertEquals("string", columnDefinition.getType()); + assertEquals("array", columnDefinition.getType()); assertEquals(".spec.excluded", columnDefinition.getJsonPath()); assertEquals("excludedTopics", columnDefinition.getName()); assertEquals(0, columnDefinition.getPriority()); From 906f86cea474a56635b6c3fa490b43e600b9c1b2 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Thu, 25 Apr 2024 07:05:25 -0400 Subject: [PATCH 05/16] removing erroneous v1 changes --- .../crd/generator/CRDGeneratorAssertions.java | 1 - .../k8svalidations.samples.fabric8.io-v1beta1.yml | 14 +++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java index 643d00c7f28..173d31abff8 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorAssertions.java @@ -121,7 +121,6 @@ public static void assertFileEquals(final File expectedFile, final File actualFi // skip license headers String expectedLine = skipCommentsAndEmptyLines(expectedReader); String actualLine = skipCommentsAndEmptyLines(actualReader); - // compare both files final String message = String.format("Expected %s and actual %s files are not equal", expectedFile, actualFile); while (expectedLine != null || actualLine != null) { diff --git a/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml b/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml index ec040281662..868038a1294 100644 --- a/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml +++ b/crd-generator/api/src/test/resources/k8svalidations.samples.fabric8.io-v1beta1.yml @@ -123,18 +123,18 @@ spec: x-kubernetes-validations: - rule: self.startsWith('simple-') required: - - deepLevel1 - - maxReplicas - minReplicas + - onAttributeAndGetter + - replicas + - deepLevel1 + - onAttributeAndClass + - simple - multiple - onAbstractClass - - onAttributeAndClass - - onAttributeAndGetter - onGetter - - namePrefix + - maxReplicas - priority - - replicas - - simple + - namePrefix type: object x-kubernetes-validations: - fieldPath: .replicas From 71a7c1db08dde3d2e100cb2f7dcc16bbe9bed6d1 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Fri, 26 Apr 2024 06:08:29 -0400 Subject: [PATCH 06/16] removing decorator classes --- .../AbstractCustomResourceHandler.java | 46 +---- .../fabric8/crdv2/generator/CRDGenerator.java | 41 +++-- .../{utils/Types.java => CRDUtils.java} | 25 ++- .../crdv2/generator/CustomResourceInfo.java | 5 +- .../io/fabric8/crdv2/generator/Resources.java | 130 -------------- .../crdv2/generator/decorator/Decorator.java | 66 -------- .../decorator/NamedResourceDecorator.java | 136 --------------- .../decorator/ResourceProvidingDecorator.java | 39 ----- .../crdv2/generator/utils/Generics.java | 115 ------------- .../crdv2/generator/utils/Metadata.java | 124 -------------- .../generator/v1/CustomResourceHandler.java | 160 ++++++++++++------ .../AddAdditionPrinterColumnDecorator.java | 128 -------------- ...omResourceDefinitionResourceDecorator.java | 87 ---------- ...tomResourceDefinitionVersionDecorator.java | 63 ------- .../AddLabelSelectorPathDecorator.java | 45 ----- ...tomResourceDefinitionVersionDecorator.java | 46 ----- .../AddSpecReplicasPathDecorator.java | 45 ----- .../AddStatusReplicasPathDecorator.java | 44 ----- .../AddStatusSubresourceDecorator.java | 43 ----- .../decorator/AddSubresourcesDecorator.java | 44 ----- .../CustomResourceDefinitionDecorator.java | 30 ---- ...tomResourceDefinitionVersionDecorator.java | 133 --------------- .../EnsureSingleStorageVersionDecorator.java | 61 ------- .../SetDeprecatedVersionDecorator.java | 45 ----- .../decorator/SetServedVersionDecorator.java | 39 ----- .../decorator/SetStorageVersionDecorator.java | 39 ----- ...tomResourceDefinitionVersionDecorator.java | 45 ----- .../SortPrinterColumnsDecorator.java | 81 --------- .../crdv2/generator/ResourcesTest.java | 55 ------ .../crdv2/generator/utils/TypesTest.java | 5 +- crd-generator/test/pom.xml | 14 +- .../keycloak/KeycloakRealmCRDTest.java | 35 +++- 32 files changed, 200 insertions(+), 1814 deletions(-) rename crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/{utils/Types.java => CRDUtils.java} (79%) delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java delete mode 100644 crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java delete mode 100644 crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index 8eff433feb2..0e60ed2502c 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -15,12 +15,9 @@ */ package io.fabric8.crdv2.generator; -import io.fabric8.crd.generator.annotation.PrinterColumn; -import io.fabric8.crdv2.generator.AbstractJsonSchema.AnnotationMetadata; -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.client.utils.Utils; +import io.fabric8.kubernetes.api.model.HasMetadata; -import java.util.Map; +import java.util.stream.Stream; /** * This class encapsulates the common behavior between different CRD generation logic. The @@ -28,45 +25,8 @@ */ public abstract class AbstractCustomResourceHandler { - protected final Resources resources; - - protected AbstractCustomResourceHandler(Resources resources) { - this.resources = resources; - } - public abstract void handle(CustomResourceInfo config); - protected void handlePrinterColumns(String name, String version, Map additionalPrinterColumns) { - additionalPrinterColumns.forEach((path, property) -> { - PrinterColumn printerColumn = ((PrinterColumn) property.annotation); - String column = printerColumn.name(); - if (Utils.isNullOrEmpty(column)) { - column = path.substring(path.lastIndexOf(".")); - } - String format = printerColumn.format(); - int priority = printerColumn.priority(); - - // TODO: add description to the annotation? The previous logic considered the comments, which are not available here - String description = property.description; - - resources.decorate( - getPrinterColumnDecorator(name, version, path, property.type, column, description, format, priority)); - }); - } - - /** - * Provides the decorator implementation associated with the CRD generation version. - * - * @param name the resource name - * @param version the associated version - * @param path the path from which the printer column is extracted - * @param type the data type of the printer column - * @param column the name of the column - * @param description the description of the column - * @param format the format of the printer column - * @return the concrete decorator implementing the addition of a printer column to the currently built CRD - */ - protected abstract Decorator getPrinterColumnDecorator(String name, String version, String path, - String type, String column, String description, String format, int priority); + public abstract Stream finish(); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index 0d9bd221393..d537f5d24cd 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -50,7 +50,6 @@ public class CRDGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(CRDGenerator.class); - private final Resources resources; private final Map handlers = new HashMap<>(2); private CRDOutput output; private boolean parallel; @@ -68,10 +67,6 @@ public class CRDGenerator { .serializationInclusion(NON_EMPTY) .build(); - public CRDGenerator() { - resources = new Resources(); - } - public CRDGenerator inOutputDir(File outputDir) { output = new DirCRDOutput(outputDir); return this; @@ -99,7 +94,7 @@ public CRDGenerator forCRDVersions(String... versions) { switch (version) { case CustomResourceHandler.VERSION: handlers.computeIfAbsent(CustomResourceHandler.VERSION, - s -> new CustomResourceHandler(resources)); + s -> new CustomResourceHandler()); break; default: LOGGER.warn("Ignoring unsupported CRD version: {}", version); @@ -172,24 +167,26 @@ public CRDGenerationInfo detailedGenerate() { } final CRDGenerationInfo crdGenerationInfo = new CRDGenerationInfo(); - for (HasMetadata crd : resources.generate().getItems()) { - final String version = ApiVersionUtil.trimVersion(crd.getApiVersion()); - final String crdName = crd.getMetadata().getName(); - try { - final String outputName = getOutputName(crdName, version); - try (final OutputStream outputStream = output.outputFor(outputName)) { - outputStream.write( - "# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n" - .getBytes()); - YAML_MAPPER.writeValue(outputStream, crd); - final URI fileURI = output.crdURI(outputName); - crdGenerationInfo.add(crdName, version, fileURI); - } - } catch (IOException e) { - throw new RuntimeException(e); + handlers.values().stream().flatMap(AbstractCustomResourceHandler::finish).forEach(crd -> emitCrd(crd, crdGenerationInfo)); + return crdGenerationInfo; + } + + public void emitCrd(HasMetadata crd, CRDGenerationInfo crdGenerationInfo) { + final String version = ApiVersionUtil.trimVersion(crd.getApiVersion()); + final String crdName = crd.getMetadata().getName(); + try { + final String outputName = getOutputName(crdName, version); + try (final OutputStream outputStream = output.outputFor(outputName)) { + outputStream.write( + "# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n" + .getBytes()); + YAML_MAPPER.writeValue(outputStream, crd); + final URI fileURI = output.crdURI(outputName); + crdGenerationInfo.add(crdName, version, fileURI); } + } catch (IOException e) { + throw new RuntimeException(e); } - return crdGenerationInfo; } public static String getOutputName(String crdName, String crdSpecVersion) { diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Types.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDUtils.java similarity index 79% rename from crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Types.java rename to crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDUtils.java index 5f02a4fc486..a41cdab0a49 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Types.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDUtils.java @@ -13,15 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.fabric8.crdv2.generator.utils; +package io.fabric8.crdv2.generator; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; -public class Types { - private Types() { +import java.util.HashMap; +import java.util.Map; + +public class CRDUtils { + private CRDUtils() { throw new IllegalStateException("Utility class"); } @@ -72,4 +75,20 @@ public static SpecAndStatus resolveSpecAndStatusTypes(Class definition) { return new SpecAndStatus(specClassName, statusClassName); } + public static Map toMap(String[] arr) { + Map res = new HashMap<>(); + if (arr != null) { + for (String e : arr) { + String[] splitted = e.split("\\="); + if (splitted.length >= 2) { + res.put(splitted[0], e.substring(splitted[0].length() + 1)); + } else { + throw new IllegalArgumentException( + "Invalid value: " + e + " cannot be parsed as a key-value pair. Expected format is 'key=value'."); + } + } + } + return res; + } + } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java index 63cf08a77e2..e6afda58db3 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java @@ -15,8 +15,7 @@ */ package io.fabric8.crdv2.generator; -import io.fabric8.crdv2.generator.utils.Types; -import io.fabric8.crdv2.generator.utils.Types.SpecAndStatus; +import io.fabric8.crdv2.generator.CRDUtils.SpecAndStatus; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.CustomResource; import io.fabric8.kubernetes.client.KubernetesClientException; @@ -165,7 +164,7 @@ public static CustomResourceInfo fromClass(Class customRe final Scope scope = Utils.isResourceNamespaced(customResource) ? Scope.NAMESPACED : Scope.CLUSTER; - SpecAndStatus specAndStatus = Types.resolveSpecAndStatusTypes(customResource); + SpecAndStatus specAndStatus = CRDUtils.resolveSpecAndStatusTypes(customResource); if (specAndStatus.isUnreliable()) { LOGGER.warn( "Cannot reliably determine status types for {} because it isn't parameterized with only spec and status types. Status replicas detection will be deactivated.", diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java deleted file mode 100644 index 45e671581b9..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/Resources.java +++ /dev/null @@ -1,130 +0,0 @@ -/* - * 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.crdv2.generator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.KubernetesList; -import io.fabric8.kubernetes.api.model.KubernetesListBuilder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -public class Resources { - private static final Logger LOGGER = LoggerFactory.getLogger(Resources.class); - private static final int SORT_ROUND_LIMIT = 10; - - private final KubernetesListBuilder global = new KubernetesListBuilder(); - private final Set> globalDecorators = new TreeSet<>(); - - /** - * Get the global builder - * - * @return The groups map. - */ - public KubernetesListBuilder global() { - return this.global; - } - - /** - * Get the Decorator Set. - * The method is visible for testing purposes. - * - * @return the Set of registed Decorators. - */ - protected Set> getDecorators() { - return globalDecorators; - } - - /** - * Add a {@link Decorator}. - * - * @param decorator The decorator. - */ - public void decorate(Decorator decorator) { - globalDecorators.add(decorator); - } - - /** - * Add a resource to all groups. - * - * @param metadata the resource to add to this Resources - */ - public void add(HasMetadata metadata) { - global.addToItems(metadata); - } - - /** - * Generate all resources. - * - * @return A map of {@link KubernetesList} by group name. - */ - public KubernetesList generate() { - for (Decorator decorator : applyConstraints(globalDecorators)) { - this.global.accept(decorator); - } - return this.global.build(); - } - - public List> applyConstraints(Set> decorators) { - Decorator[] array = decorators.toArray(new Decorator[0]); - // We can't guarantee that `when `decorator a < b and b < c then a < c``. - // Why? - // Because our comparators express constraints on particular pairs and can't express the global order. - // So, in order to be accurate we need to compare each decorator, with ALL OTHER decorators. - // In other words we need bubble sort. - // We also might need it more than once. So, we'll do it as many times as we have to, till there are not more transformations. - // But hey, let's have an upper limit just to prevent infinite loops. - for (int i = 0; i < SORT_ROUND_LIMIT && bubbleSort(array); i++) { - LOGGER.debug("Sorting again: {}", i + 1); - } - - List> result = Collections.unmodifiableList(Arrays.asList(array)); - - if (LOGGER.isTraceEnabled()) { - result.forEach(decorator -> LOGGER.trace("{}", decorator)); - } - - return result; - } - - /** - * Bubble sort for decorators. - * - * @param decorators the {@link Decorator} array to be sorted - */ - private boolean bubbleSort(Decorator[] decorators) { - boolean swapped = false; - int n = decorators.length; - Decorator temp; - for (int i = 0; i < n; i++) { - for (int j = 1; j < (n - i); j++) { - if (decorators[j].compareTo(decorators[j - 1]) < 0) { - swapped = true; - temp = decorators[j - 1]; - decorators[j - 1] = decorators[j]; - decorators[j] = temp; - } - } - } - return swapped; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java deleted file mode 100644 index 4de4def88c0..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/Decorator.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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.crdv2.generator.decorator; - -import io.fabric8.kubernetes.api.builder.TypedVisitor; - -public abstract class Decorator extends TypedVisitor implements Comparable { - - public Class[] after() { - return new Class[0]; - } - - public Class[] before() { - return new Class[0]; - } - - @Override - public int compareTo(Decorator o) { - //We only want to return 0 if decorators are equal. - if (this.equals(o)) { - return 0; - } - Class c = o.getClass(); - //1st pass: ours - for (Class b : before()) { - if (b.isAssignableFrom(c)) { - return -1; - } - } - for (Class a : after()) { - if (a.isAssignableFrom(c)) { - return 1; - } - } - //2nd pass: their - for (Class b : o.before()) { - if (b.isAssignableFrom(getClass())) { - return 1; - } - } - for (Class a : o.after()) { - if (a.isAssignableFrom(getClass())) { - return -1; - } - } - //Reproducible order every single time - int result = getClass().getName().compareTo(o.getClass().getName()); - if (result == 0) { - result = hashCode() - o.hashCode(); - } - return result; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java deleted file mode 100644 index 9c1eb3a5291..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/NamedResourceDecorator.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * 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.crdv2.generator.decorator; - -import io.fabric8.crdv2.generator.utils.Generics; -import io.fabric8.crdv2.generator.utils.Metadata; -import io.fabric8.kubernetes.api.builder.TypedVisitor; -import io.fabric8.kubernetes.api.builder.VisitableBuilder; -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.client.utils.Utils; - -import java.util.Optional; - -public abstract class NamedResourceDecorator extends Decorator { - - /** - * For resource name null acts as a wildcards. - * Let's use a constant instead, for clarity's shake - */ - public static final String ANY = null; - - protected final String kind; - protected final String name; - - private final ResourceVisitor visitor = new ResourceVisitor(null, null); - - public NamedResourceDecorator() { - this(ANY, ANY); - } - - public NamedResourceDecorator(String name) { - this(ANY, name); - } - - public NamedResourceDecorator(String kind, String name) { - this.kind = kind; - this.name = name; - } - - public String getKind() { - return kind; - } - - public String getName() { - return name; - } - - @Override - public void visit(VisitableBuilder builder) { - Optional resourceKind = Metadata.getKind(builder); - Optional objectMeta = Metadata.getMetadata(builder); - if (!resourceKind.isPresent() || !objectMeta.isPresent()) { - return; - } - if (Utils.isNullOrEmpty(kind)) { - if (Utils.isNullOrEmpty(name)) { - builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); - } else if (objectMeta.map(ObjectMeta::getName).filter(s -> s.equals(name)).isPresent()) { - builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); - } - } else if (resourceKind.filter(k -> k.equals(kind)).isPresent()) { - if (Utils.isNullOrEmpty(name)) { - builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); - } else if (objectMeta.map(ObjectMeta::getName).filter(s -> s.equals(name)).isPresent()) { - builder.accept(visitor.withKind(resourceKind.get()).withMetadata(objectMeta.get())); - } - } - } - - /** - * Visit a part of a Resource. - * - * @param item the visited item - * @param resourceMeta the {@link ObjectMeta} of the current resource. - */ - public abstract void andThenVisit(T item, ObjectMeta resourceMeta); - - /** - * Visit a part of a Resource. - * - * @param item the visited item - * @param kind the resource kind - * @param resourceMeta the {@link ObjectMeta} of the current resource. - */ - public void andThenVisit(T item, String kind, ObjectMeta resourceMeta) { - andThenVisit(item, resourceMeta); - } - - private class ResourceVisitor extends TypedVisitor { - - private final String kind; - private final ObjectMeta metadata; - - public ResourceVisitor(String kind, ObjectMeta metadata) { - this.kind = kind; - this.metadata = metadata; - } - - @Override - public void visit(T item) { - andThenVisit(item, kind, metadata); - } - - public ResourceVisitor withKind(String kind) { - return new ResourceVisitor(kind, this.metadata); - } - - public ResourceVisitor withMetadata(ObjectMeta metadata) { - return new ResourceVisitor(this.kind, metadata); - } - - public Class getType() { - return (Class) Generics - .getTypeArguments(NamedResourceDecorator.class, NamedResourceDecorator.this.getClass()) - .get(0); - } - } - - @Override - public Class[] after() { - return new Class[] { ResourceProvidingDecorator.class }; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java deleted file mode 100644 index 8e5b661867e..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/decorator/ResourceProvidingDecorator.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.crdv2.generator.decorator; - -import java.util.HashMap; -import java.util.Map; - -public abstract class ResourceProvidingDecorator extends Decorator { - - protected Map toMap(String[] arr) { - Map res = new HashMap<>(); - if (arr != null) { - for (String e : arr) { - String[] splitted = e.split("\\="); - if (splitted.length >= 2) { - res.put(splitted[0], e.substring(splitted[0].length() + 1)); - } else { - throw new IllegalArgumentException( - "Invalid value: " + e + " cannot be parsed as a key-value pair. Expected format is 'key=value'."); - } - } - } - return res; - } - -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java deleted file mode 100644 index 977a1771464..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Generics.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * 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.crdv2.generator.utils; - -import java.lang.reflect.Array; -import java.lang.reflect.GenericArrayType; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; -import java.lang.reflect.TypeVariable; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -/** - * Generics utilities borrowed from sundrio's TypedVisitor class. - */ -public class Generics { - - /** - * Get the underlying class for a type, or null if the type is a variable type. - * - * @param type the type - * @return the underlying class - */ - private Generics() { - throw new IllegalStateException("Utility class"); - } - - public static Class getClass(Type type) { - if (type instanceof Class) { - return (Class) type; - } else if (type instanceof ParameterizedType) { - return getClass(((ParameterizedType) type).getRawType()); - } else if (type instanceof GenericArrayType) { - Type componentType = ((GenericArrayType) type).getGenericComponentType(); - Class componentClass = getClass(componentType); - if (componentClass != null) { - return Array.newInstance(componentClass, 0).getClass(); - } else { - return null; - } - } else { - return null; - } - } - - /** - * Get the actual type arguments a child class has used to extend a generic base class. - * - * @param baseClass the base class - * @param childClass the child class - * @param the type of the base class - * @return a list of the raw classes for the actual type arguments - */ - public static List getTypeArguments(Class baseClass, - Class childClass) { - Map resolvedTypes = new LinkedHashMap<>(); - Type type = childClass; - // start walking up the inheritance hierarchy until we hit baseClass - while (true) { - final Class clazz = getClass(type); - if (clazz == null || clazz.equals(baseClass)) - break; - if (type instanceof Class) { - // there is no useful information for us in raw types, so just keep going. - type = ((Class) type).getGenericSuperclass(); - } else { - ParameterizedType parameterizedType = (ParameterizedType) type; - Class rawType = (Class) parameterizedType.getRawType(); - - Type[] actualTypeArguments = parameterizedType.getActualTypeArguments(); - TypeVariable[] typeParameters = rawType.getTypeParameters(); - for (int i = 0; i < actualTypeArguments.length; i++) { - resolvedTypes.put(typeParameters[i], actualTypeArguments[i]); - } - - if (!rawType.equals(baseClass)) { - type = rawType.getGenericSuperclass(); - } - } - } - - // finally, for each actual type argument provided to baseClass, determine (if possible) - // the raw class for that type argument. - Type[] actualTypeArguments; - if (type instanceof Class) { - actualTypeArguments = ((Class) type).getTypeParameters(); - } else { - actualTypeArguments = ((ParameterizedType) type).getActualTypeArguments(); - } - List typeArgumentsAsClasses = new ArrayList<>(); - // resolve types by chasing down type variables. - for (Type baseType : actualTypeArguments) { - while (resolvedTypes.containsKey(baseType)) { - baseType = resolvedTypes.get(baseType); - } - typeArgumentsAsClasses.add(getClass(baseType)); - } - return typeArgumentsAsClasses; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java deleted file mode 100644 index 6554e3e488e..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/utils/Metadata.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * 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.crdv2.generator.utils; - -import io.fabric8.kubernetes.api.builder.Builder; -import io.fabric8.kubernetes.api.builder.VisitableBuilder; -import io.fabric8.kubernetes.api.model.HasMetadata; -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.ObjectMetaFluent; - -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.Optional; -import java.util.function.Predicate; - -public class Metadata { - private Metadata() { - throw new IllegalStateException("Utility class"); - } - - public static Optional getKind(Builder builder) { - try { - Method method = builder.getClass().getMethod("getKind"); - Object o = method.invoke(builder); - if (o instanceof String) { - return Optional.of((String) o); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - //ignore - } - return Optional.empty(); - } - - public static Optional getMetadata(Builder builder) { - try { - Method method = builder.getClass().getMethod("buildMetadata"); - Object o = method.invoke(builder); - if (o instanceof ObjectMeta) { - return Optional.of((ObjectMeta) o); - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - //ignore - } - return Optional.empty(); - } - - public static boolean addToLabels(Builder builder, String key, String value) { - try { - Method editMethod = builder.getClass().getMethod("editOrNewMetadata"); - Object o = editMethod.invoke(builder); - if (o instanceof ObjectMetaFluent) { - ObjectMetaFluent fluent = (ObjectMetaFluent) o; - fluent.addToLabels(key, value); - Method endMethod = fluent.getClass().getMethod("endMetadata"); - endMethod.invoke(fluent); - return true; - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - //ignore - } - return false; - } - - public static boolean removeFromLabels(Builder builder, String key) { - try { - Method editMethod = builder.getClass().getMethod("editOrNewMetadata"); - Object o = editMethod.invoke(builder); - if (o instanceof ObjectMetaFluent) { - ObjectMetaFluent fluent = (ObjectMetaFluent) o; - fluent.removeFromLabels(key); - Method endMethod = fluent.getClass().getMethod("endMetadata"); - endMethod.invoke(fluent); - return true; - } - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { - //ignore - } - return false; - } - - /** - * Create a {@link Predicate} that checks that a resource builder doesn't match the name and kind. - * - * @param candidate The specified resource. - * @return The predicate. - */ - public static Predicate> matching( - HasMetadata candidate) { - return matching(candidate.getApiVersion(), candidate.getKind(), - candidate.getMetadata().getName()); - } - - /** - * Create a {@link Predicate} that checks that a resource builder doesn't match the name and kind. - * - * @param apiVersion the API version the resources must match - * @param kind The specified kind. - * @param name The specified name. - * @return The predicate. - */ - public static Predicate> matching(String apiVersion, - String kind, String name) { - return builder -> { - HasMetadata item = builder.build(); - ObjectMeta metadata = item.getMetadata(); - return apiVersion.equals(item.getApiVersion()) && - kind != null && kind.equals(item.getKind()) && - name != null && name.equals(metadata.getName()); - }; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java index 42e4868ec60..cb46efa4407 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -17,91 +17,145 @@ import io.fabric8.crd.generator.annotation.PrinterColumn; import io.fabric8.crdv2.generator.AbstractCustomResourceHandler; +import io.fabric8.crdv2.generator.AbstractJsonSchema.AnnotationMetadata; +import io.fabric8.crdv2.generator.CRDUtils; import io.fabric8.crdv2.generator.CustomResourceInfo; import io.fabric8.crdv2.generator.ResolvingContext; -import io.fabric8.crdv2.generator.Resources; -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.crdv2.generator.v1.decorator.AddAdditionPrinterColumnDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddCustomResourceDefinitionResourceDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddCustomResourceDefinitionVersionDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddLabelSelectorPathDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddSchemaToCustomResourceDefinitionVersionDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddSpecReplicasPathDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddStatusReplicasPathDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddStatusSubresourceDecorator; -import io.fabric8.crdv2.generator.v1.decorator.AddSubresourcesDecorator; -import io.fabric8.crdv2.generator.v1.decorator.EnsureSingleStorageVersionDecorator; -import io.fabric8.crdv2.generator.v1.decorator.SetDeprecatedVersionDecorator; -import io.fabric8.crdv2.generator.v1.decorator.SetServedVersionDecorator; -import io.fabric8.crdv2.generator.v1.decorator.SetStorageVersionDecorator; -import io.fabric8.crdv2.generator.v1.decorator.SortCustomResourceDefinitionVersionDecorator; -import io.fabric8.crdv2.generator.v1.decorator.SortPrinterColumnsDecorator; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionBuilder; import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.client.utils.KubernetesVersionPriority; +import io.fabric8.kubernetes.client.utils.Utils; import io.fabric8.kubernetes.model.annotation.LabelSelector; import io.fabric8.kubernetes.model.annotation.SpecReplicas; import io.fabric8.kubernetes.model.annotation.StatusReplicas; -public class CustomResourceHandler extends AbstractCustomResourceHandler { +import java.util.List; +import java.util.Optional; +import java.util.TreeMap; +import java.util.concurrent.CopyOnWriteArrayList; +import java.util.stream.Collectors; +import java.util.stream.Stream; - public static final String VERSION = "v1"; +public class CustomResourceHandler extends AbstractCustomResourceHandler { - public CustomResourceHandler(Resources resources) { - super(resources); - } + private List crds = new CopyOnWriteArrayList<>(); - @Override - protected Decorator getPrinterColumnDecorator(String name, - String version, String path, - String type, String column, String description, String format, int priority) { - return new AddAdditionPrinterColumnDecorator(name, version, type, column, path, format, - description, priority); - } + public static final String VERSION = "v1"; @Override public void handle(CustomResourceInfo config) { final String name = config.crdName(); final String version = config.version(); - resources.decorate( - new AddCustomResourceDefinitionResourceDecorator(name, config.group(), config.kind(), - config.scope().value(), config.shortNames(), config.plural(), config.singular(), config.annotations(), - config.labels())); - - resources.decorate(new AddCustomResourceDefinitionVersionDecorator(name, version)); JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), config.definition()); JSONSchemaProps schema = resolver.getSchema(); - resources.decorate(new AddSchemaToCustomResourceDefinitionVersionDecorator(name, version, - schema)); + CustomResourceDefinitionVersionBuilder builder = new CustomResourceDefinitionVersionBuilder() + .withName(version) + .withStorage(config.storage()) + .withServed(config.served()) + .withDeprecated(config.deprecated() ? true : null) + .withDeprecationWarning(config.deprecationWarning()) + .withNewSchema() + .withOpenAPIV3Schema(schema) + .endSchema(); + + TreeMap sortedCols = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + sortedCols.putAll(resolver.getAllPaths(PrinterColumn.class)); + sortedCols.forEach((path, property) -> { + PrinterColumn printerColumn = ((PrinterColumn) property.annotation); + String column = printerColumn.name(); + if (Utils.isNullOrEmpty(column)) { + column = path.substring(path.lastIndexOf(".")); + } + String format = printerColumn.format(); + int priority = printerColumn.priority(); + + // TODO: add description to the annotation? The previous logic considered the comments, which are not available here + String description = property.description; + + builder.addNewAdditionalPrinterColumn() + .withType(property.type) + .withName(column) + .withJsonPath(path) + .withFormat(Utils.isNotNullOrEmpty(format) ? format : null) + .withDescription(Utils.isNotNullOrEmpty(description) ? description : null) + .withPriority(priority) + .endAdditionalPrinterColumn(); + }); resolver.getSinglePath(SpecReplicas.class).ifPresent(path -> { - resources.decorate(new AddSubresourcesDecorator(name, version)); - resources.decorate(new AddSpecReplicasPathDecorator(name, version, path)); + builder.editOrNewSubresources().editOrNewScale().withSpecReplicasPath(path).endScale().endSubresources(); }); resolver.getSinglePath(StatusReplicas.class).ifPresent(path -> { - resources.decorate(new AddSubresourcesDecorator(name, version)); - resources.decorate(new AddStatusReplicasPathDecorator(name, version, path)); + builder.editOrNewSubresources().editOrNewScale().withStatusReplicasPath(path).endScale().endSubresources(); }); resolver.getSinglePath(LabelSelector.class).ifPresent(path -> { - resources.decorate(new AddSubresourcesDecorator(name, version)); - resources.decorate(new AddLabelSelectorPathDecorator(name, version, path)); + builder.editOrNewSubresources().editOrNewScale().withLabelSelectorPath(path).endScale().endSubresources(); }); - handlePrinterColumns(name, version, resolver.getAllPaths(PrinterColumn.class)); - if (config.statusClassName().isPresent()) { - resources.decorate(new AddSubresourcesDecorator(name, version)); - resources.decorate(new AddStatusSubresourceDecorator(name, version)); + builder.editOrNewSubresources().withNewStatus().endStatus().endSubresources(); } - resources.decorate(new SetServedVersionDecorator(name, version, config.served())); - 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)); + CustomResourceDefinition crd = new CustomResourceDefinitionBuilder() + .withNewMetadata() + .withName(name) + .withAnnotations(CRDUtils.toMap(config.annotations())) + .withLabels(CRDUtils.toMap(config.labels())) + .endMetadata() + .withNewSpec() + .withScope(config.scope().value()) + .withGroup(config.group()) + .withNewNames() + .withKind(config.kind()) + .withShortNames(config.shortNames()) + .withPlural(config.plural()) + .withSingular(config.singular()) + .endNames() + .addToVersions(builder.build()) + .endSpec() + .build(); + + crds.add(crd); + } + + @Override + public Stream finish() { + return crds.stream().collect(Collectors.groupingBy(crd -> crd.getMetadata().getName())).values().stream() + .map(this::combine); + } + + private CustomResourceDefinition combine(List definitions) { + CustomResourceDefinition primary = definitions.get(0); + if (definitions.size() == 1) { + return primary; + } + + List versions = definitions.stream().flatMap(crd -> crd.getSpec().getVersions().stream()) + .collect(Collectors.toList()); + + List storageVersions = versions.stream() + .filter(v -> Optional.ofNullable(v.getStorage()).orElse(true)) + .map(CustomResourceDefinitionVersion::getName) + .collect(Collectors.toList()); + + if (storageVersions.size() > 1) { + throw new IllegalStateException(String.format( + "'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.", + primary.getMetadata().getName(), storageVersions)); + } + + versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName); + + //TODO: we could double check that the top-level metadata is consistent across all versions + return new CustomResourceDefinitionBuilder(primary).editSpec().withVersions(versions).endSpec().build(); } } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java deleted file mode 100644 index aded91f2c96..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddAdditionPrinterColumnDecorator.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceColumnDefinitionBuilder; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; -import io.fabric8.kubernetes.client.utils.Utils; - -import java.util.function.Predicate; - -public class AddAdditionPrinterColumnDecorator extends - CustomResourceDefinitionVersionDecorator> { - - private final String type; - private final String columnName; - private final String path; - private final String format; - private final String description; - private final int priority; - - public AddAdditionPrinterColumnDecorator(String name, String version, String type, String columnName, String path, - String format, String description, int priority) { - super(name, version); - this.type = type; - this.columnName = columnName; - this.path = path; - this.format = format; - this.description = description; - this.priority = priority; - } - - @Override - public void andThenVisit(CustomResourceDefinitionVersionFluent spec) { - Predicate matchingColumn = col -> col.getName() != null - && col.getName().equals(columnName) && col.getJsonPath() != null && col.getJsonPath().equals(path); - spec.removeMatchingFromAdditionalPrinterColumns(matchingColumn); - - spec.addNewAdditionalPrinterColumn() - .withType(type) - .withName(columnName) - .withJsonPath(path) - .withFormat(Utils.isNotNullOrEmpty(format) ? format : null) - .withDescription(Utils.isNotNullOrEmpty(description) ? description : null) - .withPriority(priority) - .endAdditionalPrinterColumn(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); - result = prime * result + ((getVersion() == null) ? 0 : getVersion().hashCode()); - result = prime * result + ((columnName == null) ? 0 : columnName.hashCode()); - result = prime * result + ((type == null) ? 0 : type.hashCode()); - result = prime * result + ((path == null) ? 0 : path.hashCode()); - result = prime * result + ((format == null) ? 0 : format.hashCode()); - result = prime * result + ((description == null) ? 0 : description.hashCode()); - result = prime * result + priority; - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (!super.equals(obj)) - return false; - if (getClass() != obj.getClass()) - return false; - AddAdditionPrinterColumnDecorator other = (AddAdditionPrinterColumnDecorator) obj; - if (getName() == null) { - if (other.getName() != null) - return false; - } else if (!getName().equals(other.getName())) - return false; - if (getVersion() == null) { - if (other.getVersion() != null) - return false; - } else if (!getVersion().equals(other.getVersion())) - return false; - if (description == null) { - if (other.description != null) - return false; - } else if (!description.equals(other.description)) - return false; - if (format == null) { - if (other.format != null) - return false; - } else if (!format.equals(other.format)) - return false; - if (columnName == null) { - if (other.columnName != null) - return false; - } else if (!columnName.equals(other.columnName)) - return false; - if (path == null) { - if (other.path != null) - return false; - } else if (!path.equals(other.path)) - return false; - if (type == null) { - if (other.type != null) - return false; - } else if (!type.equals(other.type)) - return false; - return priority == other.priority; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "column:" + columnName + "priority:" - + priority + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java deleted file mode 100644 index 56cb53e051e..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionResourceDecorator.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.crdv2.generator.decorator.ResourceProvidingDecorator; -import io.fabric8.kubernetes.api.model.KubernetesListBuilder; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder; -import io.fabric8.kubernetes.client.utils.ApiVersionUtil; - -public class AddCustomResourceDefinitionResourceDecorator extends ResourceProvidingDecorator { - - private String name; - private String apiGroup; - private String kind; - - private String scope; - private String[] shortNames; - private String plural; - private String singular; - private String[] annotations; - private String[] labels; - - public AddCustomResourceDefinitionResourceDecorator(String name, String apiGroup, String kind, - String scope, String[] shortNames, String plural, String singular, String[] annotations, String[] labels) { - this.name = name; - this.apiGroup = apiGroup; - this.kind = kind; - this.scope = scope; - this.shortNames = shortNames; - this.plural = plural; - this.singular = singular; - this.annotations = annotations; - this.labels = labels; - } - - @Override - public void visit(KubernetesListBuilder list) { - boolean exists = list.buildItems().stream().anyMatch(i -> i.getKind().equals("CustomResourceDefinition") - && i.getMetadata().getName().equals(name) - && ApiVersionUtil.trimVersion(i.getApiVersion()).equals("v1")); - - if (!exists) { - list.addToItems(new CustomResourceDefinitionBuilder() - .withNewMetadata() - .withName(name) - .withAnnotations(toMap(annotations)) - .withLabels(toMap(labels)) - .endMetadata() - .withNewSpec() - .withScope(scope) - .withGroup(apiGroup) - .withNewNames() - .withKind(kind) - .withShortNames(shortNames) - .withPlural(plural) - .withSingular(singular) - .endNames() - .endSpec() - .build()); - } - } - - @Override - public Class[] before() { - return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, CustomResourceDefinitionDecorator.class }; - } - - @Override - public String toString() { - return getClass().getName() + " [apiGroup=" + apiGroup + ", kind=" + kind + ", name=" + name + ", plural=" + plural - + ", scope=" + scope + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java deleted file mode 100644 index 5362427e01f..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddCustomResourceDefinitionVersionDecorator.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.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.CustomResourceDefinitionVersionBuilder; - -import java.util.function.Predicate; - -public class AddCustomResourceDefinitionVersionDecorator extends - CustomResourceDefinitionDecorator> { - - private final String version; - - public AddCustomResourceDefinitionVersionDecorator(String name, String version) { - super(name); - this.version = version; - } - - public String getName() { - return this.name; - } - - public String getVersion() { - return this.version; - } - - @Override - public void andThenVisit(CustomResourceDefinitionSpecFluent spec, ObjectMeta resourceMeta) { - Predicate predicate = candidate -> candidate.getName() - .equals(version); - - spec.removeMatchingFromVersions(predicate); - spec.addNewVersion() - .withName(version) - .endVersion(); - } - - @Override - public Class[] after() { - return new Class[] { AddCustomResourceDefinitionResourceDecorator.class }; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + version + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java deleted file mode 100644 index ac47673f099..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddLabelSelectorPathDecorator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; - -public class AddLabelSelectorPathDecorator - extends CustomResourceDefinitionVersionDecorator> { - - private final String path; - - public AddLabelSelectorPathDecorator(String name, String version, String path) { - super(name, version); - this.path = path; - } - - @Override - public void andThenVisit(CustomResourceSubresourcesFluent subresources) { - if (subresources.hasScale()) { - subresources.editScale().withLabelSelectorPath(path).endScale(); - } else { - subresources.withNewScale().withLabelSelectorPath(path).endScale(); - } - } - - @Override - public Class[] after() { - return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, - AddSubresourcesDecorator.class }; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java deleted file mode 100644 index d3aa120c50f..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSchemaToCustomResourceDefinitionVersionDecorator.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; -import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; - -public class AddSchemaToCustomResourceDefinitionVersionDecorator - extends CustomResourceDefinitionVersionDecorator> { - - private JSONSchemaProps schema; - - public AddSchemaToCustomResourceDefinitionVersionDecorator(String name, String version, JSONSchemaProps schema) { - super(name, version); - this.schema = schema; - } - - @Override - public void andThenVisit(CustomResourceDefinitionVersionFluent version) { - version.withNewSchema().withOpenAPIV3Schema(schema).endSchema(); - } - - @Override - public Class[] after() { - return new Class[] { AddCustomResourceDefinitionVersionDecorator.class }; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java deleted file mode 100644 index f0fa4f29d28..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSpecReplicasPathDecorator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; - -public class AddSpecReplicasPathDecorator - extends CustomResourceDefinitionVersionDecorator> { - - private final String path; - - public AddSpecReplicasPathDecorator(String name, String version, String path) { - super(name, version); - this.path = path; - } - - @Override - public void andThenVisit(CustomResourceSubresourcesFluent subresources) { - if (subresources.hasScale()) { - subresources.editScale().withSpecReplicasPath(path).endScale(); - } else { - subresources.withNewScale().withSpecReplicasPath(path).endScale(); - } - } - - @Override - public Class[] after() { - return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, - AddSubresourcesDecorator.class }; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java deleted file mode 100644 index e6a920f76c2..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusReplicasPathDecorator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; - -public class AddStatusReplicasPathDecorator - extends CustomResourceDefinitionVersionDecorator> { - - private final String path; - - public AddStatusReplicasPathDecorator(String name, String version, String path) { - super(name, version); - this.path = path; - } - - @Override - public void andThenVisit(CustomResourceSubresourcesFluent subresources) { - if (subresources.hasScale()) { - subresources.editScale().withStatusReplicasPath(path).endScale(); - } else { - subresources.withNewScale().withStatusReplicasPath(path).endScale(); - } - } - - @Override - public Class[] after() { - return new Class[] { AddSubresourcesDecorator.class }; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java deleted file mode 100644 index 1636df8f225..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddStatusSubresourceDecorator.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceSubresourcesFluent; - -public class AddStatusSubresourceDecorator - extends CustomResourceDefinitionVersionDecorator> { - - public AddStatusSubresourceDecorator(String name, String version) { - super(name, version); - } - - @Override - public void andThenVisit(CustomResourceSubresourcesFluent subresources) { - subresources.withNewStatus().endStatus(); - } - - @Override - public Class[] after() { - return new Class[] { AddCustomResourceDefinitionVersionDecorator.class, - AddSubresourcesDecorator.class }; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java deleted file mode 100644 index ae092e2c07e..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/AddSubresourcesDecorator.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; - -public class AddSubresourcesDecorator - extends CustomResourceDefinitionVersionDecorator> { - - public AddSubresourcesDecorator(String name, String version) { - super(name, version); - } - - @Override - public void andThenVisit(CustomResourceDefinitionVersionFluent spec) { - if (!spec.hasSubresources()) { - spec.withNewSubresources().endSubresources(); - } - } - - @Override - public Class[] after() { - return new Class[] { AddCustomResourceDefinitionVersionDecorator.class }; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java deleted file mode 100644 index 0ffb7d4db60..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionDecorator.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.NamedResourceDecorator; -import io.fabric8.kubernetes.api.model.ObjectMeta; - -public class CustomResourceDefinitionDecorator extends NamedResourceDecorator { - - public CustomResourceDefinitionDecorator(String name) { - super("CustomResourceDefinition", name); - } - - @Override - public void andThenVisit(T item, ObjectMeta resourceMeta) { - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java deleted file mode 100644 index d12584b69cd..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/CustomResourceDefinitionVersionDecorator.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.crdv2.generator.utils.Generics; -import io.fabric8.kubernetes.api.builder.TypedVisitor; -import io.fabric8.kubernetes.api.builder.VisitableBuilder; -import io.fabric8.kubernetes.api.model.ObjectMeta; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionBuilder; -import io.fabric8.kubernetes.client.utils.Utils; - -import java.util.Optional; - -import static io.fabric8.crdv2.generator.utils.Metadata.getMetadata; - -public abstract class CustomResourceDefinitionVersionDecorator extends Decorator { - - protected static final String ANY = null; - - private final String name; - private final String version; - - private final CustomResourceDefinitionVersionVisitor versionSelector = new CustomResourceDefinitionVersionVisitor(); - private final VersionVisitor versionVisitor = new VersionVisitor(); - - public CustomResourceDefinitionVersionDecorator(String name, String version) { - this.name = name; - this.version = version; - } - - public String getName() { - return this.name; - } - - public String getVersion() { - return this.version; - } - - @Override - public void visit(VisitableBuilder builder) { - Optional objectMeta = getMetadata(builder); - if (!objectMeta.isPresent()) { - return; - } - if (Utils.isNullOrEmpty(name) || objectMeta.map(ObjectMeta::getName).filter(s -> s.equals(name)).isPresent()) { - builder.accept(versionSelector); - } - } - - public abstract void andThenVisit(T version); - - private class CustomResourceDefinitionVersionVisitor extends TypedVisitor { - - @Override - public void visit(CustomResourceDefinitionVersionBuilder builder) { - if (Utils.isNullOrEmpty(version) || builder.getName().equals(version)) { - builder.accept(versionVisitor); - } - } - } - - private class VersionVisitor extends TypedVisitor { - - @Override - public void visit(T version) { - andThenVisit(version); - } - - public Class getType() { - return (Class) Generics - .getTypeArguments(CustomResourceDefinitionVersionDecorator.class, - CustomResourceDefinitionVersionDecorator.this.getClass()) - .get(0); - } - } - - @Override - public Class[] after() { - return new Class[] { - AddCustomResourceDefinitionResourceDecorator.class, - AddCustomResourceDefinitionVersionDecorator.class }; - } - - @Override - public Class[] before() { - return new Class[] {}; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((name == null) ? 0 : name.hashCode()); - result = prime * result + ((version == null) ? 0 : version.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - CustomResourceDefinitionVersionDecorator other = (CustomResourceDefinitionVersionDecorator) obj; - if (name == null) { - if (other.name != null) - return false; - } else if (!name.equals(other.name)) - return false; - if (version == null) { - if (other.version != null) - return false; - } else if (!version.equals(other.version)) - return false; - return true; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java deleted file mode 100644 index fb787ba31ab..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/EnsureSingleStorageVersionDecorator.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.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 java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -public class EnsureSingleStorageVersionDecorator - extends CustomResourceDefinitionDecorator> { - - public EnsureSingleStorageVersionDecorator(String name) { - super(name); - } - - @Override - public void andThenVisit(CustomResourceDefinitionSpecFluent spec, ObjectMeta resourceMeta) { - List versions = spec.buildVersions(); - - List storageVersions = versions.stream() - .filter(v -> Optional.ofNullable(v.getStorage()).orElse(true)) - .map(CustomResourceDefinitionVersion::getName) - .collect(Collectors.toList()); - - if (storageVersions.size() > 1) { - throw new IllegalStateException(String.format( - "'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.", - resourceMeta.getName(), storageVersions)); - } - } - - @Override - public Class[] after() { - return new Class[] { - CustomResourceDefinitionVersionDecorator.class - }; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java deleted file mode 100644 index a32183066d5..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetDeprecatedVersionDecorator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; - -public class SetDeprecatedVersionDecorator - extends CustomResourceDefinitionVersionDecorator> { - - private final boolean deprecated; - private final String deprecationWarning; - - public SetDeprecatedVersionDecorator(String name, String version, boolean deprecated, String deprecationWarning) { - super(name, version); - this.deprecated = deprecated; - this.deprecationWarning = deprecationWarning; - } - - @Override - public void andThenVisit(CustomResourceDefinitionVersionFluent version) { - if (deprecated) { - version.withDeprecated(true); - version.withDeprecationWarning(deprecationWarning); - } - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() - + ", deprecated:" + deprecated + ", deprecationWarning:" + deprecationWarning + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java deleted file mode 100644 index 51c25a8c972..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetServedVersionDecorator.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; - -public class SetServedVersionDecorator - extends CustomResourceDefinitionVersionDecorator> { - - private final boolean served; - - public SetServedVersionDecorator(String name, String version, boolean served) { - super(name, version); - this.served = served; - } - - @Override - public void andThenVisit(CustomResourceDefinitionVersionFluent version) { - version.withServed(served); - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + ", served:" + served + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java deleted file mode 100644 index 0e12aac7cf9..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SetStorageVersionDecorator.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; - -public class SetStorageVersionDecorator - extends CustomResourceDefinitionVersionDecorator> { - - private final boolean storage; - - public SetStorageVersionDecorator(String name, String version, boolean storage) { - super(name, version); - this.storage = storage; - } - - @Override - public void andThenVisit(CustomResourceDefinitionVersionFluent version) { - version.withStorage(storage); - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + ", storage:" + storage + "]"; - } -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java deleted file mode 100644 index 0ab0c9776fe..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortCustomResourceDefinitionVersionDecorator.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.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> { - 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[] after() { - return new Class[] { EnsureSingleStorageVersionDecorator.class }; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + "]"; - } - -} diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java deleted file mode 100644 index 942483fd8da..00000000000 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/decorator/SortPrinterColumnsDecorator.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * 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.crdv2.generator.v1.decorator; - -import io.fabric8.crdv2.generator.decorator.Decorator; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceColumnDefinition; -import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionFluent; - -import java.util.List; - -public class SortPrinterColumnsDecorator - extends CustomResourceDefinitionVersionDecorator> { - - public SortPrinterColumnsDecorator(String name, String version) { - super(name, version); - } - - @Override - public void andThenVisit(CustomResourceDefinitionVersionFluent version) { - List columns = version.buildAdditionalPrinterColumns(); - if (columns != null && !columns.isEmpty()) { - columns.sort((o1, o2) -> String.CASE_INSENSITIVE_ORDER.compare(o1.getJsonPath(), o2.getJsonPath())); - } - version.withAdditionalPrinterColumns(columns); - } - - @Override - public Class[] after() { - return new Class[] { CustomResourceDefinitionVersionDecorator.class, AddAdditionPrinterColumnDecorator.class }; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + ((getName() == null) ? 0 : getName().hashCode()); - result = prime * result + ((getVersion() == null) ? 0 : getVersion().hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - CustomResourceDefinitionVersionDecorator other = (CustomResourceDefinitionVersionDecorator) obj; - if (getName() == null) { - if (other.getName() != null) - return false; - } else if (!getName().equals(other.getName())) - return false; - if (getVersion() == null) { - if (other.getVersion() != null) - return false; - } else if (!getVersion().equals(other.getVersion())) - return false; - return true; - } - - @Override - public String toString() { - return getClass().getName() + " [name:" + getName() + ", version:" + getVersion() + "]"; - } - -} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java deleted file mode 100644 index f11cb52f466..00000000000 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ResourcesTest.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * 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.crdv2.generator; - -import io.fabric8.crdv2.generator.v1.decorator.AddAdditionPrinterColumnDecorator; -import io.fabric8.crdv2.generator.v1.decorator.SortPrinterColumnsDecorator; -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class ResourcesTest { - - @Test - public void shouldSupportMultiplePrinterColumns() { - Resources r = new Resources(); - - AddAdditionPrinterColumnDecorator dec1 = new AddAdditionPrinterColumnDecorator("resource", "v1", "string", "replicas", - ".replicas", null, null, 0); - AddAdditionPrinterColumnDecorator dec2 = new AddAdditionPrinterColumnDecorator("resource", "v1", "boolean", "enabled", - ".replicas", null, null, 0); - - r.decorate(dec1); - r.decorate(dec2); - - assertEquals(2, r.getDecorators().size()); - assertTrue(r.getDecorators().contains(dec1)); - assertTrue(r.getDecorators().contains(dec2)); - } - - @Test - public void shouldSupportMultipleSortPrinterColumns() { - Resources r = new Resources(); - SortPrinterColumnsDecorator dec1 = new SortPrinterColumnsDecorator("my-crd", "v1"); - SortPrinterColumnsDecorator dec2 = new SortPrinterColumnsDecorator("my-crd", "v2"); - r.decorate(dec1); - r.decorate(dec2); - assertEquals(2, r.getDecorators().size()); - assertTrue(r.getDecorators().contains(dec1)); - assertTrue(r.getDecorators().contains(dec2)); - } -} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java index 3f56a578589..3cc4dffbcd4 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java @@ -16,7 +16,8 @@ package io.fabric8.crdv2.generator.utils; import io.fabric8.crdv2.example.inherited.Child; -import io.fabric8.crdv2.generator.utils.Types.SpecAndStatus; +import io.fabric8.crdv2.generator.CRDUtils; +import io.fabric8.crdv2.generator.CRDUtils.SpecAndStatus; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -25,7 +26,7 @@ public class TypesTest { @Test void shouldFindInheritedStatusProperty() { - final SpecAndStatus specAndStatus = Types.resolveSpecAndStatusTypes(Child.class); + final SpecAndStatus specAndStatus = CRDUtils.resolveSpecAndStatusTypes(Child.class); assertEquals("io.fabric8.crdv2.example.inherited.ChildStatus", specAndStatus.getStatusClassName()); assertEquals("io.fabric8.crdv2.example.inherited.ChildSpec", specAndStatus.getSpecClassName()); } diff --git a/crd-generator/test/pom.xml b/crd-generator/test/pom.xml index 96240459e9e..f667ad46976 100644 --- a/crd-generator/test/pom.xml +++ b/crd-generator/test/pom.xml @@ -38,7 +38,12 @@ io.fabric8 crd-generator-apt - + + + io.fabric8 + crd-generator-api-v2 + + org.projectlombok lombok @@ -76,6 +81,13 @@ ${keycloak-version} test + + + org.jboss.logging + jboss-logging + 3.5.3.Final + test + diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java index 881c1c36c48..a9f615e6de1 100644 --- a/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java @@ -15,19 +15,46 @@ */ package io.fabric8.crd.generator.keycloak; +import io.fabric8.crdv2.generator.CRDGenerator; +import io.fabric8.crdv2.generator.CRDGenerator.AbstractCRDOutput; import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.client.utils.IOHelpers; import io.fabric8.kubernetes.client.utils.Serialization; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; class KeycloakRealmCRDTest { @Test - void testCrd() { - CustomResourceDefinition d = Serialization.unmarshal(getClass().getClassLoader() + void testCrd() throws IOException { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + CRDGenerator generator = new CRDGenerator().withOutput(new AbstractCRDOutput() { + + @Override + protected OutputStream createStreamFor(String crdName) throws IOException { + return baos; + } + + }); + generator.customResourceClasses(KeycloakRealm.class); + generator.generate(); + String result = baos.toString(); + + CustomResourceDefinition d1 = Serialization.unmarshal(result, + CustomResourceDefinition.class); + + CustomResourceDefinition d2 = Serialization.unmarshal(getClass().getClassLoader() .getResourceAsStream("META-INF/fabric8/keycloakrealms.sample.fabric8.io-v1.yml"), CustomResourceDefinition.class); - assertNotNull(d); // just make sure it generates + + assertEquals(IOHelpers.readFully(getClass().getClassLoader() + .getResourceAsStream("META-INF/fabric8/keycloakrealms.sample.fabric8.io-v1.yml")), result); + + //assertNotNull(d); // just make sure it generates } } From 73997e377b4ca59296365002f600a59b5565c246 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Fri, 26 Apr 2024 08:34:53 -0400 Subject: [PATCH 07/16] wiring up new top level options --- .../AbstractCustomResourceHandler.java | 2 +- .../crdv2/generator/AbstractJsonSchema.java | 20 ++++++----- .../fabric8/crdv2/generator/CRDGenerator.java | 30 +++++++++++++++- .../crdv2/generator/ResolvingContext.java | 36 +++++++++++++------ .../generator/v1/CustomResourceHandler.java | 19 +++++----- .../crdv2/generator/v1/JsonSchema.java | 2 +- .../example/annotated/AnnotatedSpec.java | 1 + .../crdv2/generator/v1/JsonSchemaTest.java | 5 +-- .../generator/v1/SpecReplicasPathTest.java | 4 +-- crd-generator/test/pom.xml | 14 +------- .../keycloak/KeycloakRealmCRDTest.java | 35 +++--------------- 11 files changed, 88 insertions(+), 80 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index 0e60ed2502c..1f5e4b20fb4 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -25,7 +25,7 @@ */ public abstract class AbstractCustomResourceHandler { - public abstract void handle(CustomResourceInfo config); + public abstract void handle(CustomResourceInfo config, ResolvingContext resolvingContext); public abstract Stream finish(); diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index 6e1236c0c44..608861a1f4f 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -145,7 +145,7 @@ private T resolveRoot(Class definition) { return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata"); } return resolveProperty(new LinkedHashMap<>(), schemaSwaps, null, - resolvingContext.serializationConfig.constructType(definition), schema); + resolvingContext.objectMapper.getSerializationConfig().constructType(definition), schema); } /** @@ -282,9 +282,12 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa final InternalSchemaSwaps swaps = schemaSwaps; GeneratorObjectSchema gos = (GeneratorObjectSchema) jacksonSchema.asObjectSchema(); - AnnotationIntrospector ai = resolvingContext.serializationConfig.getAnnotationIntrospector(); - BeanDescription bd = resolvingContext.serializationConfig.introspect(gos.javaType); - boolean preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null; + AnnotationIntrospector ai = resolvingContext.objectMapper.getSerializationConfig().getAnnotationIntrospector(); + BeanDescription bd = resolvingContext.objectMapper.getSerializationConfig().introspect(gos.javaType); + boolean preserveUnknownFields = false; + if (resolvingContext.implicitPreserveUnknownFields) { + preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null; + } consumeRepeatingAnnotation(gos.javaType.getRawClass(), SchemaSwap.class, ss -> { swaps.registerSwap(gos.javaType.getRawClass(), @@ -333,7 +336,7 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa continue; } propertySchema = toJsonSchema(propertyMetadata.schemaFrom); - type = resolvingContext.serializationConfig.constructType(propertyMetadata.schemaFrom); + type = resolvingContext.objectMapper.getSerializationConfig().constructType(propertyMetadata.schemaFrom); } T schema = resolveProperty(visited, schemaSwaps, name, type, propertySchema); @@ -423,7 +426,7 @@ private T resolveProperty(LinkedHashMap visited, InternalSchemaS } else if (jacksonSchema instanceof ReferenceSchema) { // de-reference the reference schema - these can be naturally non-cyclic, for example siblings ReferenceSchema ref = (ReferenceSchema) jacksonSchema; - GeneratorObjectSchema referenced = resolvingContext.seen.get(ref.get$ref()); + GeneratorObjectSchema referenced = resolvingContext.uriToJacksonSchema.get(ref.get$ref()); Utils.checkNotNull(referenced, "Could not find previously generated schema"); jacksonSchema = referenced; } else if (type.isMapLikeType()) { @@ -469,11 +472,10 @@ private Set findIngoredEnumConstants(JavaType type) { Set toIgnore = new HashSet<>(); for (Field field : fields) { if (field.isEnumConstant() && field.getAnnotation(JsonIgnore.class) != null) { - // hack to figure out the enum constant + // hack to figure out the enum constant - this guards against some using both JsonIgnore and JsonProperty try { Object value = field.get(null); - toIgnore.add(resolvingContext.kubernetesSerialization - .unmarshal(resolvingContext.kubernetesSerialization.asJson(value), String.class)); + toIgnore.add(resolvingContext.objectMapper.convertValue(value, String.class)); } catch (IllegalArgumentException | IllegalAccessException e) { } } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index d537f5d24cd..e2a4042ec51 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -41,6 +41,9 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; +import java.util.stream.Collectors; import java.util.stream.Stream; import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; @@ -53,6 +56,8 @@ public class CRDGenerator { private final Map handlers = new HashMap<>(2); private CRDOutput output; private boolean parallel; + private boolean implicitPreserveUnknownFields; + private ObjectMapper objectMapper; private Map infos; // TODO: why not rely on the standard fabric8 yaml mapping @@ -77,11 +82,21 @@ public CRDGenerator withOutput(CRDOutput output) { return this; } + public CRDGenerator withImplicitPreserveUnknownFields(boolean implicitPreserveUnknownFields) { + this.implicitPreserveUnknownFields = implicitPreserveUnknownFields; + return this; + } + public CRDGenerator withParallelGenerationEnabled(boolean parallel) { this.parallel = parallel; return this; } + public CRDGenerator withObjectMapper(ObjectMapper mapper) { + this.objectMapper = mapper; + return this; + } + public CRDGenerator forCRDVersions(List versions) { return versions != null && !versions.isEmpty() ? forCRDVersions(versions.toArray(new String[0])) : this; @@ -154,6 +169,13 @@ public CRDGenerationInfo detailedGenerate() { forCRDVersions(CustomResourceHandler.VERSION); } + final ResolvingContext context; + if (this.objectMapper == null) { + context = ResolvingContext.defaultResolvingContext(implicitPreserveUnknownFields); + } else { + context = new ResolvingContext(this.objectMapper, implicitPreserveUnknownFields); + } + for (CustomResourceInfo info : infos.values()) { if (info != null) { if (LOGGER.isInfoEnabled()) { @@ -162,7 +184,13 @@ public CRDGenerationInfo detailedGenerate() { info.specClassName().orElse("undetermined"), info.statusClassName().orElse("undetermined")); } - handlers.values().forEach(h -> h.handle(info)); + + if (parallel) { + handlers.values().stream().map(h -> ForkJoinPool.commonPool().submit(() -> h.handle(info, context.forkContext()))) + .collect(Collectors.toList()).stream().forEach(ForkJoinTask::join); + } else { + handlers.values().stream().forEach(h -> h.handle(info, context)); + } } } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java index 5c8b9572d0a..df7fccaf561 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -19,7 +19,6 @@ import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; import com.fasterxml.jackson.module.jsonSchema.JsonSchema; @@ -31,10 +30,15 @@ import com.fasterxml.jackson.module.jsonSchema.types.ObjectSchema; import io.fabric8.kubernetes.client.utils.KubernetesSerialization; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +/** + * Encapsulates the stateful Jackson details that allow for crd to be fully resolved by our logic + * - holds an association of uris to already generated jackson schemas + * - holds a Jackson SchemaGenerator which is not thread-safe + */ public class ResolvingContext { static final class GeneratorObjectSchema extends ObjectSchema { @@ -76,15 +80,15 @@ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) { // so that we may directly mark preserve unknown JsonObjectFormatVisitor result = super.expectObjectFormat(convertedType); ((GeneratorObjectSchema) schema).javaType = convertedType; - seen.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema) schema); + uriToJacksonSchema.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema) schema); return result; } } final JsonSchemaGenerator generator; - final SerializationConfig serializationConfig; - final KubernetesSerialization kubernetesSerialization; - final Map seen = new HashMap<>(); + final ObjectMapper objectMapper; + final Map uriToJacksonSchema; + final boolean implicitPreserveUnknownFields; private static class AccessibleKubernetesSerialization extends KubernetesSerialization { @@ -97,16 +101,26 @@ public ObjectMapper getMapper() { private static AccessibleKubernetesSerialization DEFAULT_KUBERNETES_SERIALIZATION; - public static ResolvingContext defaultResolvingContext() { + public static ResolvingContext defaultResolvingContext(boolean implicitPreserveUnknownFields) { if (DEFAULT_KUBERNETES_SERIALIZATION == null) { DEFAULT_KUBERNETES_SERIALIZATION = new AccessibleKubernetesSerialization(); } - return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), DEFAULT_KUBERNETES_SERIALIZATION); + return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), implicitPreserveUnknownFields); + } + + public ResolvingContext forkContext() { + return new ResolvingContext(objectMapper, uriToJacksonSchema, implicitPreserveUnknownFields); + } + + public ResolvingContext(ObjectMapper mapper, boolean implicitPreserveUnknownFields) { + this(mapper, new ConcurrentHashMap<>(), implicitPreserveUnknownFields); } - public ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization) { - serializationConfig = mapper.getSerializationConfig(); - this.kubernetesSerialization = kubernetesSerialization; + private ResolvingContext(ObjectMapper mapper, Map uriToJacksonSchema, + boolean implicitPreserveUnknownFields) { + this.uriToJacksonSchema = uriToJacksonSchema; + this.objectMapper = mapper; + this.implicitPreserveUnknownFields = implicitPreserveUnknownFields; generator = new JsonSchemaGenerator(mapper, new WrapperFactory() { @Override diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java index cb46efa4407..46f9cf50418 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -35,23 +35,24 @@ import java.util.List; import java.util.Optional; +import java.util.Queue; import java.util.TreeMap; -import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import java.util.stream.Stream; public class CustomResourceHandler extends AbstractCustomResourceHandler { - private List crds = new CopyOnWriteArrayList<>(); + private Queue crds = new ConcurrentLinkedQueue<>(); public static final String VERSION = "v1"; @Override - public void handle(CustomResourceInfo config) { + public void handle(CustomResourceInfo config, ResolvingContext resolvingContext) { final String name = config.crdName(); final String version = config.version(); - JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), config.definition()); + JsonSchema resolver = new JsonSchema(resolvingContext, config.definition()); JSONSchemaProps schema = resolver.getSchema(); CustomResourceDefinitionVersionBuilder builder = new CustomResourceDefinitionVersionBuilder() @@ -146,16 +147,16 @@ private CustomResourceDefinition combine(List definiti .map(CustomResourceDefinitionVersion::getName) .collect(Collectors.toList()); - if (storageVersions.size() > 1) { + if (storageVersions.size() > 1) { throw new IllegalStateException(String.format( "'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.", primary.getMetadata().getName(), storageVersions)); - } + } - versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName); + versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName); - //TODO: we could double check that the top-level metadata is consistent across all versions - return new CustomResourceDefinitionBuilder(primary).editSpec().withVersions(versions).endSpec().build(); + //TODO: we could double check that the top-level metadata is consistent across all versions + return new CustomResourceDefinitionBuilder(primary).editSpec().withVersions(versions).endSpec().build(); } } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java index b5fa2c60167..e845d4c89c2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/JsonSchema.java @@ -41,7 +41,7 @@ public static class V1JSONSchemaProps extends JSONSchemaProps implements Kuberne } public static JSONSchemaProps from(Class definition) { - return new JsonSchema(ResolvingContext.defaultResolvingContext(), definition).getSchema(); + return new JsonSchema(ResolvingContext.defaultResolvingContext(false), definition).getSchema(); } public JsonSchema(ResolvingContext resolvingContext, Class definition) { diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java index 39b77d0a5fd..55cfe8837ab 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java @@ -151,6 +151,7 @@ public enum AnnotatedEnum { non("N"), @JsonProperty("oui") es("O"), + @JsonProperty("foo") @JsonIgnore Maybe("Maybe"); diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java index ad00af91735..ac1327e8969 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java @@ -38,6 +38,7 @@ import io.fabric8.crdv2.example.extraction.NestedSchemaSwap; import io.fabric8.crdv2.example.json.ContainingJson; import io.fabric8.crdv2.example.person.Person; +import io.fabric8.crdv2.generator.ResolvingContext; import io.fabric8.kubernetes.api.model.AnyType; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.Quantity; @@ -188,7 +189,7 @@ void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingExcepti @Test void shouldProduceKubernetesPreserveFields() { - JSONSchemaProps schema = JsonSchema.from(ContainingJson.class); + JSONSchemaProps schema = new JsonSchema(ResolvingContext.defaultResolvingContext(true), ContainingJson.class).getSchema(); assertNotNull(schema); Map properties = assertSchemaHasNumberOfProperties(schema, 2); final JSONSchemaProps specSchema = properties.get("spec"); @@ -344,7 +345,7 @@ public void setValues(Map values) { @Test void testPreserveUnknown() { - JSONSchemaProps schema = JsonSchema.from(PreserveUnknown.class); + JSONSchemaProps schema = new JsonSchema(ResolvingContext.defaultResolvingContext(true), PreserveUnknown.class).getSchema(); assertNotNull(schema); assertEquals(0, schema.getProperties().size()); assertEquals(Boolean.TRUE, schema.getXKubernetesPreserveUnknownFields()); diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java index 095d80b28ea..411c97cb341 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java @@ -30,7 +30,7 @@ class SpecReplicasPathTest { @Test public void shoudDetectSpecReplicasPath() throws Exception { - JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), WebServerWithStatusProperty.class); + JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithStatusProperty.class); Optional path = resolver.getSinglePath(SpecReplicas.class); assertTrue(path.isPresent()); assertEquals(".replicas", path.get()); @@ -38,7 +38,7 @@ public void shoudDetectSpecReplicasPath() throws Exception { @Test public void shoudDetectNestedSpecReplicasPath() throws Exception { - JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(), WebServerWithSpec.class); + JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithSpec.class); Optional path = resolver.getSinglePath(SpecReplicas.class); assertTrue(path.isPresent()); assertEquals(".spec.replicas", path.get()); diff --git a/crd-generator/test/pom.xml b/crd-generator/test/pom.xml index f667ad46976..96240459e9e 100644 --- a/crd-generator/test/pom.xml +++ b/crd-generator/test/pom.xml @@ -38,12 +38,7 @@ io.fabric8 crd-generator-apt - - - io.fabric8 - crd-generator-api-v2 - - + org.projectlombok lombok @@ -81,13 +76,6 @@ ${keycloak-version} test - - - org.jboss.logging - jboss-logging - 3.5.3.Final - test - diff --git a/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java b/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java index a9f615e6de1..881c1c36c48 100644 --- a/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java +++ b/crd-generator/test/src/test/java/io/fabric8/crd/generator/keycloak/KeycloakRealmCRDTest.java @@ -15,46 +15,19 @@ */ package io.fabric8.crd.generator.keycloak; -import io.fabric8.crdv2.generator.CRDGenerator; -import io.fabric8.crdv2.generator.CRDGenerator.AbstractCRDOutput; import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; -import io.fabric8.kubernetes.client.utils.IOHelpers; import io.fabric8.kubernetes.client.utils.Serialization; import org.junit.jupiter.api.Test; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.OutputStream; - -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; class KeycloakRealmCRDTest { @Test - void testCrd() throws IOException { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - CRDGenerator generator = new CRDGenerator().withOutput(new AbstractCRDOutput() { - - @Override - protected OutputStream createStreamFor(String crdName) throws IOException { - return baos; - } - - }); - generator.customResourceClasses(KeycloakRealm.class); - generator.generate(); - String result = baos.toString(); - - CustomResourceDefinition d1 = Serialization.unmarshal(result, - CustomResourceDefinition.class); - - CustomResourceDefinition d2 = Serialization.unmarshal(getClass().getClassLoader() + void testCrd() { + CustomResourceDefinition d = Serialization.unmarshal(getClass().getClassLoader() .getResourceAsStream("META-INF/fabric8/keycloakrealms.sample.fabric8.io-v1.yml"), CustomResourceDefinition.class); - - assertEquals(IOHelpers.readFully(getClass().getClassLoader() - .getResourceAsStream("META-INF/fabric8/keycloakrealms.sample.fabric8.io-v1.yml")), result); - - //assertNotNull(d); // just make sure it generates + assertNotNull(d); // just make sure it generates } } From 8d51ec8ef84fc3f39ae794119858eab60425cf67 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Mon, 29 Apr 2024 09:31:21 -0400 Subject: [PATCH 08/16] removing deprecated method, adding another test --- .../crdv2/generator/AbstractJsonSchema.java | 2 +- .../io/fabric8/crdv2/generator/CRDInfo.java | 9 --------- .../crdv2/generator/v1/JsonSchemaTest.java | 17 +++++++++++++++++ 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index 608861a1f4f..2247d30f572 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -216,6 +216,7 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) { defaultValue = beanProperty.getMetadata().getDefaultValue(); schemaFrom = ofNullable(beanProperty.getAnnotation(SchemaFrom.class)).map(SchemaFrom::type).orElse(null); + preserveUnknownFields = beanProperty.getAnnotation(PreserveUnknownFields.class) != null; if (value.isValueTypeSchema()) { ValueTypeSchema valueTypeSchema = value.asValueTypeSchema(); @@ -244,7 +245,6 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) { required = beanProperty.getAnnotation(Required.class) != null; defaultValue = ofNullable(beanProperty.getAnnotation(Default.class)).map(Default::value).orElse(defaultValue); pattern = ofNullable(beanProperty.getAnnotation(Pattern.class)).map(Pattern::value).orElse(pattern); - preserveUnknownFields = beanProperty.getAnnotation(PreserveUnknownFields.class) != null; } public void updateSchema(T schema) { diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java index 25310b5518c..ac5281b639d 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java @@ -30,15 +30,6 @@ public String getCrdName() { return crdName; } - /** - * @deprecated Use {@link #getCrdSpecVersion()} instead - * @return the CRD spec version - */ - @Deprecated - public String getVersion() { - return getCrdSpecVersion(); - } - public String getCrdSpecVersion() { return crdSpecVersion; } diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java index ac1327e8969..bd9fb262584 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java @@ -18,6 +18,8 @@ import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; @@ -351,6 +353,21 @@ void testPreserveUnknown() { assertEquals(Boolean.TRUE, schema.getXKubernetesPreserveUnknownFields()); } + private static class WithDefaultAndDescription { + @JsonProperty(defaultValue = "xzy") + @JsonPropertyDescription("I'm x") + public String x; + } + + @Test + void testDefaultAndDescription() { + JSONSchemaProps schema = JsonSchema.from(WithDefaultAndDescription.class); + assertNotNull(schema); + JSONSchemaProps x = schema.getProperties().get("x"); + assertEquals("xzy", x.getDefault().textValue()); + assertEquals("I'm x", x.getDescription()); + } + // implicitly uses AnySchema private static class MySerializer extends StdSerializer { From cfa0f60aef58f8b753e0cd8aae1ecabd2309e7e3 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Mon, 29 Apr 2024 12:18:32 -0400 Subject: [PATCH 09/16] Update crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bernhard Strähle --- .../java/io/fabric8/crdv2/generator/AbstractJsonSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index 2247d30f572..a20fdf8e4b1 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -467,7 +467,7 @@ private T resolveProperty(LinkedHashMap visited, InternalSchemaS * we've added support for ignoring an enum values, which complicates this processing * as that is something not supported directly by jackson */ - private Set findIngoredEnumConstants(JavaType type) { + private Set findIgnoredEnumConstants(JavaType type) { Field[] fields = type.getRawClass().getFields(); Set toIgnore = new HashSet<>(); for (Field field : fields) { From 4352572fbb3819f0bdf9e175d304cb6763fac433 Mon Sep 17 00:00:00 2001 From: Steven Hawkins Date: Mon, 29 Apr 2024 12:19:05 -0400 Subject: [PATCH 10/16] Update crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Bernhard Strähle --- .../io/fabric8/crdv2/generator/v1/CustomResourceHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java index 46f9cf50418..96bf464480a 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -71,7 +71,7 @@ public void handle(CustomResourceInfo config, ResolvingContext resolvingContext) PrinterColumn printerColumn = ((PrinterColumn) property.annotation); String column = printerColumn.name(); if (Utils.isNullOrEmpty(column)) { - column = path.substring(path.lastIndexOf(".")); + column = path.substring(path.lastIndexOf(".") + 1).toUpperCase(); } String format = printerColumn.format(); int priority = printerColumn.priority(); From ef05f18235fe01c2f714df05d0d7392e90c48af3 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Mon, 29 Apr 2024 12:27:33 -0400 Subject: [PATCH 11/16] finishing off review changes and adding a test --- .../java/io/fabric8/crdv2/generator/AbstractJsonSchema.java | 2 +- .../java/io/fabric8/crdv2/example/complex/ComplexStatus.java | 2 +- .../api-v2/src/test/resources/complexkinds.example.com-v1.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index a20fdf8e4b1..394638bc43b 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -397,7 +397,7 @@ private T resolveProperty(LinkedHashMap visited, InternalSchemaS // currently on string enums are supported StringSchema stringSchema = jacksonSchema.asStringSchema(); if (!stringSchema.getEnums().isEmpty()) { - Set ignores = type.isEnumType() ? findIngoredEnumConstants(type) : Collections.emptySet(); + Set ignores = type.isEnumType() ? findIgnoredEnumConstants(type) : Collections.emptySet(); final JsonNode[] enumValues = stringSchema.getEnums().stream() .sorted() .filter(s -> !ignores.contains(s)) diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java index daa35af8451..98061a60ead 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/complex/ComplexStatus.java @@ -40,7 +40,7 @@ public ComplexStatus() { private State state; @JsonProperty("message") - @PrinterColumn(name = "Message") + @PrinterColumn() private String message; public State getState() { diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml index a1b7ed14ba5..901cee2cbaa 100644 --- a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml +++ b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml @@ -29,7 +29,7 @@ spec: versions: - additionalPrinterColumns: - jsonPath: .status.message - name: Message + name: MESSAGE priority: 0 type: string - jsonPath: .status.state From abe9a2aadbafe5a1a0e4f864e1cf1441f3384996 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Mon, 29 Apr 2024 13:59:36 -0400 Subject: [PATCH 12/16] replacing the YAML_MAPPER with standard serialization --- .../crdv2/generator/AbstractJsonSchema.java | 10 +- .../fabric8/crdv2/generator/CRDGenerator.java | 33 +-- .../crdv2/generator/ResolvingContext.java | 15 +- .../resources/complexkinds.example.com-v1.yml | 201 +++++++++--------- .../k8svalidations.samples.fabric8.io-v1.yml | 171 +++++++-------- .../multiples.sample.fabric8.io-v1.yml | 37 ++-- 6 files changed, 229 insertions(+), 238 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index 394638bc43b..d82b94ae6c7 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -17,7 +17,6 @@ import com.fasterxml.jackson.annotation.JsonFormat.Value; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.BeanProperty; @@ -79,7 +78,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.fabric8.crdv2.generator.CRDGenerator.YAML_MAPPER; import static java.util.Optional.ofNullable; /** @@ -226,7 +224,7 @@ public PropertyMetadata(JsonSchema value, BeanProperty beanProperty) { if (value.isStringSchema()) { StringSchema stringSchema = value.asStringSchema(); // only set if ValidationSchemaFactoryWrapper is used - this.pattern = stringSchema.getPattern(); // looks for the Pattern annotation + this.pattern = stringSchema.getPattern(); this.max = ofNullable(stringSchema.getMaxLength()).map(Integer::doubleValue).orElse(null); this.min = ofNullable(stringSchema.getMinLength()).map(Integer::doubleValue).orElse(null); } else { @@ -252,9 +250,9 @@ public void updateSchema(T schema) { if (defaultValue != null) { try { - schema.setDefault(YAML_MAPPER.readTree(defaultValue)); - } catch (JsonProcessingException e) { - throw new IllegalArgumentException("Cannot parse default value: '" + defaultValue + "' as valid YAML."); + schema.setDefault(resolvingContext.kubernetesSerialization.convertValue(defaultValue, JsonNode.class)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Cannot parse default value: '" + defaultValue + "' as valid YAML.", e); } } if (nullable) { diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index e2a4042ec51..0df30063270 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -15,15 +15,11 @@ */ package io.fabric8.crdv2.generator; -import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.json.JsonMapper; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; import io.fabric8.crdv2.generator.v1.CustomResourceHandler; import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.client.utils.ApiVersionUtil; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,6 +29,7 @@ import java.io.IOException; import java.io.OutputStream; import java.net.URI; +import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; @@ -46,10 +43,6 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; -import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; -import static com.fasterxml.jackson.annotation.JsonInclude.Value.construct; - public class CRDGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(CRDGenerator.class); @@ -58,20 +51,9 @@ public class CRDGenerator { private boolean parallel; private boolean implicitPreserveUnknownFields; private ObjectMapper objectMapper; + private KubernetesSerialization kubernetesSerialization; private Map infos; - // TODO: why not rely on the standard fabric8 yaml mapping - public static final ObjectMapper YAML_MAPPER = JsonMapper.builder(new YAMLFactory() - .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) - .enable(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS) - .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)) - .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) - .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) - .configure(SerializationFeature.INDENT_OUTPUT, true) - .withConfigOverride(Map.class, configOverride -> configOverride.setInclude(construct(NON_NULL, NON_NULL))) - .serializationInclusion(NON_EMPTY) - .build(); - public CRDGenerator inOutputDir(File outputDir) { output = new DirCRDOutput(outputDir); return this; @@ -92,8 +74,9 @@ public CRDGenerator withParallelGenerationEnabled(boolean parallel) { return this; } - public CRDGenerator withObjectMapper(ObjectMapper mapper) { + public CRDGenerator withObjectMapper(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization) { this.objectMapper = mapper; + this.kubernetesSerialization = kubernetesSerialization; return this; } @@ -172,8 +155,9 @@ public CRDGenerationInfo detailedGenerate() { final ResolvingContext context; if (this.objectMapper == null) { context = ResolvingContext.defaultResolvingContext(implicitPreserveUnknownFields); + this.kubernetesSerialization = context.kubernetesSerialization; } else { - context = new ResolvingContext(this.objectMapper, implicitPreserveUnknownFields); + context = new ResolvingContext(this.objectMapper, this.kubernetesSerialization, implicitPreserveUnknownFields); } for (CustomResourceInfo info : infos.values()) { @@ -208,7 +192,8 @@ public void emitCrd(HasMetadata crd, CRDGenerationInfo crdGenerationInfo) { outputStream.write( "# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n" .getBytes()); - YAML_MAPPER.writeValue(outputStream, crd); + String yaml = kubernetesSerialization.asYaml(crd); + outputStream.write(yaml.getBytes(StandardCharsets.UTF_8)); final URI fileURI = output.crdURI(outputName); crdGenerationInfo.add(crdName, version, fileURI); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java index df7fccaf561..82cb28a9f4b 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -87,6 +87,7 @@ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) { final JsonSchemaGenerator generator; final ObjectMapper objectMapper; + final KubernetesSerialization kubernetesSerialization; final Map uriToJacksonSchema; final boolean implicitPreserveUnknownFields; @@ -105,21 +106,25 @@ public static ResolvingContext defaultResolvingContext(boolean implicitPreserveU if (DEFAULT_KUBERNETES_SERIALIZATION == null) { DEFAULT_KUBERNETES_SERIALIZATION = new AccessibleKubernetesSerialization(); } - return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), implicitPreserveUnknownFields); + return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), DEFAULT_KUBERNETES_SERIALIZATION, + implicitPreserveUnknownFields); } public ResolvingContext forkContext() { - return new ResolvingContext(objectMapper, uriToJacksonSchema, implicitPreserveUnknownFields); + return new ResolvingContext(objectMapper, kubernetesSerialization, uriToJacksonSchema, implicitPreserveUnknownFields); } - public ResolvingContext(ObjectMapper mapper, boolean implicitPreserveUnknownFields) { - this(mapper, new ConcurrentHashMap<>(), implicitPreserveUnknownFields); + public ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization, + boolean implicitPreserveUnknownFields) { + this(mapper, kubernetesSerialization, new ConcurrentHashMap<>(), implicitPreserveUnknownFields); } - private ResolvingContext(ObjectMapper mapper, Map uriToJacksonSchema, + private ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization, + Map uriToJacksonSchema, boolean implicitPreserveUnknownFields) { this.uriToJacksonSchema = uriToJacksonSchema; this.objectMapper = mapper; + this.kubernetesSerialization = kubernetesSerialization; this.implicitPreserveUnknownFields = implicitPreserveUnknownFields; generator = new JsonSchemaGenerator(mapper, new WrapperFactory() { diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml index 901cee2cbaa..bf47592f36e 100644 --- a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml +++ b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml @@ -15,200 +15,201 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition +--- +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" metadata: - name: complexkinds.example.com + name: "complexkinds.example.com" spec: - group: example.com + group: "example.com" names: - kind: ComplexKind - plural: complexkinds - singular: complexkind - scope: Namespaced + kind: "ComplexKind" + plural: "complexkinds" + singular: "complexkind" + scope: "Namespaced" versions: - additionalPrinterColumns: - - jsonPath: .status.message - name: MESSAGE + - jsonPath: ".status.message" + name: "MESSAGE" priority: 0 - type: string - - jsonPath: .status.state - name: State + type: "string" + - jsonPath: ".status.state" + name: "State" priority: 0 - type: string - name: v1 + type: "string" + name: "v1" schema: openAPIV3Schema: properties: spec: properties: actuatorPort: - type: integer + type: "integer" configMapName: - type: string + type: "string" metricsPath: - type: string + type: "string" metricsPort: - type: integer + type: "integer" services: items: properties: metadata: - description: The metadata of this Service + description: "The metadata of this Service" properties: annotations: additionalProperties: - type: string - type: object + type: "string" + type: "object" creationTimestamp: - type: string + type: "string" deletionGracePeriodSeconds: - type: integer + type: "integer" deletionTimestamp: - type: string + type: "string" finalizers: items: - type: string - type: array + type: "string" + type: "array" generateName: - type: string + type: "string" generation: - type: integer + type: "integer" labels: additionalProperties: - type: string - type: object + type: "string" + type: "object" name: - type: string + type: "string" namespace: - type: string + type: "string" resourceVersion: - type: string + type: "string" selfLink: - type: string + type: "string" uid: - type: string - type: object + type: "string" + type: "object" spec: - description: The spec of this Service + description: "The spec of this Service" nullable: true properties: allocateLoadBalancerNodePorts: - type: boolean + type: "boolean" clusterIP: - type: string + type: "string" clusterIPs: items: - type: string - type: array + type: "string" + type: "array" externalIPs: items: - type: string - type: array + type: "string" + type: "array" externalName: - type: string + type: "string" externalTrafficPolicy: - type: string + type: "string" healthCheckNodePort: - type: integer + type: "integer" internalTrafficPolicy: - type: string + type: "string" ipFamilies: items: - type: string - type: array + type: "string" + type: "array" ipFamilyPolicy: - type: string + type: "string" loadBalancerClass: - type: string + type: "string" loadBalancerIP: - type: string + type: "string" loadBalancerSourceRanges: items: - type: string - type: array + type: "string" + type: "array" publishNotReadyAddresses: - type: boolean + type: "boolean" selector: additionalProperties: - type: string - type: object + type: "string" + type: "object" sessionAffinity: - type: string + type: "string" type: - type: string - type: object - type: object - type: array + type: "string" + type: "object" + type: "object" + type: "array" statefulSet: properties: metadata: - description: The metadata of this StatefulSet + description: "The metadata of this StatefulSet" properties: annotations: additionalProperties: - type: string - type: object + type: "string" + type: "object" creationTimestamp: - type: string + type: "string" deletionGracePeriodSeconds: - type: integer + type: "integer" deletionTimestamp: - type: string + type: "string" finalizers: items: - type: string - type: array + type: "string" + type: "array" generateName: - type: string + type: "string" generation: - type: integer + type: "integer" labels: additionalProperties: - type: string - type: object + type: "string" + type: "object" name: - type: string + type: "string" namespace: - type: string + type: "string" resourceVersion: - type: string + type: "string" selfLink: - type: string + type: "string" uid: - type: string - type: object + type: "string" + type: "object" spec: - description: The spec of this StatefulSet + description: "The spec of this StatefulSet" properties: minReadySeconds: - type: integer + type: "integer" podManagementPolicy: - type: string + type: "string" replicas: - type: integer + type: "integer" revisionHistoryLimit: - type: integer + type: "integer" serviceName: - type: string - type: object - type: object - type: object + type: "string" + type: "object" + type: "object" + type: "object" status: properties: message: - type: string + type: "string" state: enum: - - CREATED - - ERROR - - ROLLING_UPDATE - - RUNNING - - SCALING - - STARTING - type: string - type: object - type: object + - "CREATED" + - "ERROR" + - "ROLLING_UPDATE" + - "RUNNING" + - "SCALING" + - "STARTING" + type: "string" + type: "object" + type: "object" served: true storage: true subresources: diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml index 0ae072eb120..43c15f5cf34 100644 --- a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml +++ b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml @@ -15,19 +15,20 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition +--- +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" metadata: - name: k8svalidations.samples.fabric8.io + name: "k8svalidations.samples.fabric8.io" spec: - group: samples.fabric8.io + group: "samples.fabric8.io" names: - kind: K8sValidation - plural: k8svalidations - singular: k8svalidation - scope: Cluster + kind: "K8sValidation" + plural: "k8svalidations" + singular: "k8svalidation" + scope: "Cluster" versions: - - name: v1alpha1 + - name: "v1alpha1" schema: openAPIV3Schema: properties: @@ -38,123 +39,123 @@ spec: deepLevel2: properties: simple: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('deep-') + - rule: "self.startsWith('deep-')" valueL2: - type: string + type: "string" required: - - valueL2 - type: object + - "valueL2" + type: "object" valueL1: - type: string + type: "string" required: - - deepLevel2 - - valueL1 - type: object + - "deepLevel2" + - "valueL1" + type: "object" x-kubernetes-validations: - - messageExpression: '''valueL1 ('' + self.valueL1 + '') must be equal - to deepLevel2.valueL2 ('' + self.deepLevel2.valueL2 + '')''' - rule: self.valueL1 == self.deepLevel2.valueL2 + - messageExpression: "'valueL1 (' + self.valueL1 + ') must be equal\ + \ to deepLevel2.valueL2 (' + self.deepLevel2.valueL2 + ')'" + rule: "self.valueL1 == self.deepLevel2.valueL2" immutable: - type: string + type: "string" x-kubernetes-validations: - - message: cannot be changed once set - rule: self == oldSelf + - message: "cannot be changed once set" + rule: "self == oldSelf" maxReplicas: - type: integer + type: "integer" minReplicas: - type: integer + type: "integer" monotonicCounter: - type: integer + type: "integer" x-kubernetes-validations: - - message: cannot decrease value once set - reason: FieldValueForbidden - rule: self >= oldSelf + - message: "cannot decrease value once set" + reason: "FieldValueForbidden" + rule: "self >= oldSelf" multiple: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('start-') - - rule: self.endsWith('-end') + - rule: "self.startsWith('start-')" + - rule: "self.endsWith('-end')" namePrefix: - type: string + type: "string" onAbstractClass: properties: dummy: - type: string + type: "string" required: - - dummy - type: object + - "dummy" + type: "object" x-kubernetes-validations: - - rule: self.dummy.startsWith('abstract-') + - rule: "self.dummy.startsWith('abstract-')" onAttributeAndClass: properties: dummy: - type: string + type: "string" required: - - dummy - type: object + - "dummy" + type: "object" x-kubernetes-validations: - - rule: self.dummy.startsWith('on-class-') - - rule: self.dummy.startsWith('on-attr-') + - rule: "self.dummy.startsWith('on-class-')" + - rule: "self.dummy.startsWith('on-attr-')" onAttributeAndGetter: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('start-') - - rule: self.endsWith('-end') + - rule: "self.startsWith('start-')" + - rule: "self.endsWith('-end')" onGetter: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('on-getter-') + - rule: "self.startsWith('on-getter-')" priority: enum: - - high - - low - - medium - type: string + - "high" + - "low" + - "medium" + type: "string" x-kubernetes-validations: - - message: cannot transition directly between 'low' and 'high' - rule: '!(self == ''high'' && oldSelf == ''low'') && !(self == ''low'' - && oldSelf == ''high'')' + - message: "cannot transition directly between 'low' and 'high'" + rule: "!(self == 'high' && oldSelf == 'low') && !(self == 'low'\ + \ && oldSelf == 'high')" replicas: - type: integer + type: "integer" simple: - type: string + type: "string" x-kubernetes-validations: - - rule: self.startsWith('simple-') + - rule: "self.startsWith('simple-')" required: - - deepLevel1 - - maxReplicas - - minReplicas - - multiple - - namePrefix - - onAbstractClass - - onAttributeAndClass - - onAttributeAndGetter - - onGetter - - priority - - replicas - - simple - type: object + - "deepLevel1" + - "maxReplicas" + - "minReplicas" + - "multiple" + - "namePrefix" + - "onAbstractClass" + - "onAttributeAndClass" + - "onAttributeAndGetter" + - "onGetter" + - "priority" + - "replicas" + - "simple" + type: "object" x-kubernetes-validations: - - fieldPath: .replicas - rule: self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas - - message: replicas must be greater than or equal to minReplicas - rule: self.minReplicas <= self.replicas - - message: replicas must be smaller than or equal to maxReplicas - rule: self.replicas <= self.maxReplicas + - fieldPath: ".replicas" + rule: "self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas" + - message: "replicas must be greater than or equal to minReplicas" + rule: "self.minReplicas <= self.replicas" + - message: "replicas must be smaller than or equal to maxReplicas" + rule: "self.replicas <= self.maxReplicas" status: properties: availableReplicas: - type: integer - type: object - type: object + type: "integer" + type: "object" + type: "object" x-kubernetes-validations: - - messageExpression: '''name must start with '' + self.spec.namePrefix' - reason: FieldValueForbidden - rule: self.metadata.name.startsWith(self.spec.namePrefix) - - message: updates not allowed in degraded state - rule: self.status.availableReplicas >= self.spec.minReplicas + - messageExpression: "'name must start with ' + self.spec.namePrefix" + reason: "FieldValueForbidden" + rule: "self.metadata.name.startsWith(self.spec.namePrefix)" + - message: "updates not allowed in degraded state" + rule: "self.status.availableReplicas >= self.spec.minReplicas" served: true storage: true subresources: diff --git a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml index 50a58ec3692..0ad05e879a0 100644 --- a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml +++ b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml @@ -15,43 +15,44 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! -apiVersion: apiextensions.k8s.io/v1 -kind: CustomResourceDefinition +--- +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" metadata: - name: multiples.sample.fabric8.io + name: "multiples.sample.fabric8.io" spec: - group: sample.fabric8.io + group: "sample.fabric8.io" names: - kind: Multiple - plural: multiples - singular: multiple - scope: Cluster + kind: "Multiple" + plural: "multiples" + singular: "multiple" + scope: "Cluster" versions: - - name: v2 + - name: "v2" schema: openAPIV3Schema: properties: spec: properties: v2: - type: string - type: object + type: "string" + type: "object" status: - type: object - type: object + type: "object" + type: "object" served: true storage: true - - name: v1 + - name: "v1" schema: openAPIV3Schema: properties: spec: properties: v1: - type: string - type: object + type: "string" + type: "object" status: - type: object - type: object + type: "object" + type: "object" served: true storage: false From 006f6a7ff8a8b004db48bc94811bd7006dc1ed98 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Tue, 30 Apr 2024 09:46:43 -0400 Subject: [PATCH 13/16] adding back a common method for printer columns also using string instead of object or array types --- .../AbstractCustomResourceHandler.java | 41 +++++++++++++++++++ .../crdv2/generator/AbstractJsonSchema.java | 14 +++---- .../generator/KubernetesJSONSchemaProps.java | 4 ++ .../generator/v1/CustomResourceHandler.java | 35 +++++----------- .../crdv2/generator/CRDGeneratorTest.java | 2 +- 5 files changed, 63 insertions(+), 33 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index 1f5e4b20fb4..46054e40424 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -15,8 +15,14 @@ */ package io.fabric8.crdv2.generator; +import io.fabric8.crd.generator.annotation.PrinterColumn; +import io.fabric8.crdv2.generator.AbstractJsonSchema.AnnotationMetadata; import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.client.utils.Utils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.TreeMap; import java.util.stream.Stream; /** @@ -25,8 +31,43 @@ */ public abstract class AbstractCustomResourceHandler { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJsonSchema.class); + public abstract void handle(CustomResourceInfo config, ResolvingContext resolvingContext); + public interface PrinterColumnHandler { + void addPrinterColumn(String path, String column, String format, + int priority, String type, String description); + } + + protected void handlePrinterColumns(AbstractJsonSchema resolver, PrinterColumnHandler handler) { + TreeMap sortedCols = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + sortedCols.putAll(resolver.getAllPaths(PrinterColumn.class)); + sortedCols.forEach((path, property) -> { + PrinterColumn printerColumn = ((PrinterColumn) property.annotation); + String column = printerColumn.name(); + if (Utils.isNullOrEmpty(column)) { + column = path.substring(path.lastIndexOf(".") + 1).toUpperCase(); + } + String format = printerColumn.format(); + format = Utils.isNotNullOrEmpty(format) ? format : null; + int priority = printerColumn.priority(); + String type = property.schema.getType(); + if ("object".equals(type) || "array".equals(type)) { + LOGGER.warn("Printer column '{}' has a type '{}' that is not allowed, will use string intead", column, type); + type = "string"; + } else if ("string".equals(type) && "date".equals(property.schema.getFormat())) { + type = "date"; + } + + // TODO: add description to the annotation? The previous logic considered the comments, which are not available here + String description = property.schema.getDescription(); + description = Utils.isNotNullOrEmpty(description) ? description : null; + + handler.addPrinterColumn(path, column, format, priority, type, description); + }); + } + public abstract Stream finish(); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index d82b94ae6c7..f65fce97ac3 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -95,13 +95,11 @@ public abstract class AbstractJsonSchema visited, InternalSchemaSwa T schema = resolveProperty(visited, schemaSwaps, name, type, propertySchema); + propertyMetadata.updateSchema(schema); + if (!swapResult.onGoing) { for (Entry, LinkedHashMap> entry : pathMetadata.entrySet()) { ofNullable(beanProperty.getAnnotation(entry.getKey())).ifPresent( ann -> entry.getValue().put(toFQN(savedVisited, name), - new AnnotationMetadata(ann, propertyMetadata.description, schema.getType()))); + new AnnotationMetadata(ann, schema))); } } - propertyMetadata.updateSchema(schema); - visited = savedVisited; addProperty(name, objectSchema, schema); diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java index 90d39ac63e6..e548f08b517 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.java @@ -24,6 +24,10 @@ public interface KubernetesJSONSchemaProps { String getType(); + String getFormat(); + + String getDescription(); + void setXKubernetesPreserveUnknownFields(Boolean b); void setMaximum(Double max); diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java index 96bf464480a..66efd563412 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -15,9 +15,7 @@ */ package io.fabric8.crdv2.generator.v1; -import io.fabric8.crd.generator.annotation.PrinterColumn; import io.fabric8.crdv2.generator.AbstractCustomResourceHandler; -import io.fabric8.crdv2.generator.AbstractJsonSchema.AnnotationMetadata; import io.fabric8.crdv2.generator.CRDUtils; import io.fabric8.crdv2.generator.CustomResourceInfo; import io.fabric8.crdv2.generator.ResolvingContext; @@ -36,7 +34,6 @@ import java.util.List; import java.util.Optional; import java.util.Queue; -import java.util.TreeMap; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -65,28 +62,18 @@ public void handle(CustomResourceInfo config, ResolvingContext resolvingContext) .withOpenAPIV3Schema(schema) .endSchema(); - TreeMap sortedCols = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - sortedCols.putAll(resolver.getAllPaths(PrinterColumn.class)); - sortedCols.forEach((path, property) -> { - PrinterColumn printerColumn = ((PrinterColumn) property.annotation); - String column = printerColumn.name(); - if (Utils.isNullOrEmpty(column)) { - column = path.substring(path.lastIndexOf(".") + 1).toUpperCase(); + handlePrinterColumns(resolver, new PrinterColumnHandler() { + @Override + public void addPrinterColumn(String path, String column, String format, int priority, String type, String description) { + builder.addNewAdditionalPrinterColumn() + .withType(type) + .withName(column) + .withJsonPath(path) + .withFormat(Utils.isNotNullOrEmpty(format) ? format : null) + .withDescription(Utils.isNotNullOrEmpty(description) ? description : null) + .withPriority(priority) + .endAdditionalPrinterColumn(); } - String format = printerColumn.format(); - int priority = printerColumn.priority(); - - // TODO: add description to the annotation? The previous logic considered the comments, which are not available here - String description = property.description; - - builder.addNewAdditionalPrinterColumn() - .withType(property.type) - .withName(column) - .withJsonPath(path) - .withFormat(Utils.isNotNullOrEmpty(format) ? format : null) - .withDescription(Utils.isNotNullOrEmpty(description) ? description : null) - .withPriority(priority) - .endAdditionalPrinterColumn(); }); resolver.getSinglePath(SpecReplicas.class).ifPresent(path -> { diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java index 924fc599559..1869b67bc75 100644 --- a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java @@ -377,7 +377,7 @@ void jokerequestCRDShouldWork() { assertEquals("jokeCategory", columnDefinition.getName()); assertEquals(1, columnDefinition.getPriority()); columnDefinition = printerColumns.get(1); - assertEquals("array", columnDefinition.getType()); + assertEquals("string", columnDefinition.getType()); assertEquals(".spec.excluded", columnDefinition.getJsonPath()); assertEquals("excludedTopics", columnDefinition.getName()); assertEquals(0, columnDefinition.getPriority()); From 7f14fbbdc475569e57e5d3c965e72a7f48058228 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Tue, 30 Apr 2024 12:38:56 -0400 Subject: [PATCH 14/16] adding back dependent class names --- .../AbstractCustomResourceHandler.java | 4 ++- .../crdv2/generator/AbstractJsonSchema.java | 36 +++++++++++-------- .../crdv2/generator/CRDGenerationInfo.java | 5 +-- .../fabric8/crdv2/generator/CRDGenerator.java | 18 +++++----- .../io/fabric8/crdv2/generator/CRDInfo.java | 10 +++++- .../crdv2/generator/ResolvingContext.java | 9 +++++ .../generator/v1/CustomResourceHandler.java | 28 ++++++++++----- 7 files changed, 73 insertions(+), 37 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java index 46054e40424..317c6b7baf2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.java @@ -22,6 +22,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Map; +import java.util.Set; import java.util.TreeMap; import java.util.stream.Stream; @@ -68,6 +70,6 @@ protected void handlePrinterColumns(AbstractJsonSchema resolver, PrinterCo }); } - public abstract Stream finish(); + public abstract Stream>> finish(); } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index f65fce97ac3..256e5dcc9ed 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -21,7 +21,6 @@ import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JavaType; -import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.node.JsonNodeFactory; @@ -92,6 +91,7 @@ public abstract class AbstractJsonSchema dependentClasses = new HashSet<>(); public static class AnnotationMetadata { public final Annotation annotation; @@ -118,6 +118,10 @@ public T getSchema() { return root; } + public Set getDependentClasses() { + return dependentClasses; + } + public Optional getSinglePath(Class clazz) { return ofNullable(pathMetadata.get(clazz)).flatMap(m -> m.keySet().stream().findFirst()); } @@ -136,7 +140,7 @@ public Map getAllPaths(Class clazz) { */ private T resolveRoot(Class definition) { InternalSchemaSwaps schemaSwaps = new InternalSchemaSwaps(); - JsonSchema schema = toJsonSchema(definition); + JsonSchema schema = resolvingContext.toJsonSchema(definition); if (schema instanceof GeneratorObjectSchema) { return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata"); } @@ -285,8 +289,11 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null; } - consumeRepeatingAnnotation(gos.javaType.getRawClass(), SchemaSwap.class, ss -> { - swaps.registerSwap(gos.javaType.getRawClass(), + Class rawClass = gos.javaType.getRawClass(); + collectDependentClasses(rawClass); + + consumeRepeatingAnnotation(rawClass, SchemaSwap.class, ss -> { + swaps.registerSwap(rawClass, ss.originalType(), ss.fieldName(), ss.targetType(), ss.depth()); @@ -300,7 +307,7 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa continue; } schemaSwaps = schemaSwaps.branchDepths(); - SwapResult swapResult = schemaSwaps.lookupAndMark(gos.javaType.getRawClass(), name); + SwapResult swapResult = schemaSwaps.lookupAndMark(rawClass, name); LinkedHashMap savedVisited = visited; if (swapResult.onGoing) { visited = new LinkedHashMap<>(); @@ -331,7 +338,7 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa // fully omit - this is a little inconsistent with the NullSchema handling continue; } - propertySchema = toJsonSchema(propertyMetadata.schemaFrom); + propertySchema = resolvingContext.toJsonSchema(propertyMetadata.schemaFrom); type = resolvingContext.objectMapper.getSerializationConfig().constructType(propertyMetadata.schemaFrom); } @@ -359,12 +366,19 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa objectSchema.setXKubernetesPreserveUnknownFields(true); } List validationRules = new ArrayList<>(); - consumeRepeatingAnnotation(gos.javaType.getRawClass(), ValidationRule.class, + consumeRepeatingAnnotation(rawClass, ValidationRule.class, v -> validationRules.add(from(v))); addToValidationRules(objectSchema, validationRules); return objectSchema; } + private void collectDependentClasses(Class rawClass) { + if (rawClass != null && !rawClass.getName().startsWith("java.") && dependentClasses.add(rawClass.getName())) { + Stream.of(rawClass.getInterfaces()).forEach(this::collectDependentClasses); + collectDependentClasses(rawClass.getSuperclass()); + } + } + static String toFQN(LinkedHashMap visited, String name) { if (visited.isEmpty()) { return "." + name; @@ -494,14 +508,6 @@ private static String mapNotEmpty(String s) { return Utils.isNullOrEmpty(s) ? null : s; } - private JsonSchema toJsonSchema(Class clazz) { - try { - return resolvingContext.generator.generateSchema(clazz); - } catch (JsonMappingException e) { - throw new RuntimeException(e); - } - } - protected abstract V newKubernetesValidationRule(); /** diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java index ea85f2ab830..e216af4aebb 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.java @@ -19,6 +19,7 @@ import java.net.URI; import java.util.HashMap; import java.util.Map; +import java.util.Set; public class CRDGenerationInfo { @@ -33,9 +34,9 @@ public Map> getCRDDetailsPerNameAndVersion() { return crdNameToVersionToCRDInfoMap; } - void add(String crdName, String version, URI fileURI) { + void add(String crdName, String version, URI fileURI, Set dependentClassNames) { crdNameToVersionToCRDInfoMap.computeIfAbsent(crdName, k -> new HashMap<>()) - .put(version, new CRDInfo(crdName, version, new File(fileURI).getAbsolutePath())); + .put(version, new CRDInfo(crdName, version, new File(fileURI).getAbsolutePath(), dependentClassNames)); } public int numberOfGeneratedCRDs() { diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index 0df30063270..6b287fe7847 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -28,6 +28,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import java.io.OutputStreamWriter; import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.Arrays; @@ -173,29 +174,28 @@ public CRDGenerationInfo detailedGenerate() { handlers.values().stream().map(h -> ForkJoinPool.commonPool().submit(() -> h.handle(info, context.forkContext()))) .collect(Collectors.toList()).stream().forEach(ForkJoinTask::join); } else { - handlers.values().stream().forEach(h -> h.handle(info, context)); + handlers.values().stream().forEach(h -> h.handle(info, context.forkContext())); } } } final CRDGenerationInfo crdGenerationInfo = new CRDGenerationInfo(); - handlers.values().stream().flatMap(AbstractCustomResourceHandler::finish).forEach(crd -> emitCrd(crd, crdGenerationInfo)); + handlers.values().stream().flatMap(AbstractCustomResourceHandler::finish) + .forEach(crd -> emitCrd(crd.getKey(), crd.getValue(), crdGenerationInfo)); return crdGenerationInfo; } - public void emitCrd(HasMetadata crd, CRDGenerationInfo crdGenerationInfo) { + public void emitCrd(HasMetadata crd, Set dependentClassNames, CRDGenerationInfo crdGenerationInfo) { final String version = ApiVersionUtil.trimVersion(crd.getApiVersion()); final String crdName = crd.getMetadata().getName(); try { final String outputName = getOutputName(crdName, version); - try (final OutputStream outputStream = output.outputFor(outputName)) { - outputStream.write( - "# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n" - .getBytes()); + try (final OutputStreamWriter writer = new OutputStreamWriter(output.outputFor(outputName), StandardCharsets.UTF_8)) { + writer.write("# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n"); String yaml = kubernetesSerialization.asYaml(crd); - outputStream.write(yaml.getBytes(StandardCharsets.UTF_8)); + writer.write(yaml); final URI fileURI = output.crdURI(outputName); - crdGenerationInfo.add(crdName, version, fileURI); + crdGenerationInfo.add(crdName, version, fileURI, dependentClassNames); } } catch (IOException e) { throw new RuntimeException(e); diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java index ac5281b639d..df26d8135f2 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.java @@ -15,15 +15,19 @@ */ package io.fabric8.crdv2.generator; +import java.util.Set; + public class CRDInfo { private final String crdName; private final String crdSpecVersion; private final String filePath; + private final Set dependentClassNames; - public CRDInfo(String crdName, String crdSpecVersion, String filePath) { + public CRDInfo(String crdName, String crdSpecVersion, String filePath, Set dependentClassNames) { this.crdName = crdName; this.crdSpecVersion = crdSpecVersion; this.filePath = filePath; + this.dependentClassNames = dependentClassNames; } public String getCrdName() { @@ -38,4 +42,8 @@ public String getFilePath() { return filePath; } + public Set getDependentClassNames() { + return dependentClassNames; + } + } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java index 82cb28a9f4b..b3a5b6e7850 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.databind.BeanProperty; import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonObjectFormatVisitor; @@ -143,4 +144,12 @@ public SchemaFactoryWrapper getWrapper(SerializerProvider provider, VisitorConte }); } + JsonSchema toJsonSchema(Class clazz) { + try { + return generator.generateSchema(clazz); + } catch (JsonMappingException e) { + throw new RuntimeException(e); + } + } + } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java index 66efd563412..7088c8f0292 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java @@ -31,16 +31,20 @@ import io.fabric8.kubernetes.model.annotation.SpecReplicas; import io.fabric8.kubernetes.model.annotation.StatusReplicas; +import java.util.AbstractMap; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Optional; import java.util.Queue; +import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.stream.Collectors; import java.util.stream.Stream; public class CustomResourceHandler extends AbstractCustomResourceHandler { - private Queue crds = new ConcurrentLinkedQueue<>(); + private Queue>> crds = new ConcurrentLinkedQueue<>(); public static final String VERSION = "v1"; @@ -111,24 +115,28 @@ public void addPrinterColumn(String path, String column, String format, int prio .endSpec() .build(); - crds.add(crd); + crds.add(new AbstractMap.SimpleEntry<>(crd, resolver.getDependentClasses())); } @Override - public Stream finish() { - return crds.stream().collect(Collectors.groupingBy(crd -> crd.getMetadata().getName())).values().stream() + public Stream>> finish() { + return crds.stream().collect(Collectors.groupingBy(crd -> crd.getKey().getMetadata().getName())).values().stream() .map(this::combine); } - private CustomResourceDefinition combine(List definitions) { - CustomResourceDefinition primary = definitions.get(0); + private Map.Entry> combine( + List>> definitions) { + Map.Entry> primary = definitions.get(0); if (definitions.size() == 1) { return primary; } - List versions = definitions.stream().flatMap(crd -> crd.getSpec().getVersions().stream()) + List versions = definitions.stream() + .flatMap(crd -> crd.getKey().getSpec().getVersions().stream()) .collect(Collectors.toList()); + Set allDependentClasses = definitions.stream().flatMap(crd -> crd.getValue().stream()).collect(Collectors.toSet()); + List storageVersions = versions.stream() .filter(v -> Optional.ofNullable(v.getStorage()).orElse(true)) .map(CustomResourceDefinitionVersion::getName) @@ -137,13 +145,15 @@ private CustomResourceDefinition combine(List definiti if (storageVersions.size() > 1) { throw new IllegalStateException(String.format( "'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.", - primary.getMetadata().getName(), storageVersions)); + primary.getKey().getMetadata().getName(), storageVersions)); } versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName); //TODO: we could double check that the top-level metadata is consistent across all versions - return new CustomResourceDefinitionBuilder(primary).editSpec().withVersions(versions).endSpec().build(); + return new AbstractMap.SimpleEntry<>( + new CustomResourceDefinitionBuilder(primary.getKey()).editSpec().withVersions(versions).endSpec().build(), + allDependentClasses); } } From 2ac72a8ec5b0170d975253e351dabef5584254f5 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Tue, 30 Apr 2024 20:48:29 -0400 Subject: [PATCH 15/16] stripping the explicit start --- .../src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java | 3 ++- .../api-v2/src/test/resources/complexkinds.example.com-v1.yml | 1 - .../test/resources/k8svalidations.samples.fabric8.io-v1.yml | 1 - .../src/test/resources/multiples.sample.fabric8.io-v1.yml | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java index 6b287fe7847..6ad7c6289e6 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java @@ -193,7 +193,8 @@ public void emitCrd(HasMetadata crd, Set dependentClassNames, CRDGenerat try (final OutputStreamWriter writer = new OutputStreamWriter(output.outputFor(outputName), StandardCharsets.UTF_8)) { writer.write("# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n"); String yaml = kubernetesSerialization.asYaml(crd); - writer.write(yaml); + // strip the explicit start added by default + writer.write(yaml.substring(4)); final URI fileURI = output.crdURI(outputName); crdGenerationInfo.add(crdName, version, fileURI, dependentClassNames); } diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml index bf47592f36e..5bc075002c8 100644 --- a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml +++ b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml @@ -15,7 +15,6 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! ---- apiVersion: "apiextensions.k8s.io/v1" kind: "CustomResourceDefinition" metadata: diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml index 43c15f5cf34..39cac710b64 100644 --- a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml +++ b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml @@ -15,7 +15,6 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! ---- apiVersion: "apiextensions.k8s.io/v1" kind: "CustomResourceDefinition" metadata: diff --git a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml index 0ad05e879a0..f083aa0d6f6 100644 --- a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml +++ b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml @@ -15,7 +15,6 @@ # # Generated by Fabric8 CRDGenerator, manual edits might get overwritten! ---- apiVersion: "apiextensions.k8s.io/v1" kind: "CustomResourceDefinition" metadata: From 220c2393f7d229d22970769ac6fbbf7670b9fbc4 Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Thu, 2 May 2024 09:51:10 -0400 Subject: [PATCH 16/16] removing unnecessary override --- .../crdv2/generator/ResolvingContext.java | 20 ++++++------------- .../client/utils/KubernetesSerialization.java | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java index b3a5b6e7850..f4a31756b9b 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java @@ -92,23 +92,15 @@ public JsonObjectFormatVisitor expectObjectFormat(JavaType convertedType) { final Map uriToJacksonSchema; final boolean implicitPreserveUnknownFields; - private static class AccessibleKubernetesSerialization extends KubernetesSerialization { - - @Override - public ObjectMapper getMapper() { - return super.getMapper(); - }; - - } - - private static AccessibleKubernetesSerialization DEFAULT_KUBERNETES_SERIALIZATION; + private static KubernetesSerialization KUBERNETES_SERIALIZATION; + private static ObjectMapper OBJECT_MAPPER; public static ResolvingContext defaultResolvingContext(boolean implicitPreserveUnknownFields) { - if (DEFAULT_KUBERNETES_SERIALIZATION == null) { - DEFAULT_KUBERNETES_SERIALIZATION = new AccessibleKubernetesSerialization(); + if (KUBERNETES_SERIALIZATION == null) { + OBJECT_MAPPER = new ObjectMapper(); + KUBERNETES_SERIALIZATION = new KubernetesSerialization(OBJECT_MAPPER, false); } - return new ResolvingContext(DEFAULT_KUBERNETES_SERIALIZATION.getMapper(), DEFAULT_KUBERNETES_SERIALIZATION, - implicitPreserveUnknownFields); + return new ResolvingContext(OBJECT_MAPPER, KUBERNETES_SERIALIZATION, implicitPreserveUnknownFields); } public ResolvingContext forkContext() { diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java index ea2a04d1829..db04df0247c 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/utils/KubernetesSerialization.java @@ -382,7 +382,7 @@ public UnmatchedFieldTypeModule getUnmatchedFieldTypeModule() { return unmatchedFieldTypeModule; } - protected ObjectMapper getMapper() { + ObjectMapper getMapper() { return mapper; }