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..317c6b7baf2
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractCustomResourceHandler.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;
+
+import io.fabric8.crd.generator.annotation.PrinterColumn;
+import io.fabric8.crdv2.generator.AbstractJsonSchema.AnnotationMetadata;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.client.utils.Utils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.stream.Stream;
+
+/**
+ * This class encapsulates the common behavior between different CRD generation logic. The
+ * intent is that each CRD spec version is implemented as a sub-class of this one.
+ */
+public abstract class AbstractCustomResourceHandler {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AbstractJsonSchema.class);
+
+ public abstract void handle(CustomResourceInfo config, ResolvingContext resolvingContext);
+
+ public interface PrinterColumnHandler {
+ void addPrinterColumn(String path, String column, String format,
+ int priority, String type, String description);
+ }
+
+ protected void handlePrinterColumns(AbstractJsonSchema, ?> resolver, PrinterColumnHandler handler) {
+ TreeMap sortedCols = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+ sortedCols.putAll(resolver.getAllPaths(PrinterColumn.class));
+ sortedCols.forEach((path, property) -> {
+ PrinterColumn printerColumn = ((PrinterColumn) property.annotation);
+ String column = printerColumn.name();
+ if (Utils.isNullOrEmpty(column)) {
+ column = path.substring(path.lastIndexOf(".") + 1).toUpperCase();
+ }
+ String format = printerColumn.format();
+ format = Utils.isNotNullOrEmpty(format) ? format : null;
+ int priority = printerColumn.priority();
+ String type = property.schema.getType();
+ if ("object".equals(type) || "array".equals(type)) {
+ LOGGER.warn("Printer column '{}' has a type '{}' that is not allowed, will use string intead", column, type);
+ type = "string";
+ } else if ("string".equals(type) && "date".equals(property.schema.getFormat())) {
+ type = "date";
+ }
+
+ // TODO: add description to the annotation? The previous logic considered the comments, which are not available here
+ String description = property.schema.getDescription();
+ description = Utils.isNotNullOrEmpty(description) ? description : null;
+
+ handler.addPrinterColumn(path, column, format, priority, type, description);
+ });
+ }
+
+ public abstract Stream>> finish();
+
+}
diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java
new file mode 100644
index 00000000000..256e5dcc9ed
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/AbstractJsonSchema.java
@@ -0,0 +1,559 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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 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;
+ private Set dependentClasses = new HashSet<>();
+
+ public static class AnnotationMetadata {
+ public final Annotation annotation;
+ public final KubernetesJSONSchemaProps schema;
+
+ public AnnotationMetadata(Annotation annotation, KubernetesJSONSchemaProps schema) {
+ this.annotation = annotation;
+ this.schema = schema;
+ }
+ }
+
+ 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 Set getDependentClasses() {
+ return dependentClasses;
+ }
+
+ public Optional getSinglePath(Class extends Annotation> 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 = resolvingContext.toJsonSchema(definition);
+ if (schema instanceof GeneratorObjectSchema) {
+ return resolveObject(new LinkedHashMap<>(), schemaSwaps, schema, "kind", "apiVersion", "metadata");
+ }
+ return resolveProperty(new LinkedHashMap<>(), schemaSwaps, null,
+ resolvingContext.objectMapper.getSerializationConfig().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);
+ preserveUnknownFields = beanProperty.getAnnotation(PreserveUnknownFields.class) != 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();
+ 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);
+ }
+
+ public void updateSchema(T schema) {
+ schema.setDescription(description);
+
+ if (defaultValue != null) {
+ try {
+ schema.setDefault(resolvingContext.kubernetesSerialization.convertValue(defaultValue, JsonNode.class));
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Cannot parse default value: '" + defaultValue + "' as valid YAML.", e);
+ }
+ }
+ if (nullable) {
+ 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.objectMapper.getSerializationConfig().getAnnotationIntrospector();
+ BeanDescription bd = resolvingContext.objectMapper.getSerializationConfig().introspect(gos.javaType);
+ boolean preserveUnknownFields = false;
+ if (resolvingContext.implicitPreserveUnknownFields) {
+ preserveUnknownFields = bd.findAnyGetter() != null || bd.findAnySetterAccessor() != null;
+ }
+
+ Class> rawClass = gos.javaType.getRawClass();
+ collectDependentClasses(rawClass);
+
+ consumeRepeatingAnnotation(rawClass, SchemaSwap.class, ss -> {
+ swaps.registerSwap(rawClass,
+ 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(rawClass, 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 = resolvingContext.toJsonSchema(propertyMetadata.schemaFrom);
+ type = resolvingContext.objectMapper.getSerializationConfig().constructType(propertyMetadata.schemaFrom);
+ }
+
+ T schema = resolveProperty(visited, schemaSwaps, name, type, propertySchema);
+
+ propertyMetadata.updateSchema(schema);
+
+ if (!swapResult.onGoing) {
+ for (Entry, LinkedHashMap> entry : pathMetadata.entrySet()) {
+ ofNullable(beanProperty.getAnnotation(entry.getKey())).ifPresent(
+ ann -> entry.getValue().put(toFQN(savedVisited, name),
+ new AnnotationMetadata(ann, schema)));
+ }
+ }
+
+ visited = savedVisited;
+
+ addProperty(name, objectSchema, schema);
+ }
+
+ swaps.throwIfUnmatchedSwaps();
+
+ objectSchema.setRequired(required);
+ if (preserveUnknownFields) {
+ objectSchema.setXKubernetesPreserveUnknownFields(true);
+ }
+ List validationRules = new ArrayList<>();
+ consumeRepeatingAnnotation(rawClass, ValidationRule.class,
+ v -> validationRules.add(from(v)));
+ addToValidationRules(objectSchema, validationRules);
+ return objectSchema;
+ }
+
+ private void collectDependentClasses(Class> rawClass) {
+ if (rawClass != null && !rawClass.getName().startsWith("java.") && dependentClasses.add(rawClass.getName())) {
+ Stream.of(rawClass.getInterfaces()).forEach(this::collectDependentClasses);
+ collectDependentClasses(rawClass.getSuperclass());
+ }
+ }
+
+ static String toFQN(LinkedHashMap visited, String name) {
+ if (visited.isEmpty()) {
+ return "." + name;
+ }
+ 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() ? findIgnoredEnumConstants(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.uriToJacksonSchema.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 findIgnoredEnumConstants(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 - this guards against some using both JsonIgnore and JsonProperty
+ try {
+ Object value = field.get(null);
+ toIgnore.add(resolvingContext.objectMapper.convertValue(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;
+ }
+
+ 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..e216af4aebb
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerationInfo.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 java.io.File;
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+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, Set dependentClassNames) {
+ crdNameToVersionToCRDInfoMap.computeIfAbsent(crdName, k -> new HashMap<>())
+ .put(version, new CRDInfo(crdName, version, new File(fileURI).getAbsolutePath(), dependentClassNames));
+ }
+
+ 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..6ad7c6289e6
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDGenerator.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.ObjectMapper;
+import io.fabric8.crdv2.generator.v1.CustomResourceHandler;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.client.utils.ApiVersionUtil;
+import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+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.concurrent.ForkJoinPool;
+import java.util.concurrent.ForkJoinTask;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class CRDGenerator {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(CRDGenerator.class);
+ private final Map handlers = new HashMap<>(2);
+ private CRDOutput extends OutputStream> output;
+ private boolean parallel;
+ private boolean implicitPreserveUnknownFields;
+ private ObjectMapper objectMapper;
+ private KubernetesSerialization kubernetesSerialization;
+ private Map infos;
+
+ public CRDGenerator inOutputDir(File outputDir) {
+ output = new DirCRDOutput(outputDir);
+ return this;
+ }
+
+ public CRDGenerator withOutput(CRDOutput extends OutputStream> output) {
+ this.output = output;
+ return this;
+ }
+
+ public CRDGenerator withImplicitPreserveUnknownFields(boolean implicitPreserveUnknownFields) {
+ this.implicitPreserveUnknownFields = implicitPreserveUnknownFields;
+ return this;
+ }
+
+ public CRDGenerator withParallelGenerationEnabled(boolean parallel) {
+ this.parallel = parallel;
+ return this;
+ }
+
+ public CRDGenerator withObjectMapper(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization) {
+ this.objectMapper = mapper;
+ this.kubernetesSerialization = kubernetesSerialization;
+ 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());
+ 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 extends HasMetadata>... 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);
+ }
+
+ final ResolvingContext context;
+ if (this.objectMapper == null) {
+ context = ResolvingContext.defaultResolvingContext(implicitPreserveUnknownFields);
+ this.kubernetesSerialization = context.kubernetesSerialization;
+ } else {
+ context = new ResolvingContext(this.objectMapper, this.kubernetesSerialization, implicitPreserveUnknownFields);
+ }
+
+ 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"));
+ }
+
+ if (parallel) {
+ handlers.values().stream().map(h -> ForkJoinPool.commonPool().submit(() -> h.handle(info, context.forkContext())))
+ .collect(Collectors.toList()).stream().forEach(ForkJoinTask::join);
+ } else {
+ handlers.values().stream().forEach(h -> h.handle(info, context.forkContext()));
+ }
+ }
+ }
+
+ final CRDGenerationInfo crdGenerationInfo = new CRDGenerationInfo();
+ handlers.values().stream().flatMap(AbstractCustomResourceHandler::finish)
+ .forEach(crd -> emitCrd(crd.getKey(), crd.getValue(), crdGenerationInfo));
+ return crdGenerationInfo;
+ }
+
+ public void emitCrd(HasMetadata crd, Set dependentClassNames, CRDGenerationInfo crdGenerationInfo) {
+ final String version = ApiVersionUtil.trimVersion(crd.getApiVersion());
+ final String crdName = crd.getMetadata().getName();
+ try {
+ final String outputName = getOutputName(crdName, version);
+ try (final OutputStreamWriter writer = new OutputStreamWriter(output.outputFor(outputName), StandardCharsets.UTF_8)) {
+ writer.write("# Generated by Fabric8 CRDGenerator, manual edits might get overwritten!\n");
+ String yaml = kubernetesSerialization.asYaml(crd);
+ // strip the explicit start added by default
+ writer.write(yaml.substring(4));
+ final URI fileURI = output.crdURI(outputName);
+ crdGenerationInfo.add(crdName, version, fileURI, dependentClassNames);
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ 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<>();
+
+ @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..df26d8135f2
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDInfo.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.generator;
+
+import java.util.Set;
+
+public class CRDInfo {
+ private final String crdName;
+ private final String crdSpecVersion;
+ private final String filePath;
+ private final Set dependentClassNames;
+
+ public CRDInfo(String crdName, String crdSpecVersion, String filePath, Set dependentClassNames) {
+ this.crdName = crdName;
+ this.crdSpecVersion = crdSpecVersion;
+ this.filePath = filePath;
+ this.dependentClassNames = dependentClassNames;
+ }
+
+ public String getCrdName() {
+ return crdName;
+ }
+
+ public String getCrdSpecVersion() {
+ return crdSpecVersion;
+ }
+
+ public String getFilePath() {
+ return filePath;
+ }
+
+ public Set getDependentClassNames() {
+ return dependentClassNames;
+ }
+
+}
diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDUtils.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDUtils.java
new file mode 100644
index 00000000000..a41cdab0a49
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CRDUtils.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.BeanDescription;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationConfig;
+import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class CRDUtils {
+ private CRDUtils() {
+ 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);
+ }
+
+ public static Map toMap(String[] arr) {
+ Map res = new HashMap<>();
+ if (arr != null) {
+ for (String e : arr) {
+ String[] splitted = e.split("\\=");
+ if (splitted.length >= 2) {
+ res.put(splitted[0], e.substring(splitted[0].length() + 1));
+ } else {
+ throw new IllegalArgumentException(
+ "Invalid value: " + e + " cannot be parsed as a key-value pair. Expected format is 'key=value'.");
+ }
+ }
+ }
+ return res;
+ }
+
+}
diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java
new file mode 100644
index 00000000000..e6afda58db3
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/CustomResourceInfo.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.CRDUtils.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 extends HasMetadata> 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 = CRDUtils.resolveSpecAndStatusTypes(customResource);
+ if (specAndStatus.isUnreliable()) {
+ LOGGER.warn(
+ "Cannot reliably determine status types for {} because it isn't parameterized with only spec and status types. Status replicas detection will be deactivated.",
+ 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..59621c0b768
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/InternalSchemaSwaps.java
@@ -0,0 +1,171 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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..e548f08b517
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/KubernetesJSONSchemaProps.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.generator;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
+import java.util.List;
+
+public interface KubernetesJSONSchemaProps {
+
+ String getType();
+
+ String getFormat();
+
+ String getDescription();
+
+ 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..f4a31756b9b
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/ResolvingContext.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.JsonMappingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+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.LinkedHashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Encapsulates the stateful Jackson details that allow for crd to be fully resolved by our logic
+ * - holds an association of uris to already generated jackson schemas
+ * - holds a Jackson SchemaGenerator which is not thread-safe
+ */
+public class ResolvingContext {
+
+ static final class GeneratorObjectSchema extends ObjectSchema {
+
+ 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;
+ uriToJacksonSchema.putIfAbsent(this.visitorContext.getSeenSchemaUri(convertedType), (GeneratorObjectSchema) schema);
+ return result;
+ }
+ }
+
+ final JsonSchemaGenerator generator;
+ final ObjectMapper objectMapper;
+ final KubernetesSerialization kubernetesSerialization;
+ final Map uriToJacksonSchema;
+ final boolean implicitPreserveUnknownFields;
+
+ private static KubernetesSerialization KUBERNETES_SERIALIZATION;
+ private static ObjectMapper OBJECT_MAPPER;
+
+ public static ResolvingContext defaultResolvingContext(boolean implicitPreserveUnknownFields) {
+ if (KUBERNETES_SERIALIZATION == null) {
+ OBJECT_MAPPER = new ObjectMapper();
+ KUBERNETES_SERIALIZATION = new KubernetesSerialization(OBJECT_MAPPER, false);
+ }
+ return new ResolvingContext(OBJECT_MAPPER, KUBERNETES_SERIALIZATION, implicitPreserveUnknownFields);
+ }
+
+ public ResolvingContext forkContext() {
+ return new ResolvingContext(objectMapper, kubernetesSerialization, uriToJacksonSchema, implicitPreserveUnknownFields);
+ }
+
+ public ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization,
+ boolean implicitPreserveUnknownFields) {
+ this(mapper, kubernetesSerialization, new ConcurrentHashMap<>(), implicitPreserveUnknownFields);
+ }
+
+ private ResolvingContext(ObjectMapper mapper, KubernetesSerialization kubernetesSerialization,
+ Map uriToJacksonSchema,
+ boolean implicitPreserveUnknownFields) {
+ this.uriToJacksonSchema = uriToJacksonSchema;
+ this.objectMapper = mapper;
+ this.kubernetesSerialization = kubernetesSerialization;
+ this.implicitPreserveUnknownFields = implicitPreserveUnknownFields;
+ generator = new JsonSchemaGenerator(mapper, new WrapperFactory() {
+
+ @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;
+ }
+
+ });
+ }
+
+ JsonSchema toJsonSchema(Class> clazz) {
+ try {
+ return generator.generateSchema(clazz);
+ } catch (JsonMappingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java
new file mode 100644
index 00000000000..7088c8f0292
--- /dev/null
+++ b/crd-generator/api-v2/src/main/java/io/fabric8/crdv2/generator/v1/CustomResourceHandler.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.generator.AbstractCustomResourceHandler;
+import io.fabric8.crdv2.generator.CRDUtils;
+import io.fabric8.crdv2.generator.CustomResourceInfo;
+import io.fabric8.crdv2.generator.ResolvingContext;
+import io.fabric8.kubernetes.api.model.HasMetadata;
+import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition;
+import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionBuilder;
+import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion;
+import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersionBuilder;
+import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps;
+import io.fabric8.kubernetes.client.utils.KubernetesVersionPriority;
+import io.fabric8.kubernetes.client.utils.Utils;
+import io.fabric8.kubernetes.model.annotation.LabelSelector;
+import io.fabric8.kubernetes.model.annotation.SpecReplicas;
+import io.fabric8.kubernetes.model.annotation.StatusReplicas;
+
+import java.util.AbstractMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class CustomResourceHandler extends AbstractCustomResourceHandler {
+
+ private Queue>> crds = new ConcurrentLinkedQueue<>();
+
+ public static final String VERSION = "v1";
+
+ @Override
+ public void handle(CustomResourceInfo config, ResolvingContext resolvingContext) {
+ final String name = config.crdName();
+ final String version = config.version();
+
+ JsonSchema resolver = new JsonSchema(resolvingContext, config.definition());
+ JSONSchemaProps schema = resolver.getSchema();
+
+ CustomResourceDefinitionVersionBuilder builder = new CustomResourceDefinitionVersionBuilder()
+ .withName(version)
+ .withStorage(config.storage())
+ .withServed(config.served())
+ .withDeprecated(config.deprecated() ? true : null)
+ .withDeprecationWarning(config.deprecationWarning())
+ .withNewSchema()
+ .withOpenAPIV3Schema(schema)
+ .endSchema();
+
+ handlePrinterColumns(resolver, new PrinterColumnHandler() {
+ @Override
+ public void addPrinterColumn(String path, String column, String format, int priority, String type, String description) {
+ builder.addNewAdditionalPrinterColumn()
+ .withType(type)
+ .withName(column)
+ .withJsonPath(path)
+ .withFormat(Utils.isNotNullOrEmpty(format) ? format : null)
+ .withDescription(Utils.isNotNullOrEmpty(description) ? description : null)
+ .withPriority(priority)
+ .endAdditionalPrinterColumn();
+ }
+ });
+
+ resolver.getSinglePath(SpecReplicas.class).ifPresent(path -> {
+ builder.editOrNewSubresources().editOrNewScale().withSpecReplicasPath(path).endScale().endSubresources();
+ });
+
+ resolver.getSinglePath(StatusReplicas.class).ifPresent(path -> {
+ builder.editOrNewSubresources().editOrNewScale().withStatusReplicasPath(path).endScale().endSubresources();
+ });
+
+ resolver.getSinglePath(LabelSelector.class).ifPresent(path -> {
+ builder.editOrNewSubresources().editOrNewScale().withLabelSelectorPath(path).endScale().endSubresources();
+ });
+
+ if (config.statusClassName().isPresent()) {
+ builder.editOrNewSubresources().withNewStatus().endStatus().endSubresources();
+ }
+
+ CustomResourceDefinition crd = new CustomResourceDefinitionBuilder()
+ .withNewMetadata()
+ .withName(name)
+ .withAnnotations(CRDUtils.toMap(config.annotations()))
+ .withLabels(CRDUtils.toMap(config.labels()))
+ .endMetadata()
+ .withNewSpec()
+ .withScope(config.scope().value())
+ .withGroup(config.group())
+ .withNewNames()
+ .withKind(config.kind())
+ .withShortNames(config.shortNames())
+ .withPlural(config.plural())
+ .withSingular(config.singular())
+ .endNames()
+ .addToVersions(builder.build())
+ .endSpec()
+ .build();
+
+ crds.add(new AbstractMap.SimpleEntry<>(crd, resolver.getDependentClasses()));
+ }
+
+ @Override
+ public Stream>> finish() {
+ return crds.stream().collect(Collectors.groupingBy(crd -> crd.getKey().getMetadata().getName())).values().stream()
+ .map(this::combine);
+ }
+
+ private Map.Entry> combine(
+ List>> definitions) {
+ Map.Entry> primary = definitions.get(0);
+ if (definitions.size() == 1) {
+ return primary;
+ }
+
+ List versions = definitions.stream()
+ .flatMap(crd -> crd.getKey().getSpec().getVersions().stream())
+ .collect(Collectors.toList());
+
+ Set allDependentClasses = definitions.stream().flatMap(crd -> crd.getValue().stream()).collect(Collectors.toSet());
+
+ List storageVersions = versions.stream()
+ .filter(v -> Optional.ofNullable(v.getStorage()).orElse(true))
+ .map(CustomResourceDefinitionVersion::getName)
+ .collect(Collectors.toList());
+
+ if (storageVersions.size() > 1) {
+ throw new IllegalStateException(String.format(
+ "'%s' custom resource has versions %s marked as storage. Only one version can be marked as storage per custom resource.",
+ primary.getKey().getMetadata().getName(), storageVersions));
+ }
+
+ versions = KubernetesVersionPriority.sortByPriority(versions, CustomResourceDefinitionVersion::getName);
+
+ //TODO: we could double check that the top-level metadata is consistent across all versions
+ return new AbstractMap.SimpleEntry<>(
+ new CustomResourceDefinitionBuilder(primary.getKey()).editSpec().withVersions(versions).endSpec().build(),
+ allDependentClasses);
+ }
+
+}
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..e845d4c89c2
--- /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(false), 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/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..55cfe8837ab
--- /dev/null
+++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/annotated/AnnotatedSpec.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2015 Red Hat, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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"),
+ @JsonProperty("foo")
+ @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..98061a60ead
--- /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()
+ 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