diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java index f5f7f98c09f..41232a20ad0 100644 --- a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java @@ -41,6 +41,8 @@ import io.fabric8.crd.generator.annotation.SelectableField; import io.fabric8.crdv2.generator.InternalSchemaSwaps.SwapResult; import io.fabric8.crdv2.generator.ResolvingContext.GeneratorObjectSchema; +import io.fabric8.crdv2.generator.v1.JsonSchema.V1JSONSchemaProps; +import io.fabric8.crdv2.generator.v1.SchemaCustomizer; import io.fabric8.generator.annotation.Default; import io.fabric8.generator.annotation.Max; import io.fabric8.generator.annotation.Min; @@ -54,6 +56,7 @@ 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.apiextensions.v1.JSONSchemaProps; import io.fabric8.kubernetes.api.model.runtime.RawExtension; import io.fabric8.kubernetes.client.utils.Utils; import io.fabric8.kubernetes.model.annotation.LabelSelector; @@ -412,7 +415,7 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa String... ignore) { Set ignores = ignore.length > 0 ? new LinkedHashSet<>(Arrays.asList(ignore)) : Collections.emptySet(); - final T objectSchema = singleProperty("object"); + T objectSchema = singleProperty("object"); schemaSwaps = schemaSwaps.branchAnnotations(); final InternalSchemaSwaps swaps = schemaSwaps; @@ -505,6 +508,28 @@ private T resolveObject(LinkedHashMap visited, InternalSchemaSwa consumeRepeatingAnnotation(rawClass, ValidationRule.class, v -> validationRules.add(from(v))); addToValidationRules(objectSchema, validationRules); + return handleSchemaCustomizer(objectSchema, rawClass); + } + + private T handleSchemaCustomizer(T objectSchema, Class rawClass) { + if (objectSchema instanceof JSONSchemaProps) { + JSONSchemaProps[] props = new JSONSchemaProps[] { (JSONSchemaProps) objectSchema }; + consumeRepeatingAnnotation(rawClass, SchemaCustomizer.class, sc -> { + try { + props[0] = sc.value().getConstructor().newInstance().apply(props[0], sc.input(), + this.resolvingContext.kubernetesSerialization); + } catch (Exception e) { + if (!(e instanceof RuntimeException)) { + e = new RuntimeException(e); + } + throw (RuntimeException) e; + } + }); + if (props[0] != objectSchema) { + // hack to convert back to V1JSONSchemaProps + objectSchema = (T) resolvingContext.kubernetesSerialization.convertValue(props[0], V1JSONSchemaProps.class); + } + } return objectSchema; } diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/SchemaCustomizer.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/SchemaCustomizer.java new file mode 100644 index 00000000000..c87081ddca9 --- /dev/null +++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/SchemaCustomizer.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.generator.v1; + +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface SchemaCustomizer { + + public interface Customizer { + + /** + * Customizes the given {@link JSONSchemaProps} + * + * @param props the {@link JSONSchemaProps} to customize + * @param input the input String from the {@link SchemaCustomizer} annotation + * @param kubernetesSerialization + * @return the customized {@link JSONSchemaProps} + */ + JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization); + + } + + /** + * Replace the incoming schema with the given the input of JSONSchemaProps in yaml or json, + */ + public static class RawCustomizer implements Customizer { + + @Override + public JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization) { + return kubernetesSerialization.unmarshal(input, JSONSchemaProps.class); + } + + } + + /** + * Patch the incoming schema with the given JSON merge patch input. + */ + public static class MergePatchCustomizer implements Customizer { + + @Override + public JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization) { + kubernetesSerialization.mergePatch(props, input); + return props; + } + + } + + Class value(); + + String input() default ""; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/Customized.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/Customized.java new file mode 100644 index 00000000000..cd1d4a0b142 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/Customized.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.customized; + +import io.fabric8.crdv2.generator.v1.SchemaCustomizer; + +@SchemaCustomizer(input = "my description", value = DescriptionCustomizer.class) +public class Customized { + + public int field; + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/DescriptionCustomizer.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/DescriptionCustomizer.java new file mode 100644 index 00000000000..9b2b9357236 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/DescriptionCustomizer.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.example.customized; + +import io.fabric8.crdv2.generator.v1.SchemaCustomizer; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.client.utils.KubernetesSerialization; + +public class DescriptionCustomizer implements SchemaCustomizer.Customizer { + + @Override + public JSONSchemaProps apply(JSONSchemaProps props, String input, KubernetesSerialization kubernetesSerialization) { + props.setDescription(input); + return props; + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/RawCustomized.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/RawCustomized.java new file mode 100644 index 00000000000..204277e7ed5 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/customized/RawCustomized.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.customized; + +import io.fabric8.crdv2.generator.v1.SchemaCustomizer; + +@SchemaCustomizer(input = "properties:\n" + + " prop:\n" + + " type: \"integer\"", value = SchemaCustomizer.RawCustomizer.class) +public class RawCustomized { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SchemaCustomizerTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SchemaCustomizerTest.java new file mode 100644 index 00000000000..14ecbb87c0d --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SchemaCustomizerTest.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.generator.v1; + +import io.fabric8.crdv2.example.customized.Customized; +import io.fabric8.crdv2.example.customized.RawCustomized; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class SchemaCustomizerTest { + + @Test + void applyCustomizer() { + JSONSchemaProps schema = JsonSchema.from(Customized.class); + assertEquals("my description", schema.getDescription()); + } + + @Test + void applyRawCustomizer() { + JSONSchemaProps schema = JsonSchema.from(RawCustomized.class); + assertEquals(1, schema.getProperties().size()); + assertEquals("integer", schema.getProperties().get("prop").getType()); + } + +} diff --git a/doc/CRD-generator.md b/doc/CRD-generator.md index 361ee8f496a..9cfd3c3814e 100644 --- a/doc/CRD-generator.md +++ b/doc/CRD-generator.md @@ -873,6 +873,11 @@ spec: # [...] ``` +### Schema Customization + +In some instances the built-in set of annotations and logic may not produce the desired CRD output. There is a mechanism +included in the crd-generator-api-v2 module for this. See the `io.fabric8.crdv2.generator.v1.SchemaCustomizer` annotation +for directly manipulating the JSONSchemaProps of the annotated resource. This annotation is applied last, after all of the other annotations are processed. ## Features cheatsheet 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 2c7238bb76c..d92ef3fb614 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 @@ -26,6 +26,7 @@ import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.KeyDeserializer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; import com.fasterxml.jackson.databind.SerializationConfig; import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.cfg.HandlerInstantiator; @@ -429,4 +430,13 @@ public String convertToJson(String input) { } } + public void mergePatch(Object updatable, String patch) { + ObjectReader reader = mapper.readerForUpdating(updatable); + try { + reader.readValue(patch); + } catch (JsonProcessingException e) { + throw KubernetesClientException.launderThrowable(e); + } + } + }