Skip to content

Commit

Permalink
Add default support for the crd-generator (fabric8io#5431)
Browse files Browse the repository at this point in the history
  • Loading branch information
andreaTP authored and euberseder-hubspot committed Dec 21, 2023
1 parent 56e4678 commit e2dbaf7
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public abstract class AbstractJsonSchema<T, B> {
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";
Expand Down Expand Up @@ -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;
Expand All @@ -132,6 +134,7 @@ protected static class SchemaPropsOptions {
final boolean preserveUnknownFields;

SchemaPropsOptions() {
defaultValue = null;
min = null;
max = null;
pattern = null;
Expand All @@ -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;
Expand All @@ -150,6 +154,10 @@ public SchemaPropsOptions(Double min, Double max, String pattern,
this.preserveUnknownFields = preserveUnknownFields;
}

public Optional<String> getDefault() {
return Optional.ofNullable(defaultValue);
}

public Optional<Double> getMin() {
return Optional.ofNullable(min);
}
Expand Down Expand Up @@ -331,6 +339,7 @@ private T internalFromImpl(TypeDef definition, Set<String> visited, List<Interna
}

SchemaPropsOptions options = new SchemaPropsOptions(
facade.defaultValue,
facade.min,
facade.max,
facade.pattern,
Expand Down Expand Up @@ -361,6 +370,7 @@ private static class PropertyOrAccessor {
private final String propertyName;
private final String type;
private String renamedTo;
private String defaultValue;
private Double min;
private Double max;
private String pattern;
Expand Down Expand Up @@ -389,6 +399,9 @@ static PropertyOrAccessor fromMethod(Method method, String propertyName) {
public void process() {
annotations.forEach(a -> {
switch (a.getClassRef().getFullyQualifiedName()) {
case ANNOTATION_DEFAULT:
defaultValue = (String) a.getParameters().get(VALUE);
break;
case ANNOTATION_NULLABLE:
nullable = true;
break;
Expand Down Expand Up @@ -444,6 +457,10 @@ public boolean isNullable() {
return nullable;
}

public Optional<String> getDefault() {
return Optional.ofNullable(defaultValue);
}

public Optional<Double> getMax() {
return Optional.ofNullable(max);
}
Expand Down Expand Up @@ -500,6 +517,7 @@ private static class PropertyFacade {
private final Set<InternalSchemaSwap> matchedSchemaSwaps;
private String renamedTo;
private String description;
private String defaultValue;
private Double min;
private Double max;
private String pattern;
Expand Down Expand Up @@ -531,6 +549,7 @@ public PropertyFacade(Property property, Map<String, Method> potentialAccessors,
if (method != null) {
propertyOrAccessors.add(PropertyOrAccessor.fromMethod(method, name));
}
defaultValue = null;
min = null;
max = null;
pattern = null;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class CRDGenerator {
private CRDOutput<? extends OutputStream> output;
private Map<String, CustomResourceInfo> 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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +26,8 @@

import java.util.List;

import static io.fabric8.crd.generator.CRDGenerator.YAML_MAPPER;

public class JsonSchema extends AbstractJsonSchema<JSONSchemaProps, JSONSchemaPropsBuilder> {

private static final JsonSchema instance = new JsonSchema();
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,6 +26,8 @@

import java.util.List;

import static io.fabric8.crd.generator.CRDGenerator.YAML_MAPPER;

public class JsonSchema extends AbstractJsonSchema<JSONSchemaProps, JSONSchemaPropsBuilder> {

private static final JsonSchema instance = new JsonSchema();
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {

Expand Down Expand Up @@ -72,15 +79,15 @@ void shouldCreateJsonSchemaFromClass() {
}

@Test
void shouldAugmentPropertiesSchemaFromAnnotations() {
void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingException {
TypeDef annotated = Types.typeDefFrom(Annotated.class);
JSONSchemaProps schema = JsonSchema.from(annotated);
assertNotNull(schema);
Map<String, JSONSchemaProps> properties = schema.getProperties();
assertEquals(2, properties.size());
final JSONSchemaProps specSchema = properties.get("spec");
Map<String, JSONSchemaProps> spec = specSchema.getProperties();
assertEquals(11, spec.size());
assertEquals(12, spec.size());

// check descriptions are present
assertTrue(spec.containsKey("from-field"));
Expand All @@ -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<String> required = specSchema.getRequired();
assertEquals(3, required.size());
Expand Down
24 changes: 24 additions & 0 deletions doc/CRD-generator.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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();
}

0 comments on commit e2dbaf7

Please sign in to comment.