From 52f88c5cab94fa5b79b5100c19633b3b92a51af3 Mon Sep 17 00:00:00 2001 From: wineway Date: Wed, 7 Sep 2022 11:40:30 +0800 Subject: [PATCH 1/7] add annotation @PreserveUnknownFields for field Signed-off-by: wineway --- .../crd/generator/AbstractJsonSchema.java | 27 +++++++++++++++++-- .../annotation/PreserveUnknownFields.java | 26 ++++++++++++++++++ .../fabric8/crd/generator/v1/JsonSchema.java | 4 +++ .../crd/generator/v1beta1/JsonSchema.java | 4 +++ .../example/extraction/ExtractionSpec.java | 2 ++ .../crd/generator/v1/JsonSchemaTest.java | 1 + 6 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java index ca82bf4ee71..028cb24e75d 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java @@ -91,6 +91,7 @@ public abstract class AbstractJsonSchema { public static final String ANNOTATION_REQUIRED = "io.fabric8.generator.annotation.Required"; public static final String ANNOTATION_NOT_NULL = "javax.validation.constraints.NotNull"; public static final String ANNOTATION_SCHEMA_FROM = "io.fabric8.crd.generator.annotation.SchemaFrom"; + public static final String ANNOTATION_PERSERVE_UNKNOWN_FIELDS = "io.fabric8.crd.generator.annotation.PreserveUnknownFields"; public static final String ANNOTATION_SCHEMA_SWAP = "io.fabric8.crd.generator.annotation.SchemaSwap"; public static final String JSON_NODE_TYPE = "com.fasterxml.jackson.databind.JsonNode"; @@ -128,21 +129,25 @@ protected static class SchemaPropsOptions { final boolean nullable; final boolean required; + final boolean preserveUnknownFields; + SchemaPropsOptions() { min = Optional.empty(); max = Optional.empty(); pattern = Optional.empty(); nullable = false; required = false; + preserveUnknownFields = false; } public SchemaPropsOptions(Optional min, Optional max, Optional pattern, - boolean nullable, boolean required) { + boolean nullable, boolean required, boolean preserveUnknownFields) { this.min = min; this.max = max; this.pattern = pattern; this.nullable = nullable; this.required = required; + this.preserveUnknownFields = preserveUnknownFields; } public Optional getMin() { @@ -164,6 +169,10 @@ public boolean isNullable() { public boolean getRequired() { return nullable; } + + public boolean isPreserveUnknownFields() { + return preserveUnknownFields; + } } /** @@ -326,7 +335,8 @@ private T internalFromImpl(TypeDef definition, Set visited, List barProps = bar.getProperties(); assertNotNull(barProps); + assertTrue(bar.getXKubernetesPreserveUnknownFields()); // you can change everything assertEquals("integer", barProps.get("BAZ").getType()); From b4de5ff9d8db77cdb252a033c8cb2c6fe3bcebda Mon Sep 17 00:00:00 2001 From: wineway Date: Wed, 7 Sep 2022 18:02:17 +0800 Subject: [PATCH 2/7] add changelog & fixed copyright Signed-off-by: wineway --- .../annotation/PreserveUnknownFields.java | 2 +- doc/CRD-generator.md | 22 +++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java index 59ee2ddfb69..dd784b836b9 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2022 Red Hat, Inc. + * Copyright (C) 2015 Red Hat, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/doc/CRD-generator.md b/doc/CRD-generator.md index 4275c1ae3df..89874b36862 100644 --- a/doc/CRD-generator.md +++ b/doc/CRD-generator.md @@ -357,6 +357,28 @@ Corresponding `x-kubernetes-preserve-unknown-fields: true` will be generated in x-kubernetes-preserve-unknown-fields: true ``` +You can also annotation a field with @PreserveUnknownFields: + +```java +interface ExampleInterface {} + +public class ExampleSpec { + @PreserveUnknownFields + ExampleInterface someValue; +} +``` + +will be generated as: + +```yaml + spec: + properties: + someValue: + type: object + x-kubernetes-preserve-unknown-fields: true + type: object +``` + ## Features cheatsheet | Annotation | Description | From 8433bcc18356a20d3605056f31c190bf591f3764 Mon Sep 17 00:00:00 2001 From: wineway Date: Wed, 14 Sep 2022 16:48:49 +0800 Subject: [PATCH 3/7] add more explain for changelog & doc Signed-off-by: wineway --- .../annotation/PreserveUnknownFields.java | 2 +- doc/CRD-generator.md | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java index dd784b836b9..94b84618f07 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/annotation/PreserveUnknownFields.java @@ -18,7 +18,7 @@ import java.lang.annotation.*; /* - * Used to tweak the behavior of the crd-generator + * Used to emit 'x-kubernetes-preserve-unknown-fields' */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) diff --git a/doc/CRD-generator.md b/doc/CRD-generator.md index 89874b36862..611a7a20fc6 100644 --- a/doc/CRD-generator.md +++ b/doc/CRD-generator.md @@ -381,19 +381,20 @@ will be generated as: ## Features cheatsheet -| Annotation | Description | -|-----------------------------------------------------------|---------------------------------------------------------------------------------------| -| `com.fasterxml.jackson.annotation.JsonProperty` | The field is named after the provided value instead of looking up the java field name | -| `com.fasterxml.jackson.annotation.JsonPropertyDescription`| The provided text is be embedded in the `description` of the field | -| `com.fasterxml.jackson.annotation.JsonIgnore` | The field is ignored | -| `com.fasterxml.jackson.annotation.JsonAnyGetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | -| `com.fasterxml.jackson.annotation.JsonAnySetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | -| `io.fabric8.generator.annotation.Min` | The field defines a validation `min` | -| `io.fabric8.generator.annotation.Max` | The field defines a validation `max` | -| `io.fabric8.generator.annotation.Pattern` | The field defines a validation `pattern` | -| `io.fabric8.generator.annotation.Nullable` | The field is marked as `nullable` | -| `io.fabric8.generator.annotation.Required` | The field is marked as `required` | -| `io.fabric8.crd.generator.annotation.SchemaFrom` | The field type for the generation is the one coming from the annotation | -| `io.fabric8.crd.generator.annotation.SchemaSwap` | Same as SchemaFrom, but can be applied at any point in the class hierarchy | +| Annotation | Description | +|--------------------------------------------------------------|---------------------------------------------------------------------------------------| +| `com.fasterxml.jackson.annotation.JsonProperty` | The field is named after the provided value instead of looking up the java field name | +| `com.fasterxml.jackson.annotation.JsonPropertyDescription` | The provided text is be embedded in the `description` of the field | +| `com.fasterxml.jackson.annotation.JsonIgnore` | The field is ignored | +| `io.fabric8.crd.generator.annotation.PreserveUnknownFields` | The field have `x-kubernetes-preserve-unknown-fields: true` defined | +| `com.fasterxml.jackson.annotation.JsonAnyGetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | +| `com.fasterxml.jackson.annotation.JsonAnySetter` | The corresponding object have `x-kubernetes-preserve-unknown-fields: true` defined | +| `io.fabric8.generator.annotation.Min` | The field defines a validation `min` | +| `io.fabric8.generator.annotation.Max` | The field defines a validation `max` | +| `io.fabric8.generator.annotation.Pattern` | The field defines a validation `pattern` | +| `io.fabric8.generator.annotation.Nullable` | The field is marked as `nullable` | +| `io.fabric8.generator.annotation.Required` | The field is marked as `required` | +| `io.fabric8.crd.generator.annotation.SchemaFrom` | The field type for the generation is the one coming from the annotation | +| `io.fabric8.crd.generator.annotation.SchemaSwap` | Same as SchemaFrom, but can be applied at any point in the class hierarchy | A field of type `com.fasterxml.jackson.databind.JsonNode` is encoded as an empty object with `x-kubernetes-preserve-unknown-fields: true` defined. From d4dabe10efb26a2d87752209ecb3ff9b01cf2b61 Mon Sep 17 00:00:00 2001 From: wineway Date: Wed, 14 Sep 2022 17:12:41 +0800 Subject: [PATCH 4/7] fixed typo --- doc/CRD-generator.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/CRD-generator.md b/doc/CRD-generator.md index 611a7a20fc6..fb822d3f428 100644 --- a/doc/CRD-generator.md +++ b/doc/CRD-generator.md @@ -357,7 +357,7 @@ Corresponding `x-kubernetes-preserve-unknown-fields: true` will be generated in x-kubernetes-preserve-unknown-fields: true ``` -You can also annotation a field with @PreserveUnknownFields: +You can also annotate a field with `io.fabric8.crd.generator.annotation.PreserveUnknownFields`: ```java interface ExampleInterface {} From 56e4678a5b619932617981eef8c09ddf42ba75b4 Mon Sep 17 00:00:00 2001 From: Marc Nuri Date: Fri, 16 Sep 2022 09:49:59 +0200 Subject: [PATCH 5/7] refactor: JsonSchema remove preserveSelfUnknownFields Signed-off-by: Marc Nuri --- .../crd/generator/AbstractJsonSchema.java | 89 +++++++------------ 1 file changed, 31 insertions(+), 58 deletions(-) diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java index 028cb24e75d..21b550555a7 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java @@ -123,24 +123,24 @@ public static String getSchemaTypeFor(TypeRef typeRef) { } protected static class SchemaPropsOptions { - final Optional min; - final Optional max; - final Optional pattern; + final Double min; + final Double max; + final String pattern; final boolean nullable; final boolean required; final boolean preserveUnknownFields; SchemaPropsOptions() { - min = Optional.empty(); - max = Optional.empty(); - pattern = Optional.empty(); + min = null; + max = null; + pattern = null; nullable = false; required = false; preserveUnknownFields = false; } - public SchemaPropsOptions(Optional min, Optional max, Optional pattern, + public SchemaPropsOptions(Double min, Double max, String pattern, boolean nullable, boolean required, boolean preserveUnknownFields) { this.min = min; this.max = max; @@ -151,15 +151,15 @@ public SchemaPropsOptions(Optional min, Optional max, Optional getMin() { - return min; + return Optional.ofNullable(min); } public Optional getMax() { - return max; + return Optional.ofNullable(max); } public Optional getPattern() { - return pattern; + return Optional.ofNullable(pattern); } public boolean isNullable() { @@ -336,7 +336,7 @@ private T internalFromImpl(TypeDef definition, Set visited, List min; - private Optional max; - private Optional pattern; + private Double min; + private Double max; + private String pattern; private boolean nullable; private boolean required; private boolean ignored; private boolean preserveUnknownFields; - private boolean preserveSelfUnknownFields; private String description; private TypeRef schemaFrom; @@ -377,10 +376,6 @@ private PropertyOrAccessor(Collection annotations, String name, S this.name = name; this.propertyName = propertyName; type = isMethod ? "accessor" : "field"; - - min = Optional.empty(); - max = Optional.empty(); - pattern = Optional.empty(); } static PropertyOrAccessor fromProperty(Property property) { @@ -398,13 +393,13 @@ public void process() { nullable = true; break; case ANNOTATION_MAX: - max = Optional.of((Double) a.getParameters().get(VALUE)); + max = (Double) a.getParameters().get(VALUE); break; case ANNOTATION_MIN: - min = Optional.of((Double) a.getParameters().get(VALUE)); + min = (Double) a.getParameters().get(VALUE); break; case ANNOTATION_PATTERN: - pattern = Optional.of((String) a.getParameters().get(VALUE)); + pattern = (String) a.getParameters().get(VALUE); break; case ANNOTATION_NOT_NULL: LOGGER.warn("Annotation: {} on property: {} is deprecated. Please use: {} instead", ANNOTATION_NOT_NULL, name, @@ -431,10 +426,8 @@ public void process() { break; case ANNOTATION_JSON_ANY_GETTER: case ANNOTATION_JSON_ANY_SETTER: - preserveUnknownFields = true; - break; case ANNOTATION_PERSERVE_UNKNOWN_FIELDS: - preserveSelfUnknownFields = true; + preserveUnknownFields = true; break; case ANNOTATION_SCHEMA_FROM: schemaFrom = extractClassRef(a.getParameters().get("type")); @@ -452,15 +445,15 @@ public boolean isNullable() { } public Optional getMax() { - return max; + return Optional.ofNullable(max); } public Optional getMin() { - return min; + return Optional.ofNullable(min); } public Optional getPattern() { - return pattern; + return Optional.ofNullable(pattern); } public boolean isRequired() { @@ -475,10 +468,6 @@ public boolean isPreserveUnknownFields() { return preserveUnknownFields; } - public boolean isPreserveSelfUnknownFields() { - return preserveSelfUnknownFields; - } - public String getDescription() { return description; } @@ -511,14 +500,13 @@ private static class PropertyFacade { private final Set matchedSchemaSwaps; private String renamedTo; private String description; - private Optional min; - private Optional max; - private Optional pattern; + private Double min; + private Double max; + private String pattern; private boolean nullable; private boolean required; private boolean ignored; private boolean preserveUnknownFields; - private boolean preserveSelfUnknownFields; private final Property original; private String nameContributedBy; private String descriptionContributedBy; @@ -543,9 +531,9 @@ public PropertyFacade(Property property, Map potentialAccessors, if (method != null) { propertyOrAccessors.add(PropertyOrAccessor.fromMethod(method, name)); } - min = Optional.empty(); - max = Optional.empty(); - pattern = Optional.empty(); + min = null; + max = null; + pattern = null; } public Property process() { @@ -581,18 +569,9 @@ public Property process() { LOGGER.debug("Description for property {} has already been contributed by: {}", name, descriptionContributedBy); } } - - if (p.getMin().isPresent()) { - min = p.getMin(); - } - - if (p.getMax().isPresent()) { - max = p.getMax(); - } - - if (p.getPattern().isPresent()) { - pattern = p.getPattern(); - } + min = p.getMin().orElse(min); + max = p.getMax().orElse(max); + pattern = p.getPattern().orElse(pattern); if (p.isNullable()) { nullable = true; @@ -604,13 +583,7 @@ public Property process() { ignored = true; } - if (p.isPreserveUnknownFields()) { - preserveUnknownFields = true; - } - - if (p.isPreserveSelfUnknownFields()) { - preserveSelfUnknownFields = true; - } + preserveUnknownFields = p.isPreserveUnknownFields() || preserveUnknownFields; if (p.contributeSchemaFrom()) { schemaFrom = p.getSchemaFrom(); From e2dbaf7f114181e4069d2a1c4f5386f983acfab3 Mon Sep 17 00:00:00 2001 From: Andrea Peruffo Date: Thu, 28 Sep 2023 08:32:58 +0100 Subject: [PATCH 6/7] Add default support for the crd-generator (#5431) --- .../crd/generator/AbstractJsonSchema.java | 22 ++++++++++++- .../fabric8/crd/generator/CRDGenerator.java | 2 +- .../fabric8/crd/generator/v1/JsonSchema.java | 10 ++++++ .../crd/generator/v1beta1/JsonSchema.java | 10 ++++++ .../crd/example/annotated/AnnotatedSpec.java | 9 +++++- .../crd/generator/v1/JsonSchemaTest.java | 24 ++++++++++++-- doc/CRD-generator.md | 24 ++++++++++++++ .../fabric8/generator/annotation/Default.java | 31 +++++++++++++++++++ 8 files changed, 126 insertions(+), 6 deletions(-) create mode 100644 generator-annotations/src/main/java/io/fabric8/generator/annotation/Default.java diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java index 21b550555a7..e2574b918b0 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java @@ -84,6 +84,7 @@ public abstract class AbstractJsonSchema { public static final String ANNOTATION_JSON_IGNORE = "com.fasterxml.jackson.annotation.JsonIgnore"; public static final String ANNOTATION_JSON_ANY_GETTER = "com.fasterxml.jackson.annotation.JsonAnyGetter"; public static final String ANNOTATION_JSON_ANY_SETTER = "com.fasterxml.jackson.annotation.JsonAnySetter"; + public static final String ANNOTATION_DEFAULT = "io.fabric8.generator.annotation.Default"; public static final String ANNOTATION_MIN = "io.fabric8.generator.annotation.Min"; public static final String ANNOTATION_MAX = "io.fabric8.generator.annotation.Max"; public static final String ANNOTATION_PATTERN = "io.fabric8.generator.annotation.Pattern"; @@ -123,6 +124,7 @@ public static String getSchemaTypeFor(TypeRef typeRef) { } protected static class SchemaPropsOptions { + final String defaultValue; final Double min; final Double max; final String pattern; @@ -132,6 +134,7 @@ protected static class SchemaPropsOptions { final boolean preserveUnknownFields; SchemaPropsOptions() { + defaultValue = null; min = null; max = null; pattern = null; @@ -140,8 +143,9 @@ protected static class SchemaPropsOptions { preserveUnknownFields = false; } - public SchemaPropsOptions(Double min, Double max, String pattern, + public SchemaPropsOptions(String defaultValue, Double min, Double max, String pattern, boolean nullable, boolean required, boolean preserveUnknownFields) { + this.defaultValue = defaultValue; this.min = min; this.max = max; this.pattern = pattern; @@ -150,6 +154,10 @@ public SchemaPropsOptions(Double min, Double max, String pattern, this.preserveUnknownFields = preserveUnknownFields; } + public Optional getDefault() { + return Optional.ofNullable(defaultValue); + } + public Optional getMin() { return Optional.ofNullable(min); } @@ -331,6 +339,7 @@ private T internalFromImpl(TypeDef definition, Set visited, List { switch (a.getClassRef().getFullyQualifiedName()) { + case ANNOTATION_DEFAULT: + defaultValue = (String) a.getParameters().get(VALUE); + break; case ANNOTATION_NULLABLE: nullable = true; break; @@ -444,6 +457,10 @@ public boolean isNullable() { return nullable; } + public Optional getDefault() { + return Optional.ofNullable(defaultValue); + } + public Optional getMax() { return Optional.ofNullable(max); } @@ -500,6 +517,7 @@ private static class PropertyFacade { private final Set matchedSchemaSwaps; private String renamedTo; private String description; + private String defaultValue; private Double min; private Double max; private String pattern; @@ -531,6 +549,7 @@ public PropertyFacade(Property property, Map potentialAccessors, if (method != null) { propertyOrAccessors.add(PropertyOrAccessor.fromMethod(method, name)); } + defaultValue = null; min = null; max = null; pattern = null; @@ -569,6 +588,7 @@ public Property process() { LOGGER.debug("Description for property {} has already been contributed by: {}", name, descriptionContributedBy); } } + defaultValue = p.getDefault().orElse(null); min = p.getMin().orElse(min); max = p.getMax().orElse(max); pattern = p.getPattern().orElse(pattern); diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java index 77b85f881e7..bbc526b2293 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java @@ -41,7 +41,7 @@ public class CRDGenerator { private CRDOutput output; private Map infos; - private static final ObjectMapper YAML_MAPPER = new ObjectMapper( + public static final ObjectMapper YAML_MAPPER = new ObjectMapper( new YAMLFactory() .enable(Feature.MINIMIZE_QUOTES) .enable(Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS) diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/JsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/JsonSchema.java index d0aea6a64e0..7e7e7304c2d 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/JsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/JsonSchema.java @@ -15,6 +15,7 @@ */ package io.fabric8.crd.generator.v1; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import io.fabric8.crd.generator.AbstractJsonSchema; import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; @@ -25,6 +26,8 @@ import java.util.List; +import static io.fabric8.crd.generator.CRDGenerator.YAML_MAPPER; + public class JsonSchema extends AbstractJsonSchema { private static final JsonSchema instance = new JsonSchema(); @@ -58,6 +61,13 @@ public JSONSchemaPropsBuilder newBuilder() { public void addProperty(Property property, JSONSchemaPropsBuilder builder, JSONSchemaProps schema, SchemaPropsOptions options) { if (schema != null) { + options.getDefault().ifPresent(s -> { + try { + schema.setDefault(YAML_MAPPER.readTree(s)); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Cannot parse default value: '" + s + "' as valid YAML."); + } + }); options.getMin().ifPresent(schema::setMinimum); options.getMax().ifPresent(schema::setMaximum); options.getPattern().ifPresent(schema::setPattern); diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/JsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/JsonSchema.java index 65c04b20155..92e977353d1 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/JsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/JsonSchema.java @@ -15,6 +15,7 @@ */ package io.fabric8.crd.generator.v1beta1; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import io.fabric8.crd.generator.AbstractJsonSchema; import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.JSONSchemaProps; @@ -25,6 +26,8 @@ import java.util.List; +import static io.fabric8.crd.generator.CRDGenerator.YAML_MAPPER; + public class JsonSchema extends AbstractJsonSchema { private static final JsonSchema instance = new JsonSchema(); @@ -59,6 +62,13 @@ public JSONSchemaPropsBuilder newBuilder() { public void addProperty(Property property, JSONSchemaPropsBuilder builder, JSONSchemaProps schema, SchemaPropsOptions options) { if (schema != null) { + options.getDefault().ifPresent(s -> { + try { + schema.setDefault(YAML_MAPPER.readTree(s)); + } catch (JsonProcessingException e) { + throw new IllegalArgumentException("Cannot parse default value: '" + s + "' as valid YAML."); + } + }); options.getMin().ifPresent(schema::setMinimum); options.getMax().ifPresent(schema::setMaximum); options.getPattern().ifPresent(schema::setPattern); diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/example/annotated/AnnotatedSpec.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/annotated/AnnotatedSpec.java index f09a1348a82..ba5715ecddf 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/example/annotated/AnnotatedSpec.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/example/annotated/AnnotatedSpec.java @@ -18,6 +18,7 @@ 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; @@ -37,7 +38,8 @@ public class AnnotatedSpec { private int max; private String singleDigit; private String nullable; - @NotNull + private String defaultValue; + @Required private boolean emptySetter; @Required private boolean emptySetter2; @@ -86,6 +88,11 @@ public String getNullable() { return null; } + @Default("my-value") + public String getDefaultValue() { + return "foo"; + } + @JsonProperty public void setEmptySetter(boolean emptySetter) { this.emptySetter = emptySetter; diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/generator/v1/JsonSchemaTest.java b/crd-generator/api/src/test/java/io/fabric8/crd/generator/v1/JsonSchemaTest.java index a44ae180853..57f7c071990 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/generator/v1/JsonSchemaTest.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/generator/v1/JsonSchemaTest.java @@ -15,6 +15,7 @@ */ package io.fabric8.crd.generator.v1; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import io.fabric8.crd.example.annotated.Annotated; import io.fabric8.crd.example.basic.Basic; @@ -32,7 +33,13 @@ import java.util.Map; import java.util.stream.Collectors; -import static org.junit.jupiter.api.Assertions.*; +import static io.fabric8.crd.generator.CRDGenerator.YAML_MAPPER; +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 { @@ -72,7 +79,7 @@ void shouldCreateJsonSchemaFromClass() { } @Test - void shouldAugmentPropertiesSchemaFromAnnotations() { + void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingException { TypeDef annotated = Types.typeDefFrom(Annotated.class); JSONSchemaProps schema = JsonSchema.from(annotated); assertNotNull(schema); @@ -80,7 +87,7 @@ void shouldAugmentPropertiesSchemaFromAnnotations() { assertEquals(2, properties.size()); final JSONSchemaProps specSchema = properties.get("spec"); Map spec = specSchema.getProperties(); - assertEquals(11, spec.size()); + assertEquals(12, spec.size()); // check descriptions are present assertTrue(spec.containsKey("from-field")); @@ -98,29 +105,40 @@ void shouldAugmentPropertiesSchemaFromAnnotations() { assertTrue(spec.containsKey("anEnum")); final JSONSchemaProps min = spec.get("min"); + assertNull(min.getDefault()); assertEquals(-5.0, min.getMinimum()); assertNull(min.getMaximum()); assertNull(min.getPattern()); assertNull(min.getNullable()); final JSONSchemaProps max = spec.get("max"); + assertNull(max.getDefault()); assertEquals(5.0, max.getMaximum()); assertNull(max.getMinimum()); assertNull(max.getPattern()); assertNull(max.getNullable()); final JSONSchemaProps pattern = spec.get("singleDigit"); + assertNull(pattern.getDefault()); assertEquals("\\b[1-9]\\b", pattern.getPattern()); assertNull(pattern.getMinimum()); assertNull(pattern.getMaximum()); assertNull(pattern.getNullable()); final JSONSchemaProps nullable = spec.get("nullable"); + assertNull(nullable.getDefault()); assertTrue(nullable.getNullable()); assertNull(nullable.getMinimum()); assertNull(nullable.getMaximum()); assertNull(nullable.getPattern()); + final JSONSchemaProps defaultValue = spec.get("defaultValue"); + assertEquals("my-value", YAML_MAPPER.writeValueAsString(defaultValue.getDefault()).trim()); + assertNull(defaultValue.getNullable()); + assertNull(defaultValue.getMinimum()); + assertNull(defaultValue.getMaximum()); + assertNull(defaultValue.getPattern()); + // check required list, should register properties with their modified name if needed final List required = specSchema.getRequired(); assertEquals(3, required.size()); diff --git a/doc/CRD-generator.md b/doc/CRD-generator.md index fb822d3f428..eeb17a90cb3 100644 --- a/doc/CRD-generator.md +++ b/doc/CRD-generator.md @@ -156,6 +156,30 @@ The field will be skipped in the generated CRD and will not appear in the schema If a field or one of its accessors is annotated with `io.fabric8.generator.annotation.Min` +```java +public class ExampleSpec { + @Default("foo") + String someValue; +} +``` + +The field will have the `default` property in the generated CRD, such as: + +```yaml + spec: + properties: + someValue: + default: foo + type: string + required: + - someValue + type: object +``` + +### io.fabric8.generator.annotation.Min + +If a field or one of its accessors is annotated with `io.fabric8.generator.annotation.Min` + ```java public class ExampleSpec { @Min(-1) diff --git a/generator-annotations/src/main/java/io/fabric8/generator/annotation/Default.java b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Default.java new file mode 100644 index 00000000000..a35b8f7e269 --- /dev/null +++ b/generator-annotations/src/main/java/io/fabric8/generator/annotation/Default.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.generator.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/* + * Java representation of the `default` field of JSONSchemaProps + * https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.25/#jsonschemaprops-v1-apiextensions-k8s-io + */ +@Target({ ElementType.ANNOTATION_TYPE, ElementType.FIELD, ElementType.METHOD }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Default { + String value(); +} From 9c6257c089a29ad84dc848f0c7941dc5729cfd7a Mon Sep 17 00:00:00 2001 From: Ethan Uberseder Date: Thu, 21 Dec 2023 18:16:18 -0500 Subject: [PATCH 7/7] add bug fix from https://github.com/fabric8io/kubernetes-client/pull/5503 without testing deps --- .../main/java/io/fabric8/crd/generator/AbstractJsonSchema.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java index e2574b918b0..7128f3d56e2 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java @@ -588,7 +588,7 @@ public Property process() { LOGGER.debug("Description for property {} has already been contributed by: {}", name, descriptionContributedBy); } } - defaultValue = p.getDefault().orElse(null); + defaultValue = p.getDefault().orElse(defaultValue); min = p.getMin().orElse(min); max = p.getMax().orElse(max); pattern = p.getPattern().orElse(pattern);