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 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 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 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... 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 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> stringToIntMultiMap4; + public RedundantParametersStringToIntMultiMap stringToIntMultiMap5; + public StringKeyedMultiHashMap stringToIntMultiMap6; + public IntValuedMultiMap stringToIntMultiMap7; + + static class MultiHashMap extends HashMap> { + } + + interface MultiMap extends Map> { + } + + interface SwappedParametersMap extends Map { + } + + interface RedundantParametersMap extends Map { + } + + interface RedundantParametersStringToIntMultiMap extends Map> { + } + + static class StringKeyedMultiHashMap extends MultiHashMap { + } + + interface IntValuedMultiMap extends MultiMap { + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/Multiple.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/Multiple.java new file mode 100644 index 00000000000..b0e238029b8 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/Multiple.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.multiple.v1; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version(value = "v1", storage = false) +public class Multiple extends CustomResource { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/MultipleSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/MultipleSpec.java new file mode 100644 index 00000000000..f384b0dface --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v1/MultipleSpec.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.multiple.v1; + +public class MultipleSpec { + private String v1; + + public String getV1() { + return v1; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/Multiple.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/Multiple.java new file mode 100644 index 00000000000..3eeb2099e90 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/Multiple.java @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.multiple.v2; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v2") +public class Multiple extends CustomResource { +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/MultipleSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/MultipleSpec.java new file mode 100644 index 00000000000..f77ac2a1542 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/multiple/v2/MultipleSpec.java @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.multiple.v2; + +public class MultipleSpec { + private String v2; + + public String getV2() { + return v2; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclic.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclic.java new file mode 100644 index 00000000000..2abdd702407 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclic.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.nocyclic; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("sample.fabric8.io") +@Version("v1alpha1") +public class NoCyclic extends CustomResource implements Namespaced { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicSpec.java new file mode 100644 index 00000000000..0bcf423c3f4 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicSpec.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.nocyclic; + +public class NoCyclicSpec { + public Ref ref1; + public Ref ref2; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicStatus.java new file mode 100644 index 00000000000..d485c7b2152 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/NoCyclicStatus.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.nocyclic; + +public class NoCyclicStatus { + private String message; + private Ref ref1; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/Ref.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/Ref.java new file mode 100644 index 00000000000..e901d51f641 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/nocyclic/Ref.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.nocyclic; + +public class Ref { + + public int ref; + + public Inner inner; + + public static class Inner { + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Address.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Address.java new file mode 100644 index 00000000000..1404898266d --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Address.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.person; + +public class Address { + public String street; + public int number; + public String zip; + public String country; + public Type type; + + public enum Type { + home, + work + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/AddressList.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/AddressList.java new file mode 100644 index 00000000000..98fd75a475d --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/AddressList.java @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.person; + +import java.util.ArrayList; + +public class AddressList extends ArrayList
{ +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Person.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Person.java new file mode 100644 index 00000000000..72c57dd688f --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/person/Person.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.person; + +import java.util.List; +import java.util.Optional; + +public class Person { + + public String firstName; + public Optional middleName; + public String lastName; + public int birthYear; + public List hobbies; + public AddressList addresses; + public Type type; + + public enum Type { + crazy, + crazier + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/Simplest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/Simplest.java new file mode 100644 index 00000000000..a875696430c --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/Simplest.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.simplest; + +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Group("samples.fabric8.io") +@Version("v1alpha1") +public class Simplest extends CustomResource { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestSpec.java new file mode 100644 index 00000000000..4516a828baf --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestSpec.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.simplest; + +public class SimplestSpec { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestStatus.java new file mode 100644 index 00000000000..b0238a84741 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/simplest/SimplestStatus.java @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.simplest; + +public class SimplestStatus { + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerSpec.java new file mode 100644 index 00000000000..b24dba42f36 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerSpec.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.webserver; + +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import lombok.Data; + +@Data +public class WebServerSpec { + + private int port; + + @SpecReplicas + private int replicas; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerStatus.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerStatus.java new file mode 100644 index 00000000000..a64d35ba505 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerStatus.java @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.webserver; + +import io.fabric8.kubernetes.model.annotation.StatusReplicas; +import lombok.Data; + +@Data +public class WebServerStatus { + + @StatusReplicas + int replicas; + private boolean running; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithSpec.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithSpec.java new file mode 100644 index 00000000000..e46c6af131b --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithSpec.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.webserver; + +import lombok.Data; + +@Data +public class WebServerWithSpec { + + private String name; + + private WebServerSpec spec; + + private WebServerStatus status; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithStatusProperty.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithStatusProperty.java new file mode 100644 index 00000000000..7f85996b4ac --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/example/webserver/WebServerWithStatusProperty.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.example.webserver; + +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import lombok.Data; + +@Data +public class WebServerWithStatusProperty { + + private String name; + private int port; + + @SpecReplicas + private int replicas; + + private WebServerStatus status; +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java new file mode 100644 index 00000000000..4cd39add755 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorAssertions.java @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator; + +import io.fabric8.kubernetes.client.CustomResource; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + +public class CRDGeneratorAssertions { + + private CRDGeneratorAssertions() { + } + + /** + * Generates CRD files for v1 and compares them with files in classpath. + * + * @param crClasses custom resource classes under test + * @param crdGenerator a CRDGenerator instance + */ + @SafeVarargs + public static void assertCRDOutputEquals(CRDGenerator crdGenerator, + Class>... crClasses) { + + assertCRDOutputEquals(crdGenerator, null, crClasses); + } + + /** + * Generates CRD files for v1 and compares them with files in classpath. + * + * @param crClasses custom resource classes under test + * @param crdGenerator a CRDGenerator instance + * @param classPathExpectedCRDsNullable the class path to the directory which contains the expected CRDs. Defaults to "/" if + * null. + */ + @SafeVarargs + public static void assertCRDOutputEquals(CRDGenerator crdGenerator, + String classPathExpectedCRDsNullable, + Class>... crClasses) { + assertNotNull(crClasses); + assertTrue(crClasses.length > 0); + assertEquals(1, Arrays.stream(crClasses) + .map(CustomResource::getCRDName).distinct().count(), + "all crClasses must be of the same kind"); + + final String crdName = CustomResource.getCRDName(crClasses[0]); + final File outputDir; + try { + outputDir = Files.createTempDirectory("crd-").toFile(); + } catch (IOException e) { + fail("Could not create temp directory", e); + throw new RuntimeException(e); + } + + final String classPathExpectedCRDs = Optional.ofNullable(classPathExpectedCRDsNullable) + .map(s -> s.endsWith("/") ? s : s + "/") + .orElse("/"); + + // generate actual CRDs + final CRDGenerationInfo crdInfo = crdGenerator + .inOutputDir(outputDir) + .customResourceClasses(crClasses) + .forCRDVersions("v1") + .detailedGenerate(); + final File actualCRDFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); + + // expected CRDs + final URL expectedCRDResource = CRDGeneratorTest.class.getResource(classPathExpectedCRDs + actualCRDFile.getName()); + assertNotNull(expectedCRDResource); + final File expectedCrdFile = new File(expectedCRDResource.getFile()); + + // compare + assertFileEquals(expectedCrdFile, actualCRDFile); + + // only delete the generated files if the test is successful + assertTrue(actualCRDFile.delete()); + assertTrue(outputDir.delete()); + } + + /** + * Compares two YAML files and fails if they are not equal. + * Comments and empty lines are ignored. + * + * @param expectedFile the file which contains the expected content + * @param actualFile the file which contains the content to test + */ + public static void assertFileEquals(final File expectedFile, final File actualFile) { + try (final BufferedReader expectedReader = new BufferedReader(new FileReader(expectedFile)); + final BufferedReader actualReader = new BufferedReader(new FileReader(actualFile))) { + // skip license headers + String expectedLine = skipCommentsAndEmptyLines(expectedReader); + String actualLine = skipCommentsAndEmptyLines(actualReader); + + // compare both files + final String message = String.format("Expected %s and actual %s files are not equal", expectedFile, actualFile); + while (expectedLine != null || actualLine != null) { + assertEquals(expectedLine, actualLine, message); + expectedLine = expectedReader.readLine(); + actualLine = actualReader.readLine(); + } + } catch (final IOException e) { + fail(String.format("Cannot compare files %s and %s: %s", expectedFile, actualFile, e.getMessage())); + } + } + + private static String skipCommentsAndEmptyLines(final BufferedReader reader) throws IOException { + String line = reader.readLine(); + while (line.startsWith("#") || line.isEmpty()) { + line = reader.readLine(); + } + return line; + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorExamplesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorExamplesTest.java new file mode 100644 index 00000000000..608297cbf0f --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorExamplesTest.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator; + +import io.fabric8.crdv2.example.k8svalidation.K8sValidation; +import io.fabric8.crdv2.example.multiple.v2.MultipleSpec; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static io.fabric8.crdv2.generator.CRDGeneratorAssertions.assertCRDOutputEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class CRDGeneratorExamplesTest { + + protected boolean parallelCRDGeneration; + + @Test + void multiple() throws IOException { + assertCRDOutputEquals(newCRDGenerator(), + io.fabric8.crdv2.example.multiple.v1.Multiple.class, io.fabric8.crdv2.example.multiple.v2.Multiple.class); + } + + @Group("sample.fabric8.io") + @Version(value = "v3") + public static class Multiple extends CustomResource { + } + + @Test + void multipleStorage_thenFail() { + CRDGenerator crdGenerator = newCRDGenerator(); + assertThrows(IllegalStateException.class, () -> assertCRDOutputEquals(crdGenerator, + io.fabric8.crdv2.example.multiple.v2.Multiple.class, Multiple.class)); + } + + @Test + void k8sValidation() throws IOException { + assertCRDOutputEquals(newCRDGenerator(), K8sValidation.class); + } + + private CRDGenerator newCRDGenerator() { + return new CRDGenerator() + .withParallelGenerationEnabled(parallelCRDGeneration); + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java new file mode 100644 index 00000000000..1869b67bc75 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CRDGeneratorTest.java @@ -0,0 +1,590 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator; + +import io.fabric8.crdv2.example.basic.Basic; +import io.fabric8.crdv2.example.complex.Complex; +import io.fabric8.crdv2.example.cyclic.Cyclic; +import io.fabric8.crdv2.example.cyclic.CyclicList; +import io.fabric8.crdv2.example.deprecated.v2.DeprecationExample; +import io.fabric8.crdv2.example.inherited.Child; +import io.fabric8.crdv2.example.joke.Joke; +import io.fabric8.crdv2.example.joke.JokeRequest; +import io.fabric8.crdv2.example.k8svalidation.K8sValidation; +import io.fabric8.crdv2.example.map.ContainingMaps; +import io.fabric8.crdv2.example.multiple.v1.Multiple; +import io.fabric8.crdv2.example.nocyclic.NoCyclic; +import io.fabric8.crdv2.example.simplest.Simplest; +import io.fabric8.crdv2.generator.CRDGenerator.AbstractCRDOutput; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceColumnDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionNames; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionSpec; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinitionVersion; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceValidation; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.client.utils.Serialization; +import io.fabric8.kubernetes.model.Scope; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; +import org.opentest4j.AssertionFailedError; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.net.URL; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import static io.fabric8.crdv2.generator.CRDGeneratorAssertions.assertFileEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class CRDGeneratorTest { + + private final TestCRDOutput output = new TestCRDOutput(); + protected boolean parallelCRDGeneration; + + @Test + void choosingCRDVersionsShouldWork() { + CRDGenerator generator = newCRDGenerator(); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions(); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions((List) null); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions((String[]) null); + assertTrue(generator.getHandlers().isEmpty()); + + generator.forCRDVersions("v2"); + assertTrue(generator.getHandlers().isEmpty()); + + String version = "v1"; + generator.forCRDVersions(version); + Map handlers = generator.getHandlers(); + assertEquals(1, handlers.size()); + assertTrue(handlers.containsKey(version)); + + // v1beta1 not supported + generator.forCRDVersions(version, version, "v3", null); + handlers = generator.getHandlers(); + assertEquals(1, handlers.size()); + assertTrue(handlers.containsKey(version)); + } + + @Test + void addingCustomResourceInfosShouldWork() { + CRDGenerator generator = newCRDGenerator(); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResourceClasses(); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResources(); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResources(null); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResources(null, null); + assertTrue(generator.getCustomResourceInfos().isEmpty()); + + generator.customResourceClasses(Simplest.class); + assertEquals(1, generator.getCustomResourceInfos().size()); + assertTrue(generator.getCustomResourceInfos().stream().allMatch(cri -> cri.crClassName().equals(Simplest.class.getName()))); + + generator.customResourceClasses(Child.class); + assertEquals(2, generator.getCustomResourceInfos().size()); + CustomResourceInfo simplest = CustomResourceInfo.fromClass(Simplest.class); + assertTrue(generator.getCustomResourceInfos().contains(simplest)); + CustomResourceInfo child = CustomResourceInfo.fromClass(Child.class); + assertTrue(generator.getCustomResourceInfos().contains(child)); + + generator.customResources(CustomResourceInfo.fromClass(Child.class)); + assertEquals(2, generator.getCustomResourceInfos().size()); + + CustomResourceInfo joke = CustomResourceInfo.fromClass(Joke.class); + CustomResourceInfo jr = CustomResourceInfo.fromClass(JokeRequest.class); + generator.customResources(joke, jr); + Set infos = generator.getCustomResourceInfos(); + assertEquals(4, infos.size()); + assertTrue(infos.contains(simplest)); + assertTrue(infos.contains(child)); + assertTrue(infos.contains(joke)); + assertTrue(infos.contains(jr)); + } + + @Test + void shouldProperlyRecordNumberOfGeneratedCRDs() { + CRDGenerator generator = newCRDGenerator(); + assertEquals(0, generator.generate()); + assertEquals(0, generator.detailedGenerate().numberOfGeneratedCRDs()); + + final List versions = new ArrayList<>(2); + versions.add("v1"); + + final CRDGenerationInfo info = generator + .customResourceClasses(Simplest.class, Child.class, Joke.class, JokeRequest.class) + .forCRDVersions(versions) + .withOutput(output).detailedGenerate(); + + assertEquals(4, info.numberOfGeneratedCRDs()); + final Map> details = info.getCRDDetailsPerNameAndVersion(); + assertEquals(4, details.size()); + assertTrue(details.containsKey(CustomResource.getCRDName(Simplest.class))); + assertTrue(details.containsKey(CustomResource.getCRDName(Child.class))); + assertTrue(details.containsKey(CustomResource.getCRDName(Joke.class))); + final String crdName = CustomResource.getCRDName(JokeRequest.class); + assertTrue(details.containsKey(crdName)); + final Map jokeRequestInfos = info.getCRDInfos(crdName); + assertEquals(1, jokeRequestInfos.size()); + assertTrue(jokeRequestInfos.containsKey("v1")); + } + + @Test + void checkDeprecated() { + CRDGenerator generator = newCRDGenerator(); + final String specVersion = "v1"; + final CRDGenerationInfo info = generator + .customResourceClasses( + io.fabric8.crdv2.example.deprecated.v1beta1.DeprecationExample.class, + io.fabric8.crdv2.example.deprecated.v1.DeprecationExample.class, + DeprecationExample.class) + .forCRDVersions(specVersion) + .withOutput(output) + .detailedGenerate(); + + assertEquals(1, info.numberOfGeneratedCRDs()); + final Map> details = info.getCRDDetailsPerNameAndVersion(); + assertEquals(1, details.size()); + // check multiple versions for same CR + final String crdName = CustomResource.getCRDName(DeprecationExample.class); + assertTrue(details.containsKey(crdName)); + final Map infos = info.getCRDInfos(crdName); + assertEquals(1, infos.size()); + assertTrue(infos.containsKey(specVersion)); + + final String outputName = CRDGenerator.getOutputName(crdName, specVersion); + CustomResourceDefinition definition = output.definition(outputName); + assertNotNull(definition); + assertEquals("apiextensions.k8s.io/" + specVersion, definition.getApiVersion()); + + CustomResourceDefinitionSpec spec = definition.getSpec(); + final List versions = spec.getVersions(); + assertEquals(3, versions.size()); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v1beta1")).count()); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v1")).count()); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v2")).count()); + + CustomResourceDefinitionVersion v1Beta1 = versions.stream().filter(v -> v.getName().equals("v1beta1")).findFirst().get(); + CustomResourceDefinitionVersion v1 = versions.stream().filter(v -> v.getName().equals("v1")).findFirst().get(); + CustomResourceDefinitionVersion v2 = versions.stream().filter(v -> v.getName().equals("v2")).findFirst().get(); + assertTrue(v1Beta1.getDeprecated()); + assertEquals("sample.fabric8.io/v1beta1 DeprecationExample is deprecated; Migrate to sample.fabric8.io/v2", + v1Beta1.getDeprecationWarning()); + assertTrue(v1.getDeprecated()); + assertNull(v1.getDeprecationWarning()); + assertNull(v2.getDeprecated()); + assertNull(v2.getDeprecationWarning()); + } + + @Test + void notDefiningOutputShouldNotGenerateAnything() { + CRDGenerator generator = newCRDGenerator(); + assertEquals(0, generator.generate()); + + CustomResourceInfo joke = CustomResourceInfo.fromClass(Joke.class); + CustomResourceInfo jr = CustomResourceInfo.fromClass(JokeRequest.class); + generator.customResources(joke, jr); + assertEquals(0, generator.generate()); + } + + @Test + void generatingACycleShouldFail() { + final CRDGenerator generator = newCRDGenerator() + .customResourceClasses(Cyclic.class) + .forCRDVersions("v1") + .withOutput(output); + + assertThrows( + IllegalArgumentException.class, + generator::detailedGenerate, + "An IllegalArgument Exception hasn't been thrown when generating a CRD with cyclic references"); + } + + private CRDGenerator newCRDGenerator() { + return new CRDGenerator() + .withParallelGenerationEnabled(parallelCRDGeneration); + } + + @Test + void generatingACycleInListShouldFail() { + final CRDGenerator generator = newCRDGenerator() + .customResourceClasses(CyclicList.class) + .forCRDVersions("v1") + .withOutput(output); + + assertThrows( + IllegalArgumentException.class, + generator::detailedGenerate, + "An IllegalArgument Exception hasn't been thrown when generating a CRD with cyclic references"); + } + + @Test + void notGeneratingACycleShouldSucceed() { + final CRDGenerator generator = newCRDGenerator() + .customResourceClasses(NoCyclic.class) + .forCRDVersions("v1") + .withOutput(output); + + CRDGenerationInfo info = generator.detailedGenerate(); + assertEquals(1, info.numberOfGeneratedCRDs()); + } + + @FunctionalInterface + private interface CRTest { + void test(Class> customResource); + } + + private void outputCRDIfFailed(Class> customResource, CRTest test) { + try { + test.test(customResource); + } catch (AssertionFailedError e) { + // output crd + output.outputCRD(customResource); + throw e; + } + } + + @Test + void simplestCRDShouldWork() { + outputCRDIfFailed(Simplest.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "Simplest", "simplests", + Scope.CLUSTER); + assertNotNull(version.getSubresources()); + }); + + } + + @Test + void inheritedCRDShouldWork() { + outputCRDIfFailed(Child.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "Child", "children", + Scope.NAMESPACED); + assertNotNull(version.getSubresources()); + final Map specProps = version.getSchema().getOpenAPIV3Schema() + .getProperties().get("spec").getProperties(); + assertEquals(4, specProps.size()); + assertEquals("integer", specProps.get("baseInt").getType()); + checkMapProp(specProps, "unsupported", "object"); + checkMapProp(specProps, "unsupported2", "object"); + checkMapProp(specProps, "supported", "string"); + }); + } + + @Test + void mapPropertyShouldHaveCorrectValueType() { + outputCRDIfFailed(ContainingMaps.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "ContainingMaps", "containingmaps", + Scope.CLUSTER); + assertNotNull(version.getSchema()); + + final Map specProps = version.getSchema().getOpenAPIV3Schema() + .getProperties().get("spec").getProperties(); + + assertEquals(9, specProps.size()); + + JSONSchemaProps testSchema = checkMapProp(specProps, "test", "array"); + assertEquals("string", testSchema.getItems().getSchema().getType()); + + JSONSchemaProps test2Schema = checkMapProp(specProps, "test2", "object"); + JSONSchemaProps valueSchema = test2Schema.getAdditionalProperties().getSchema(); + String valueType = valueSchema.getType(); + assertEquals("array", valueType); + assertEquals("boolean", valueSchema.getItems().getSchema().getType()); + + for (int i = 1; i <= 7; i++) { + String name = "stringToIntMultiMap" + i; + JSONSchemaProps schema = checkMapProp(specProps, name, "array"); + assertEquals("integer", schema.getItems().getSchema().getType(), name + "'s array item type should be integer"); + } + }); + } + + private JSONSchemaProps checkMapProp(Map specProps, String name, String valueType) { + final JSONSchemaProps props = specProps.get(name); + assertNotNull(props, name + " should be contained in spec"); + assertEquals("object", props.getType(), name + "'s type should be object"); + assertEquals(valueType, props.getAdditionalProperties().getSchema().getType(), + name + "'s value type should be " + valueType); + return props.getAdditionalProperties().getSchema(); + } + + @Test + void jokeCRDShouldWork() { + outputCRDIfFailed(Joke.class, (customResource) -> { + CustomResourceDefinitionVersion version = checkCRD(Joke.class, "Joke", "jokes", + Scope.NAMESPACED); + assertNull(version.getSubresources()); + }); + } + + @Test + void jokerequestCRDShouldWork() { + outputCRDIfFailed(JokeRequest.class, (customResource) -> { + final CustomResourceDefinitionSpec spec = checkSpec(customResource, Scope.NAMESPACED); + + final CustomResourceDefinitionNames names = checkNames("JokeRequest", + "jokerequests", spec); + assertEquals(1, names.getShortNames().size()); + assertTrue(names.getShortNames().contains("jr")); + + final CustomResourceDefinitionVersion version = checkVersion(spec); + assertNotNull(version.getSubresources()); + // printer columns should be ordered in the alphabetical order of their json path + final List printerColumns = version + .getAdditionalPrinterColumns(); + assertEquals(3, printerColumns.size()); + CustomResourceColumnDefinition columnDefinition = printerColumns.get(0); + assertEquals("string", columnDefinition.getType()); + assertEquals(".spec.category", columnDefinition.getJsonPath()); + assertEquals("jokeCategory", columnDefinition.getName()); + assertEquals(1, columnDefinition.getPriority()); + columnDefinition = printerColumns.get(1); + assertEquals("string", columnDefinition.getType()); + assertEquals(".spec.excluded", columnDefinition.getJsonPath()); + assertEquals("excludedTopics", columnDefinition.getName()); + assertEquals(0, columnDefinition.getPriority()); + columnDefinition = printerColumns.get(2); + assertEquals("string", columnDefinition.getType()); + assertEquals(".status.category", columnDefinition.getJsonPath()); + assertEquals("jokeCategory", columnDefinition.getName()); + assertEquals(0, columnDefinition.getPriority()); + CustomResourceValidation schema = version.getSchema(); + assertNotNull(schema); + Map properties = schema.getOpenAPIV3Schema().getProperties(); + assertEquals(2, properties.size()); + Map specProps = properties.get("spec").getProperties(); + assertEquals(3, specProps.size()); + assertEquals("boolean", specProps.get("safe").getType()); + JSONSchemaProps category = specProps.get("category"); + assertEquals("string", category.getType()); + assertEquals(7, category.getEnum().size()); + assertEquals("category-description", category.getDescription()); + JSONSchemaProps excluded = specProps.get("excluded"); + assertEquals("array", excluded.getType()); + assertEquals("string", excluded.getItems().getSchema().getType()); + assertEquals(6, excluded.getItems().getSchema().getEnum().size()); + }); + } + + @Test + void checkCRDGenerator() { + outputCRDIfFailed(Basic.class, (customResource) -> { + final CustomResourceDefinitionVersion version = checkCRD(customResource, "Basic", "basics", + Scope.NAMESPACED); + assertNotNull(version.getSubresources()); + CustomResourceValidation schema = version.getSchema(); + assertNotNull(schema); + Map properties = schema.getOpenAPIV3Schema().getProperties(); + assertEquals(2, properties.size()); + Map specProps = properties.get("spec").getProperties(); + assertEquals("integer", specProps.get("myInt").getType()); + Map status = properties.get("status").getProperties(); + assertEquals("string", status.get("message").getType()); + }); + } + + @Test + void checkGenerationIsDeterministic() throws Exception { + // generated CRD + final File outputDir = Files.createTempDirectory("crd-").toFile(); + final String crdName = CustomResourceInfo.fromClass(Complex.class).crdName(); + final CRDGenerationInfo crdInfo = newCRDGenerator() + .inOutputDir(outputDir) + .forCRDVersions("v1") + .customResourceClasses(Complex.class) + .detailedGenerate(); + final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); + + // expected CRD + final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName()); + + assertNotNull(crdResource); + final File expectedCrdFile = new File(crdResource.getFile()); + assertFileEquals(expectedCrdFile, crdFile); + + // only delete the generated files if the test is successful + assertTrue(crdFile.delete()); + assertTrue(outputDir.delete()); + } + + @RepeatedTest(value = 10) + void checkGenerationMultipleVersionsOfCRDsIsDeterministic() throws Exception { + // generated CRD + final File outputDir = Files.createTempDirectory("crd-").toFile(); + final CustomResourceInfo infoV1 = CustomResourceInfo.fromClass(Multiple.class); + final CustomResourceInfo infoV2 = CustomResourceInfo.fromClass(io.fabric8.crdv2.example.multiple.v2.Multiple.class); + assertEquals(infoV1.crdName(), infoV2.crdName()); + final String crdName = infoV1.crdName(); + + final CRDGenerationInfo crdInfo = newCRDGenerator() + .inOutputDir(outputDir) + .customResourceClasses(Multiple.class, + io.fabric8.crdv2.example.multiple.v2.Multiple.class) + .forCRDVersions("v1") + .detailedGenerate(); + + final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); + + // expected CRD + final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName()); + assertNotNull(crdResource); + + final File expectedCrdFile = new File(crdResource.getFile()); + assertFileEquals(expectedCrdFile, crdFile); + + // only delete the generated files if the test is successful + assertTrue(crdFile.delete()); + assertTrue(outputDir.delete()); + } + + @Test + void checkK8sValidationRules() throws Exception { + // generated CRD + final File outputDir = Files.createTempDirectory("crd-").toFile(); + final String crdName = CustomResourceInfo.fromClass(K8sValidation.class).crdName(); + + final CRDGenerationInfo crdInfo = newCRDGenerator() + .inOutputDir(outputDir) + .customResourceClasses(K8sValidation.class) + .forCRDVersions("v1") + .detailedGenerate(); + + final File crdFile = new File(crdInfo.getCRDInfos(crdName).get("v1").getFilePath()); + + // expected CRD + final URL crdResource = CRDGeneratorTest.class.getResource("/" + crdFile.getName()); + assertNotNull(crdResource); + + final File expectedCrdFile = new File(crdResource.getFile()); + assertFileEquals(expectedCrdFile, crdFile); + + // only delete the generated files if the test is successful + assertTrue(crdFile.delete()); + assertTrue(outputDir.delete()); + } + + private CustomResourceDefinitionVersion checkCRD(Class> customResource, String kind, + String plural, + Scope scope) { + CustomResourceDefinitionSpec spec = checkSpec(customResource, scope); + checkNames(kind, plural, spec); + + return checkVersion(spec); + } + + private CustomResourceDefinitionVersion checkVersion(CustomResourceDefinitionSpec spec) { + CustomResourceDefinitionVersion version = spec.getVersions().get(0); + assertTrue(version.getServed()); + assertTrue(version.getStorage()); + return version; + } + + private CustomResourceDefinitionNames checkNames(String kind, String plural, CustomResourceDefinitionSpec spec) { + CustomResourceDefinitionNames names = spec.getNames(); + assertEquals(kind, names.getKind()); + assertEquals(plural, names.getPlural()); + return names; + } + + private CustomResourceDefinitionSpec checkSpec( + Class> customResource, Scope scope) { + CRDGenerator generator = newCRDGenerator(); + + // record info to be able to output it if the test fails + final String outputName = keyFor(customResource); + final CustomResourceInfo info = CustomResourceInfo.fromClass(customResource); + output.put(outputName, info); + final String v1 = "v1"; + final CRDGenerationInfo generatedInfo = generator.withOutput(output) + .forCRDVersions(v1) + .customResources(info) + .detailedGenerate(); + assertEquals(1, generatedInfo.numberOfGeneratedCRDs()); + final String crdName = info.crdName(); + final Map crdInfos = generatedInfo.getCRDInfos(crdName); + assertEquals(1, crdInfos.size()); + final CRDInfo crdInfo = crdInfos.get(v1); + assertEquals(crdName, crdInfo.getCrdName()); + assertEquals(v1, crdInfo.getCrdSpecVersion()); + assertTrue(crdInfo.getFilePath().endsWith(CRDGenerator.getOutputName(crdName, v1))); // test output uses the CRD name as URI + + CustomResourceDefinition definition = output.definition(outputName); + assertNotNull(definition); + assertEquals("apiextensions.k8s.io/v1", definition.getApiVersion()); + + CustomResourceDefinitionSpec spec = definition.getSpec(); + assertEquals(scope.toString(), spec.getScope()); + return spec; + } + + private static String keyFor(Class> customResource) { + return CRDGenerator.getOutputName(CustomResource.getCRDName(customResource), "v1"); + } + + private static class TestCRDOutput extends AbstractCRDOutput { + + private static final Logger LOGGER = LoggerFactory.getLogger(TestCRDOutput.class); + private final static Class crdClass = CustomResourceDefinition.class; + private final Map infos = new ConcurrentHashMap<>(); + + @Override + protected ByteArrayOutputStream createStreamFor(String crdName) { + return new ByteArrayOutputStream(); + } + + CustomResourceDefinition definition(String outputName) { + return Serialization.unmarshal(new ByteArrayInputStream(getStreamFor(outputName).toByteArray()), crdClass); + } + + void put(String outputName, CustomResourceInfo info) { + infos.put(outputName, info); + } + + CustomResourceInfo get(String outputName) { + return infos.get(outputName); + } + + void outputCRD(Class> customResource) { + String s = getStreamFor(keyFor(customResource)).toString(); + LOGGER.debug(s); + } + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CustomResourceInfoTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CustomResourceInfoTest.java new file mode 100644 index 00000000000..4dc8a0fd9dc --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/CustomResourceInfoTest.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator; + +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.Scope; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.ShortNames; +import io.fabric8.kubernetes.model.annotation.Version; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class CustomResourceInfoTest { + + public static class Spec { + } + + public static class Status { + } + + private static final String GROUP = "sample.fabric8.io"; + private static final String VERSION = "v1"; + + @Group(GROUP) + @Version(VERSION) + @ShortNames("s") + public static class ClusteredCR extends CustomResource { + } + + @Group(GROUP) + @Version(VERSION) + public static class NamespacedCR extends CustomResource implements Namespaced { + } + + @Test + void shouldBeProperlyScoped() { + CustomResourceInfo info = CustomResourceInfo.fromClass(ClusteredCR.class); + assertEquals(GROUP, info.group()); + assertEquals(VERSION, info.version()); + assertEquals(Scope.CLUSTER, info.scope()); + + info = CustomResourceInfo.fromClass(NamespacedCR.class); + assertEquals(GROUP, info.group()); + assertEquals(VERSION, info.version()); + assertEquals(Scope.NAMESPACED, info.scope()); + } + + @Test + void shouldProperlyCreateCustomResourceInfo() { + CustomResourceInfo info = CustomResourceInfo.fromClass(ClusteredCR.class); + assertEquals(GROUP, info.group()); + assertEquals(VERSION, info.version()); + assertEquals(Scope.CLUSTER, info.scope()); + assertEquals(ClusteredCR.class.getCanonicalName(), info.crClassName()); // todo: should we actually use the type name here? + assertTrue(info.specClassName().isPresent()); + String specClassName = info.specClassName().get(); + assertEquals(Spec.class.getName(), specClassName); + // todo: check that we can load and instantiate class from the returned class name + /* + * Class specClass = Class.forName(specClassName); + * Object o = specClass.getDeclaredConstructor().newInstance(); + * assertNotNull(o); + */ + assertTrue(info.statusClassName().isPresent()); + assertEquals(Status.class.getName(), info.statusClassName().get()); + assertEquals(HasMetadata.getSingular(ClusteredCR.class), info.singular()); + assertEquals(HasMetadata.getPlural(ClusteredCR.class), info.plural()); + assertEquals(CustomResource.getCRDName(ClusteredCR.class), info.crdName()); + assertArrayEquals(CustomResource.getShortNames(ClusteredCR.class), info.shortNames()); + assertTrue(info.served()); + assertTrue(info.storage()); + assertEquals(HasMetadata.getKind(ClusteredCR.class), info.kind()); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorExamplesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorExamplesTest.java new file mode 100644 index 00000000000..1f391be0c6a --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorExamplesTest.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator; + +public class ParallelCRDGeneratorExamplesTest extends CRDGeneratorExamplesTest { + public ParallelCRDGeneratorExamplesTest() { + parallelCRDGeneration = true; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorTest.java new file mode 100644 index 00000000000..30ca61c8a8f --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/ParallelCRDGeneratorTest.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator; + +class ParallelCRDGeneratorTest extends CRDGeneratorTest { + + public ParallelCRDGeneratorTest() { + parallelCRDGeneration = false; + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.java new file mode 100644 index 00000000000..3cc4dffbcd4 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/utils/TypesTest.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.generator.utils; + +import io.fabric8.crdv2.example.inherited.Child; +import io.fabric8.crdv2.generator.CRDUtils; +import io.fabric8.crdv2.generator.CRDUtils.SpecAndStatus; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public class TypesTest { + + @Test + void shouldFindInheritedStatusProperty() { + final SpecAndStatus specAndStatus = CRDUtils.resolveSpecAndStatusTypes(Child.class); + assertEquals("io.fabric8.crdv2.example.inherited.ChildStatus", specAndStatus.getStatusClassName()); + assertEquals("io.fabric8.crdv2.example.inherited.ChildSpec", specAndStatus.getSpecClassName()); + } + +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java new file mode 100644 index 00000000000..bd9fb262584 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/JsonSchemaTest.java @@ -0,0 +1,601 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator.v1; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.node.TextNode; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import io.fabric8.crd.generator.annotation.SchemaSwap; +import io.fabric8.crdv2.example.annotated.Annotated; +import io.fabric8.crdv2.example.basic.Basic; +import io.fabric8.crdv2.example.extraction.CollectionCyclicSchemaSwap; +import io.fabric8.crdv2.example.extraction.CyclicSchemaSwap; +import io.fabric8.crdv2.example.extraction.DeeplyNestedSchemaSwaps; +import io.fabric8.crdv2.example.extraction.Extraction; +import io.fabric8.crdv2.example.extraction.IncorrectExtraction; +import io.fabric8.crdv2.example.extraction.IncorrectExtraction2; +import io.fabric8.crdv2.example.extraction.MultipleSchemaSwaps; +import io.fabric8.crdv2.example.extraction.NestedSchemaSwap; +import io.fabric8.crdv2.example.json.ContainingJson; +import io.fabric8.crdv2.example.person.Person; +import io.fabric8.crdv2.generator.ResolvingContext; +import io.fabric8.kubernetes.api.model.AnyType; +import io.fabric8.kubernetes.api.model.HasMetadata; +import io.fabric8.kubernetes.api.model.Quantity; +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaProps; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.ValidationRule; +import io.fabric8.kubernetes.api.model.coordination.v1.LeaseSpec; +import org.junit.jupiter.api.Test; +import org.w3c.dom.Node; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class JsonSchemaTest { + + @Test + void shouldCreateAnyTypeWithoutProperties() { + JSONSchemaProps schema = JsonSchema.from(AnyType.class); + assertSchemaHasNumberOfProperties(schema, 0); + assertTrue(schema.getXKubernetesPreserveUnknownFields()); + } + + @Test + void shouldCreateJsonSchemaFromClass() { + JSONSchemaProps schema = JsonSchema.from(Person.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 7); + final List personTypes = properties.get("type").getEnum().stream().map(JsonNode::asText) + .collect(Collectors.toList()); + assertEquals(2, personTypes.size()); + assertTrue(personTypes.contains("crazy")); + assertTrue(personTypes.contains("crazier")); + final Map addressProperties = properties.get("addresses").getItems() + .getSchema().getProperties(); + assertEquals(5, addressProperties.size()); + final List addressTypes = addressProperties.get("type").getEnum().stream() + .map(JsonNode::asText) + .collect(Collectors.toList()); + assertEquals(2, addressTypes.size()); + assertTrue(addressTypes.contains("home")); + assertTrue(addressTypes.contains("work")); + + schema = JsonSchema.from(Basic.class); + assertNotNull(schema); + properties = schema.getProperties(); + assertNotNull(properties); + assertEquals(2, properties.size()); + Map spec = properties.get("spec").getProperties(); + assertEquals("integer", spec.get("myInt").getType()); + assertEquals("integer", spec.get("myLong").getType()); + assertEquals("number", spec.get("myDouble").getType()); + assertEquals("number", spec.get("myFloat").getType()); + Map status = properties.get("status").getProperties(); + assertEquals("string", status.get("message").getType()); + } + + @Test + void shouldAugmentPropertiesSchemaFromAnnotations() throws JsonProcessingException { + JSONSchemaProps schema = JsonSchema.from(Annotated.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 20); + + // check descriptions are present + assertTrue(spec.containsKey("from-field")); + JSONSchemaProps prop = spec.get("from-field"); + assertEquals("from-field-description", prop.getDescription()); + assertTrue(spec.containsKey("from-getter")); + prop = spec.get("from-getter"); + assertEquals("from-getter-description", prop.getDescription()); + + // fields without description annotation shouldn't have them + assertTrue(spec.containsKey("unnamed")); + assertNull(spec.get("unnamed").getDescription()); + assertTrue(spec.containsKey("emptySetter")); + assertNull(spec.get("emptySetter").getDescription()); + assertTrue(spec.containsKey("anEnum")); + + Function type = t -> new JSONSchemaPropsBuilder().withType(t); + assertEquals(type.apply("integer").withMinimum(-5.0).build(), spec.get("min")); + assertEquals(type.apply("integer").withMaximum(5.0).build(), spec.get("max")); + assertEquals(type.apply("string").withPattern("\\b[1-9]\\b").build(), spec.get("singleDigit")); + assertEquals(type.apply("string").withNullable(true).build(), spec.get("nullable")); + assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value")).build(), spec.get("defaultValue")); + assertEquals(type.apply("string").withDefault(TextNode.valueOf("my-value2")).build(), spec.get("defaultValue2")); + assertEquals(type.apply("string").withEnum(TextNode.valueOf("non"), TextNode.valueOf("oui")).build(), spec.get("anEnum")); + assertEquals(type.apply("string").withFormat("date-time").withPattern("yyyy-MM-dd'T'HH:mm:ssVV").build(), + spec.get("issuedAt")); + // the shape does not influence the type in these cases + assertEquals(type.apply("string").build(), spec.get("bool")); + assertEquals(type.apply("string").build(), spec.get("num")); + assertEquals(type.apply("string").build(), spec.get("numFloat")); + assertEquals(type.apply("string").build(), spec.get("numInt")); + + // check required list, should register properties with their modified name if needed + final List required = specSchema.getRequired(); + assertEquals(3, required.size()); + assertTrue(required.contains("emptySetter")); + assertTrue(required.contains("emptySetter2")); + assertTrue(required.contains("from-getter")); + + // check ignored fields + assertFalse(spec.containsKey("ignoredFoo")); + assertFalse(spec.containsKey("ignoredBar")); + + final JSONSchemaProps k8sValidationProps = spec.get("kubernetesValidationRule"); + final List k8sValidationRulesSingle = k8sValidationProps.getXKubernetesValidations(); + assertNotNull(k8sValidationRulesSingle); + assertEquals(1, k8sValidationRulesSingle.size()); + assertEquals("self.startwith('prefix-')", k8sValidationRulesSingle.get(0).getRule()); + assertEquals("kubernetesValidationRule must start with prefix 'prefix-'", k8sValidationRulesSingle.get(0).getMessage()); + assertNull(k8sValidationRulesSingle.get(0).getMessageExpression()); + assertNull(k8sValidationRulesSingle.get(0).getReason()); + assertNull(k8sValidationRulesSingle.get(0).getFieldPath()); + assertNull(k8sValidationRulesSingle.get(0).getOptionalOldSelf()); + + final JSONSchemaProps kubernetesValidationsRepeated = spec.get("kubernetesValidationRules"); + final List kubernetesValidationsRepeatedRules = kubernetesValidationsRepeated.getXKubernetesValidations(); + assertNotNull(kubernetesValidationsRepeatedRules); + assertEquals(3, kubernetesValidationsRepeatedRules.size()); + assertEquals("first.rule", kubernetesValidationsRepeatedRules.get(0).getRule()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getFieldPath()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getReason()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getMessage()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getMessageExpression()); + assertNull(kubernetesValidationsRepeatedRules.get(0).getOptionalOldSelf()); + assertEquals("second.rule", kubernetesValidationsRepeatedRules.get(1).getRule()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getFieldPath()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getReason()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getMessage()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getMessageExpression()); + assertNull(kubernetesValidationsRepeatedRules.get(1).getOptionalOldSelf()); + } + + @Test + void shouldProduceKubernetesPreserveFields() { + JSONSchemaProps schema = new JsonSchema(ResolvingContext.defaultResolvingContext(true), ContainingJson.class).getSchema(); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 3); + + // check preserve unknown fields is present + assertTrue(spec.containsKey("free")); + JSONSchemaProps freeField = spec.get("free"); + + assertNull(freeField.getType()); + assertTrue(freeField.getXKubernetesPreserveUnknownFields()); + + assertTrue(spec.containsKey("field")); + JSONSchemaProps field = spec.get("field"); + + assertEquals("integer", field.getType()); + assertNull(field.getXKubernetesPreserveUnknownFields()); + + assertTrue(spec.containsKey("foo")); + JSONSchemaProps fooField = spec.get("foo"); + + assertEquals("object", fooField.getType()); + assertTrue(fooField.getXKubernetesPreserveUnknownFields()); + } + + @Test + void shouldExtractPropertiesSchemaFromExtractValueAnnotation() { + JSONSchemaProps schema = JsonSchema.from(Extraction.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 2); + + // check typed SchemaFrom + JSONSchemaProps foo = spec.get("foo"); + Map fooProps = foo.getProperties(); + assertNotNull(fooProps); + + // you can change everything + assertEquals("integer", fooProps.get("BAZ").getType()); + assertTrue(foo.getRequired().contains("BAZ")); + + // you can exclude fields + assertNull(fooProps.get("baz")); + + // check typed SchemaSwap + JSONSchemaProps bar = spec.get("bar"); + Map barProps = bar.getProperties(); + assertNotNull(barProps); + assertTrue(bar.getXKubernetesPreserveUnknownFields()); + + // you can change everything + assertEquals("integer", barProps.get("BAZ").getType()); + assertTrue(bar.getRequired().contains("BAZ")); + + // you can exclude fields + assertNull(barProps.get("baz")); + } + + @Test + void shouldExtractPropertiesSchemaFromSchemaSwapAnnotations() { + JSONSchemaProps schema = JsonSchema.from(MultipleSchemaSwaps.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + final JSONSchemaProps specSchema = properties.get("spec"); + Map spec = assertSchemaHasNumberOfProperties(specSchema, 4); + + // 'first' is replaced by SchemaSwap from int to string + JSONSchemaProps first = spec.get("first"); + assertPropertyHasType(first, "shouldBeString", "string"); + + // 'second' is replaced by the same SchemaSwap that is applied multiple times + JSONSchemaProps second = spec.get("second"); + assertPropertyHasType(second, "shouldBeString", "string"); + + // 'third' is replaced by another SchemaSwap + JSONSchemaProps third = spec.get("third"); + assertPropertyHasType(third, "shouldBeInt", "integer"); + + // 'fourth' is replaced by another SchemaSwap and its property deleted + JSONSchemaProps fourth = spec.get("fourth"); + Map properties4 = fourth.getProperties(); + assertNotNull(properties); + assertTrue(properties4.isEmpty()); + } + + @Test + void shouldApplySchemaSwapsMultipleTimesInDeepClassHierarchy() { + JSONSchemaProps schema = JsonSchema.from(DeeplyNestedSchemaSwaps.class); + assertNotNull(schema); + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + Map spec = assertSchemaHasNumberOfProperties(properties.get("spec"), 2); + + assertPropertyHasType(spec.get("myObject"), "shouldBeString", "string"); + Map level1 = assertSchemaHasNumberOfProperties(spec.get("level1"), 3); + + assertPropertyHasType(level1.get("myObject"), "shouldBeString", "string"); + List> levels2 = new ArrayList<>(); + levels2.add(assertSchemaHasNumberOfProperties(level1.get("level2a"), 3)); + levels2.add(assertSchemaHasNumberOfProperties(level1.get("level2b"), 3)); + + for (Map level2 : levels2) { + assertPropertyHasType(level2.get("myObject1"), "shouldBeString", "string"); + assertPropertyHasType(level2.get("myObject2"), "shouldBeString", "string"); + + Map level3 = assertSchemaHasNumberOfProperties(level2.get("level3"), 2); + assertPropertyHasType(level3.get("myObject1"), "shouldBeString", "string"); + assertPropertyHasType(level3.get("myObject2"), "shouldBeString", "string"); + } + } + + @Test + void shouldApplyCyclicSchemaSwaps() { + JSONSchemaProps schema = JsonSchema.from(CyclicSchemaSwap.class); + assertNotNull(schema); + + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + Map spec = assertSchemaHasNumberOfProperties(properties.get("spec"), 3); + + // the collection should emit a single level then terminate with void + assertNull(spec.get("roots").getItems().getSchema().getProperties().get("level").getProperties().get("level")); + + assertPropertyHasType(spec.get("myObject"), "value", "integer"); + + // the field should emit a single level then terminate with void + assertNull(spec.get("root").getProperties().get("level").getProperties().get("level")); + } + + public static class PreserveUnknown { + + @JsonIgnore + private Map values = new HashMap<>(); + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.values; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.values.put(name, value); + } + + public Map getValues() { + return values; + } + + public void setValues(Map values) { + this.values = values; + } + + } + + @Test + void testPreserveUnknown() { + JSONSchemaProps schema = new JsonSchema(ResolvingContext.defaultResolvingContext(true), PreserveUnknown.class).getSchema(); + assertNotNull(schema); + assertEquals(0, schema.getProperties().size()); + assertEquals(Boolean.TRUE, schema.getXKubernetesPreserveUnknownFields()); + } + + private static class WithDefaultAndDescription { + @JsonProperty(defaultValue = "xzy") + @JsonPropertyDescription("I'm x") + public String x; + } + + @Test + void testDefaultAndDescription() { + JSONSchemaProps schema = JsonSchema.from(WithDefaultAndDescription.class); + assertNotNull(schema); + JSONSchemaProps x = schema.getProperties().get("x"); + assertEquals("xzy", x.getDefault().textValue()); + assertEquals("I'm x", x.getDescription()); + } + + // implicitly uses AnySchema + private static class MySerializer extends StdSerializer { + + public MySerializer() { + super(Object.class); + } + + @Override + public void serialize(Object value, JsonGenerator gen, SerializerProvider provider) throws IOException { + + } + + } + + public static class AnyTestTypes { + @JsonSerialize(using = MySerializer.class) + public Object value; + public Node nodeValue; + } + + @Test + void testAnySchemaTypes() { + JSONSchemaProps schema = JsonSchema.from(AnyTestTypes.class); + assertNotNull(schema); + assertNull(schema.getProperties().get("nodeValue").getType()); + assertEquals(Boolean.TRUE, schema.getProperties().get("nodeValue").getXKubernetesPreserveUnknownFields()); + assertEquals(Boolean.TRUE, schema.getProperties().get("value").getXKubernetesPreserveUnknownFields()); + } + + @Test + void testLeaseSpec() { + JSONSchemaProps schema = JsonSchema.from(LeaseSpec.class); + assertNotNull(schema); + JSONSchemaProps renewTime = schema.getProperties().get("renewTime"); + assertEquals("string", renewTime.getType()); + assertEquals("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'", renewTime.getPattern()); + assertEquals("date-time", renewTime.getFormat()); + } + + private static class NestedHasMetadata { + + public HasMetadata embedded; + + } + + @Test + void testNestedHasMetadata() { + JSONSchemaProps schema = JsonSchema.from(NestedHasMetadata.class); + assertNotNull(schema); + JSONSchemaProps embedded = schema.getProperties().get("embedded"); + assertEquals(true, embedded.getXKubernetesEmbeddedResource()); + assertNull(embedded.getType()); + } + + @Test + void testQuantity() { + JSONSchemaProps schema = JsonSchema.from(Quantity.class); + assertNotNull(schema); + assertEquals(2, schema.getAnyOf().size()); + assertEquals(true, schema.getXKubernetesIntOrString()); + } + + @Test + void testServicePort() { + JSONSchemaProps schema = JsonSchema.from(ServicePort.class); + assertNotNull(schema); + JSONSchemaProps targetPort = schema.getProperties().get("targetPort"); + assertEquals(2, targetPort.getAnyOf().size()); + assertEquals(true, targetPort.getXKubernetesIntOrString()); + } + + private static class MapProperty { + + public Map map; + + } + + @Test + void testMapProperty() { + JSONSchemaProps schema = JsonSchema.from(MapProperty.class); + assertNotNull(schema); + JSONSchemaProps map = schema.getProperties().get("map"); + assertTrue(map.getProperties().isEmpty()); + assertEquals("boolean", map.getAdditionalProperties().getSchema().getType()); + } + + private static class Cyclic1 { + + public Cyclic1 parent; + + } + + private static class Cyclic2 { + + public Cyclic2 parent[]; + + } + + private static class Cyclic3 { + + public Map parent; + + } + + @Test + void testCyclicProperties() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(Cyclic1.class)); + assertEquals( + "Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic1.parent", + exception.getMessage()); + + exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(Cyclic2.class)); + assertEquals( + "Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic2.parent", + exception.getMessage()); + + exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(Cyclic3.class)); + assertEquals( + "Found a cyclic reference involving the field of type io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3 starting a field parent >>\n" + + "io.fabric8.crdv2.generator.v1.JsonSchemaTest$Cyclic3.parent", + exception.getMessage()); + } + + @SchemaSwap(originalType = Cyclic3.class, fieldName = "parent") + private static class Cyclic4 { + + public Cyclic3 parent; + public int value; + + } + + @Test + void testSchemaSwapZeroDepth() { + JSONSchemaProps schema = JsonSchema.from(Cyclic4.class); + assertNotNull(schema); + JSONSchemaProps parent = schema.getProperties().get("parent"); + assertTrue(parent.getProperties().isEmpty()); + } + + @Test + void shouldApplyCollectionCyclicSchemaSwaps() { + JSONSchemaProps schema = JsonSchema.from(CollectionCyclicSchemaSwap.class); + assertNotNull(schema); + + Map properties = assertSchemaHasNumberOfProperties(schema, 2); + Map spec = assertSchemaHasNumberOfProperties(properties.get("spec"), 2); + + assertPropertyHasType(spec.get("myObject"), "value", "integer"); + Map level1 = assertSchemaHasNumberOfProperties(spec.get("levels").getItems().getSchema(), 2); + + assertPropertyHasType(level1.get("myObject"), "value", "integer"); + Map level2 = assertSchemaHasNumberOfProperties(level1.get("levels").getItems().getSchema(), 2); + + assertPropertyHasType(level2.get("myObject"), "value", "integer"); + Map level3 = assertSchemaHasNumberOfProperties(level2.get("levels").getItems().getSchema(), 2); + + assertPropertyHasType(level3.get("myObject"), "value", "integer"); + // should terminate at the 3rd level with any - this is probably not quite the behavior we want + // targeting collection properties with a non-collection terminal seems problematic + JSONSchemaProps terminal = level3.get("levels"); + assertNull(terminal.getItems()); + assertTrue(terminal.getXKubernetesPreserveUnknownFields()); + assertSchemaHasNumberOfProperties(terminal, 0); + } + + @Test + void shouldThrowIfSchemaSwapHasUnmatchedField() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(IncorrectExtraction.class)); + assertEquals( + "Unmatched SchemaSwaps: @SchemaSwap(originalType=io.fabric8.crdv2.example.extraction.ExtractionSpec, fieldName=\"FOO\", targetType=io" + + ".fabric8.crdv2.example.extraction.FooExtractor) on io.fabric8.crdv2.example.extraction.IncorrectExtraction", + exception.getMessage()); + } + + @Test + void shouldThrowIfSchemaSwapHasUnmatchedClass() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(IncorrectExtraction2.class)); + assertEquals( + "Unmatched SchemaSwaps: @SchemaSwap(originalType=io.fabric8.crdv2.example.basic.BasicSpec, fieldName=\"bar\", targetType=io.fabric8.crdv2" + + ".example.extraction.FooExtractor) on io.fabric8.crdv2.example.extraction.IncorrectExtraction2", + exception.getMessage()); + } + + @Test + void shouldThrowIfSchemaSwapNested() { + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, + () -> JsonSchema.from(NestedSchemaSwap.class)); + assertEquals( + "Nested SchemaSwap: @SchemaSwap(originalType=io.fabric8.crdv2.example.extraction.NestedSchemaSwap$End, fieldName=\"value\", targetType=java.lang.Void) " + + "on io.fabric8.crdv2.example.extraction.NestedSchemaSwap$Intermediate", + exception.getMessage()); + } + + @io.fabric8.generator.annotation.ValidationRule(value = "base", messageExpression = "something", reason = "FieldValueForbidden") + private static class Base { + public String value; + } + + @io.fabric8.generator.annotation.ValidationRule(value = "parent", messageExpression = "something else", reason = "FieldValueForbidden") + private static class Parent extends Base { + + } + + @Test + void testValidationRuleHierarchy() { + JSONSchemaProps schema = JsonSchema.from(Parent.class); + assertNotNull(schema); + assertEquals(2, schema.getXKubernetesValidations().size()); + } + + private static Map assertSchemaHasNumberOfProperties(JSONSchemaProps specSchema, int expected) { + Map spec = specSchema.getProperties(); + assertEquals(expected, spec.size()); + return spec; + } + + private static void assertPropertyHasType(JSONSchemaProps spec, String name, String expectedType) { + Map properties = spec.getProperties(); + assertNotNull(properties); + JSONSchemaProps property = properties.get(name); + assertNotNull(property, "Property " + name + " should exist"); + assertEquals(expectedType, property.getType(), "Property " + name + " should have expected type"); + } +} diff --git a/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java new file mode 100644 index 00000000000..411c97cb341 --- /dev/null +++ b/crd-generator/api-v2/src/test/java/io/fabric8/crdv2/generator/v1/SpecReplicasPathTest.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crdv2.generator.v1; + +import io.fabric8.crdv2.example.webserver.WebServerWithSpec; +import io.fabric8.crdv2.example.webserver.WebServerWithStatusProperty; +import io.fabric8.crdv2.generator.ResolvingContext; +import io.fabric8.kubernetes.model.annotation.SpecReplicas; +import org.junit.jupiter.api.Test; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class SpecReplicasPathTest { + + @Test + public void shoudDetectSpecReplicasPath() throws Exception { + JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithStatusProperty.class); + Optional path = resolver.getSinglePath(SpecReplicas.class); + assertTrue(path.isPresent()); + assertEquals(".replicas", path.get()); + } + + @Test + public void shoudDetectNestedSpecReplicasPath() throws Exception { + JsonSchema resolver = new JsonSchema(ResolvingContext.defaultResolvingContext(false), WebServerWithSpec.class); + Optional path = resolver.getSinglePath(SpecReplicas.class); + assertTrue(path.isPresent()); + assertEquals(".spec.replicas", path.get()); + } +} diff --git a/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml new file mode 100644 index 00000000000..5bc075002c8 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/complexkinds.example.com-v1.yml @@ -0,0 +1,215 @@ +# +# Copyright (C) 2015 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" +metadata: + name: "complexkinds.example.com" +spec: + group: "example.com" + names: + kind: "ComplexKind" + plural: "complexkinds" + singular: "complexkind" + scope: "Namespaced" + versions: + - additionalPrinterColumns: + - jsonPath: ".status.message" + name: "MESSAGE" + priority: 0 + type: "string" + - jsonPath: ".status.state" + name: "State" + priority: 0 + type: "string" + name: "v1" + schema: + openAPIV3Schema: + properties: + spec: + properties: + actuatorPort: + type: "integer" + configMapName: + type: "string" + metricsPath: + type: "string" + metricsPort: + type: "integer" + services: + items: + properties: + metadata: + description: "The metadata of this Service" + properties: + annotations: + additionalProperties: + type: "string" + type: "object" + creationTimestamp: + type: "string" + deletionGracePeriodSeconds: + type: "integer" + deletionTimestamp: + type: "string" + finalizers: + items: + type: "string" + type: "array" + generateName: + type: "string" + generation: + type: "integer" + labels: + additionalProperties: + type: "string" + type: "object" + name: + type: "string" + namespace: + type: "string" + resourceVersion: + type: "string" + selfLink: + type: "string" + uid: + type: "string" + type: "object" + spec: + description: "The spec of this Service" + nullable: true + properties: + allocateLoadBalancerNodePorts: + type: "boolean" + clusterIP: + type: "string" + clusterIPs: + items: + type: "string" + type: "array" + externalIPs: + items: + type: "string" + type: "array" + externalName: + type: "string" + externalTrafficPolicy: + type: "string" + healthCheckNodePort: + type: "integer" + internalTrafficPolicy: + type: "string" + ipFamilies: + items: + type: "string" + type: "array" + ipFamilyPolicy: + type: "string" + loadBalancerClass: + type: "string" + loadBalancerIP: + type: "string" + loadBalancerSourceRanges: + items: + type: "string" + type: "array" + publishNotReadyAddresses: + type: "boolean" + selector: + additionalProperties: + type: "string" + type: "object" + sessionAffinity: + type: "string" + type: + type: "string" + type: "object" + type: "object" + type: "array" + statefulSet: + properties: + metadata: + description: "The metadata of this StatefulSet" + properties: + annotations: + additionalProperties: + type: "string" + type: "object" + creationTimestamp: + type: "string" + deletionGracePeriodSeconds: + type: "integer" + deletionTimestamp: + type: "string" + finalizers: + items: + type: "string" + type: "array" + generateName: + type: "string" + generation: + type: "integer" + labels: + additionalProperties: + type: "string" + type: "object" + name: + type: "string" + namespace: + type: "string" + resourceVersion: + type: "string" + selfLink: + type: "string" + uid: + type: "string" + type: "object" + spec: + description: "The spec of this StatefulSet" + properties: + minReadySeconds: + type: "integer" + podManagementPolicy: + type: "string" + replicas: + type: "integer" + revisionHistoryLimit: + type: "integer" + serviceName: + type: "string" + type: "object" + type: "object" + type: "object" + status: + properties: + message: + type: "string" + state: + enum: + - "CREATED" + - "ERROR" + - "ROLLING_UPDATE" + - "RUNNING" + - "SCALING" + - "STARTING" + type: "string" + type: "object" + type: "object" + served: true + storage: true + subresources: + status: {} diff --git a/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml new file mode 100644 index 00000000000..39cac710b64 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/k8svalidations.samples.fabric8.io-v1.yml @@ -0,0 +1,161 @@ +# +# Copyright (C) 2015 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" +metadata: + name: "k8svalidations.samples.fabric8.io" +spec: + group: "samples.fabric8.io" + names: + kind: "K8sValidation" + plural: "k8svalidations" + singular: "k8svalidation" + scope: "Cluster" + versions: + - name: "v1alpha1" + schema: + openAPIV3Schema: + properties: + spec: + properties: + deepLevel1: + properties: + deepLevel2: + properties: + simple: + type: "string" + x-kubernetes-validations: + - rule: "self.startsWith('deep-')" + valueL2: + type: "string" + required: + - "valueL2" + type: "object" + valueL1: + type: "string" + required: + - "deepLevel2" + - "valueL1" + type: "object" + x-kubernetes-validations: + - messageExpression: "'valueL1 (' + self.valueL1 + ') must be equal\ + \ to deepLevel2.valueL2 (' + self.deepLevel2.valueL2 + ')'" + rule: "self.valueL1 == self.deepLevel2.valueL2" + immutable: + type: "string" + x-kubernetes-validations: + - message: "cannot be changed once set" + rule: "self == oldSelf" + maxReplicas: + type: "integer" + minReplicas: + type: "integer" + monotonicCounter: + type: "integer" + x-kubernetes-validations: + - message: "cannot decrease value once set" + reason: "FieldValueForbidden" + rule: "self >= oldSelf" + multiple: + type: "string" + x-kubernetes-validations: + - rule: "self.startsWith('start-')" + - rule: "self.endsWith('-end')" + namePrefix: + type: "string" + onAbstractClass: + properties: + dummy: + type: "string" + required: + - "dummy" + type: "object" + x-kubernetes-validations: + - rule: "self.dummy.startsWith('abstract-')" + onAttributeAndClass: + properties: + dummy: + type: "string" + required: + - "dummy" + type: "object" + x-kubernetes-validations: + - rule: "self.dummy.startsWith('on-class-')" + - rule: "self.dummy.startsWith('on-attr-')" + onAttributeAndGetter: + type: "string" + x-kubernetes-validations: + - rule: "self.startsWith('start-')" + - rule: "self.endsWith('-end')" + onGetter: + type: "string" + x-kubernetes-validations: + - rule: "self.startsWith('on-getter-')" + priority: + enum: + - "high" + - "low" + - "medium" + type: "string" + x-kubernetes-validations: + - message: "cannot transition directly between 'low' and 'high'" + rule: "!(self == 'high' && oldSelf == 'low') && !(self == 'low'\ + \ && oldSelf == 'high')" + replicas: + type: "integer" + simple: + type: "string" + x-kubernetes-validations: + - rule: "self.startsWith('simple-')" + required: + - "deepLevel1" + - "maxReplicas" + - "minReplicas" + - "multiple" + - "namePrefix" + - "onAbstractClass" + - "onAttributeAndClass" + - "onAttributeAndGetter" + - "onGetter" + - "priority" + - "replicas" + - "simple" + type: "object" + x-kubernetes-validations: + - fieldPath: ".replicas" + rule: "self.minReplicas <= self.replicas && self.replicas <= self.maxReplicas" + - message: "replicas must be greater than or equal to minReplicas" + rule: "self.minReplicas <= self.replicas" + - message: "replicas must be smaller than or equal to maxReplicas" + rule: "self.replicas <= self.maxReplicas" + status: + properties: + availableReplicas: + type: "integer" + type: "object" + type: "object" + x-kubernetes-validations: + - messageExpression: "'name must start with ' + self.spec.namePrefix" + reason: "FieldValueForbidden" + rule: "self.metadata.name.startsWith(self.spec.namePrefix)" + - message: "updates not allowed in degraded state" + rule: "self.status.availableReplicas >= self.spec.minReplicas" + served: true + storage: true + subresources: + status: {} diff --git a/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml new file mode 100644 index 00000000000..f083aa0d6f6 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/multiples.sample.fabric8.io-v1.yml @@ -0,0 +1,57 @@ +# +# Copyright (C) 2015 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Generated by Fabric8 CRDGenerator, manual edits might get overwritten! +apiVersion: "apiextensions.k8s.io/v1" +kind: "CustomResourceDefinition" +metadata: + name: "multiples.sample.fabric8.io" +spec: + group: "sample.fabric8.io" + names: + kind: "Multiple" + plural: "multiples" + singular: "multiple" + scope: "Cluster" + versions: + - name: "v2" + schema: + openAPIV3Schema: + properties: + spec: + properties: + v2: + type: "string" + type: "object" + status: + type: "object" + type: "object" + served: true + storage: true + - name: "v1" + schema: + openAPIV3Schema: + properties: + spec: + properties: + v1: + type: "string" + type: "object" + status: + type: "object" + type: "object" + served: true + storage: false diff --git a/crd-generator/api-v2/src/test/resources/simplelogger.properties b/crd-generator/api-v2/src/test/resources/simplelogger.properties new file mode 100644 index 00000000000..88095757cf8 --- /dev/null +++ b/crd-generator/api-v2/src/test/resources/simplelogger.properties @@ -0,0 +1,43 @@ +# +# Copyright (C) 2015 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# SLF4J's SimpleLogger configuration file +# Simple implementation of Logger that sends all enabled log messages, for all defined loggers, to System.err. +# Default logging detail level for all instances of SimpleLogger. +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, defaults to "info". +org.slf4j.simpleLogger.defaultLogLevel=debug +# Logging detail level for a SimpleLogger instance named "xxxxx". +# Must be one of ("trace", "debug", "info", "warn", or "error"). +# If not specified, the default logging detail level is used. +#org.slf4j.simpleLogger.log.xxxxx= +# Set to true if you want the current date and time to be included in output messages. +# Default is false, and will output the number of milliseconds elapsed since startup. +#org.slf4j.simpleLogger.showDateTime=false +# The date and time format to be used in the output messages. +# The pattern describing the date and time format is the same that is used in java.text.SimpleDateFormat. +# If the format is not specified or is invalid, the default format is used. +# The default format is yyyy-MM-dd HH:mm:ss:SSS Z. +#org.slf4j.simpleLogger.dateTimeFormat=yyyy-MM-dd HH:mm:ss:SSS Z +# Set to true if you want to output the current thread name. +# Defaults to true. +#org.slf4j.simpleLogger.showThreadName=true +# Set to true if you want the Logger instance name to be included in output messages. +# Defaults to true. +org.slf4j.simpleLogger.showLogName=false +# Set to true if you want the last component of the name to be included in output messages. +# Defaults to false. +#org.slf4j.simpleLogger.showShortLogName=false diff --git a/crd-generator/pom.xml b/crd-generator/pom.xml index c86434e7c0f..c1059dddbc0 100644 --- a/crd-generator/pom.xml +++ b/crd-generator/pom.xml @@ -33,17 +33,7 @@ apt api + api-v2 + test - - - - tests - - (,17] - - - test - - - diff --git a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java index 5992ca2194a..0d9061925e9 100644 --- a/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java +++ b/kubernetes-client-api/src/main/java/io/fabric8/kubernetes/client/CustomResource.java @@ -118,22 +118,22 @@ public CustomResource() { this.status = initStatus(); } - public static boolean getServed(Class clazz) { + public static boolean getServed(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation == null || annotation.served(); } - public static boolean getStorage(Class clazz) { + public static boolean getStorage(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation == null || annotation.storage(); } - public static boolean getDeprecated(Class clazz) { + public static boolean getDeprecated(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation == null || annotation.deprecated(); } - public static String getDeprecationWarning(Class clazz) { + public static String getDeprecationWarning(Class clazz) { final Version annotation = clazz.getAnnotation(Version.class); return annotation != null && Utils.isNotNullOrEmpty(annotation.deprecationWarning()) ? annotation.deprecationWarning() @@ -255,7 +255,7 @@ public String getCRDName() { * @param clazz the CustomResource class which short names we want to retrieve * @return the short names associated with this CustomResource or an empty array if none was provided */ - public static String[] getShortNames(Class clazz) { + public static String[] getShortNames(Class clazz) { return Optional.ofNullable(clazz.getAnnotation(ShortNames.class)) .map(ShortNames::value) .orElse(new String[] {}); diff --git a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java index 8f13b7000ce..ad6ff66d1c5 100644 --- a/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java +++ b/kubernetes-model-generator/kubernetes-model-core/src/main/java/io/fabric8/kubernetes/api/model/Duration.java @@ -18,10 +18,13 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.JavaType; +import com.fasterxml.jackson.databind.JsonMappingException; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatVisitorWrapper; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; import lombok.EqualsAndHashCode; import lombok.ToString; @@ -178,12 +181,21 @@ public static Duration parse(String duration) throws ParseException { return new Duration(accumulator); } - public static class Serializer extends JsonSerializer { + public static class Serializer extends StdSerializer { + + public Serializer() { + super(Duration.class); + } @Override public void serialize(Duration duration, JsonGenerator jgen, SerializerProvider provider) throws IOException { jgen.writeString(String.format("%sns", duration.getValue())); } + + @Override + public void acceptJsonFormatVisitor(JsonFormatVisitorWrapper visitor, JavaType typeHint) throws JsonMappingException { + visitor.expectStringFormat(typeHint); + } } private enum TimeUnits { diff --git a/pom.xml b/pom.xml index 7daa1e6e824..a7fdf724e4e 100644 --- a/pom.xml +++ b/pom.xml @@ -254,6 +254,11 @@ crd-generator-api ${project.version} + + io.fabric8 + crd-generator-api-v2 + ${project.version} + io.fabric8 kubernetes-log4j