From b9891f6eeae90a1556e6df14753fac8c877c044c Mon Sep 17 00:00:00 2001 From: Steve Hawkins Date: Wed, 24 Apr 2024 12:28:29 -0400 Subject: [PATCH] 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