diff --git a/CHANGELOG.md b/CHANGELOG.md index 1961a2b4cec..116c99e3c69 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ #### Improvements * Fix #5368: added support for additional ListOptions fields +* Fix #5388: [crd-generator] Generate deterministic CRDs #### Dependency Upgrade * Fix #5373: Gradle base API based on v8.2.1 diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractCustomResourceHandler.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractCustomResourceHandler.java index 60a7cb0e934..1130cea86ba 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractCustomResourceHandler.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractCustomResourceHandler.java @@ -159,7 +159,7 @@ private TypeDef visitTypeDefInParallel(TypeDef def, List * @param format the format of the printer column * @return the concrete decorator implementing the addition of a printer column to the currently built CRD */ - protected abstract Decorator getPrinterColumnDecorator(String name, String version, String path, + protected abstract Decorator getPrinterColumnDecorator(String name, String version, String path, String type, String column, String description, String format, int priority); /** diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java index e0b54dfacf2..0794ba40bb7 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/AbstractJsonSchema.java @@ -213,7 +213,7 @@ private static ClassRef extractClassRef(Object type) { if (type instanceof ClassRef) { return (ClassRef) type; } else if (type instanceof Class) { - return Types.typeDefFrom((Class) type).toReference(); + return Types.typeDefFrom((Class) type).toReference(); } else { throw new IllegalArgumentException("Unmanaged type passed to the annotation " + type); } @@ -564,7 +564,7 @@ public Property process() { String finalName = renamedTo != null ? renamedTo : original.getName(); return new Property(original.getAnnotations(), typeRef, finalName, - original.getComments(), original.getModifiers(), original.getAttributes()); + original.getComments(), false, false, original.getModifiers(), original.getAttributes()); } } @@ -653,6 +653,7 @@ private T internalFromImpl(String name, TypeRef typeRef, Set visited, In // Note that ordering of the checks here is meaningful: we need to check for complex types last // in case some "complex" types are handled specifically if (typeRef.getDimensions() > 0 || io.sundr.model.utils.Collections.isCollection(typeRef)) { // Handle Collections & Arrays + //noinspection unchecked final TypeRef collectionType = TypeAs.combine(TypeAs.UNWRAP_ARRAY_OF, TypeAs.UNWRAP_COLLECTION_OF) .apply(typeRef); final T schema = internalFromImpl(name, collectionType, visited, schemaSwaps); @@ -695,6 +696,7 @@ private T internalFromImpl(String name, TypeRef typeRef, Set visited, In .filter(Property::isEnumConstant) .map(this::extractUpdatedNameFromJacksonPropertyIfPresent) .filter(Objects::nonNull) + .sorted() .map(JsonNodeFactory.instance::textNode) .toArray(JsonNode[]::new); return enumProperty(enumValues); diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java index 25b1aa788d0..5020da10802 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/CRDGenerator.java @@ -15,11 +15,12 @@ */ package io.fabric8.crd.generator; +import com.fasterxml.jackson.databind.MapperFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; -import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature; import io.fabric8.crd.generator.utils.Types; import io.fabric8.crd.generator.v1.CustomResourceHandler; import io.fabric8.kubernetes.api.model.HasMetadata; @@ -28,11 +29,26 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.*; +import java.io.Closeable; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.net.URI; -import java.util.*; +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.stream.Stream; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY; +import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL; +import static com.fasterxml.jackson.annotation.JsonInclude.Value.construct; + public class CRDGenerator { private static final Logger LOGGER = LoggerFactory.getLogger(CRDGenerator.class); @@ -42,17 +58,16 @@ public class CRDGenerator { private boolean parallel; private Map infos; - private static final ObjectMapper YAML_MAPPER = new ObjectMapper( - new YAMLFactory() - .enable(Feature.MINIMIZE_QUOTES) - .enable(Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS) - .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)); - - static { - YAML_MAPPER.configure(SerializationFeature.INDENT_OUTPUT, true); - YAML_MAPPER.configure(SerializationFeature.WRITE_NULL_MAP_VALUES, false); - YAML_MAPPER.configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, false); - } + private static final ObjectMapper YAML_MAPPER = JsonMapper.builder(new YAMLFactory() + .enable(YAMLGenerator.Feature.MINIMIZE_QUOTES) + .enable(YAMLGenerator.Feature.ALWAYS_QUOTE_NUMBERS_AS_STRINGS) + .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER)) + .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) + .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) + .configure(SerializationFeature.INDENT_OUTPUT, true) + .withConfigOverride(Map.class, configOverride -> configOverride.setInclude(construct(NON_NULL, NON_NULL))) + .serializationInclusion(NON_EMPTY) + .build(); public CRDGenerator() { resources = new Resources(); @@ -105,7 +120,10 @@ Map getHandlers() { return handlers; } - public CRDGenerator customResourceClasses(Class... crClasses) { + // 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)); } diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/CustomResourceInfo.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/CustomResourceInfo.java index 6535139ed41..ee0c326d076 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/CustomResourceInfo.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/CustomResourceInfo.java @@ -45,8 +45,8 @@ public class CustomResourceInfo { private final Scope scope; private final TypeDef definition; private final String crClassName; - private final Optional specClassName; - private final Optional statusClassName; + private final String specClassName; + private final String statusClassName; private final String id; private final int hash; @@ -68,8 +68,8 @@ public CustomResourceInfo(String group, String version, String kind, String sing this.scope = scope; this.definition = definition; this.crClassName = crClassName; - this.specClassName = Optional.ofNullable(specClassName); - this.statusClassName = Optional.ofNullable(statusClassName); + this.specClassName = specClassName; + this.statusClassName = statusClassName; this.id = crdName() + "/" + version; this.hash = id.hashCode(); this.annotations = annotations; @@ -125,11 +125,11 @@ public String crClassName() { } public Optional specClassName() { - return specClassName; + return Optional.ofNullable(specClassName); } public Optional statusClassName() { - return statusClassName; + return Optional.ofNullable(statusClassName); } public TypeDef definition() { @@ -144,9 +144,9 @@ public String[] labels() { return labels; } - public static CustomResourceInfo fromClass(Class customResource) { + public static CustomResourceInfo fromClass(Class> customResource) { try { - final CustomResource instance = customResource.getDeclaredConstructor().newInstance(); + final CustomResource instance = customResource.getDeclaredConstructor().newInstance(); final String[] shortNames = CustomResource.getShortNames(customResource); diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/InternalSchemaSwaps.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/InternalSchemaSwaps.java index 49fc4bef1b0..8527ddb5af9 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/InternalSchemaSwaps.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/InternalSchemaSwaps.java @@ -50,8 +50,7 @@ public InternalSchemaSwaps branchDepths() { public InternalSchemaSwaps branchAnnotations() { Map combined = new HashMap<>(swaps); combined.putAll(parentSwaps); - InternalSchemaSwaps result = new InternalSchemaSwaps(new HashMap<>(), this.swapDepths, combined); - return result; + return new InternalSchemaSwaps(new HashMap<>(), this.swapDepths, combined); } public void registerSwap(ClassRef definitionType, ClassRef originalType, String fieldName, ClassRef targetType, diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/CustomResourceHandler.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/CustomResourceHandler.java index be211f1dc76..a16af18cd85 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/CustomResourceHandler.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1/CustomResourceHandler.java @@ -45,7 +45,7 @@ public CustomResourceHandler(Resources resources, boolean parallel) { } @Override - protected Decorator getPrinterColumnDecorator(String name, + protected Decorator getPrinterColumnDecorator(String name, String version, String path, String type, String column, String description, String format, int priority) { return new AddAdditionPrinterColumnDecorator(name, version, type, column, path, format, diff --git a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/CustomResourceHandler.java b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/CustomResourceHandler.java index 9a660f86647..a257d732b59 100644 --- a/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/CustomResourceHandler.java +++ b/crd-generator/api/src/main/java/io/fabric8/crd/generator/v1beta1/CustomResourceHandler.java @@ -45,7 +45,7 @@ public CustomResourceHandler(Resources resources, boolean parallel) { } @Override - protected Decorator getPrinterColumnDecorator( + protected Decorator getPrinterColumnDecorator( String name, String version, String path, String type, String column, String description, String format, int priority) { return new AddAdditionPrinterColumnDecorator(name, version, type, column, path, format, diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/Complex.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/Complex.java new file mode 100644 index 00000000000..f299e5cfdcb --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.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/src/test/java/io/fabric8/crd/example/complex/ComplexSpec.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/ComplexSpec.java new file mode 100644 index 00000000000..776e05e25c1 --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.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/src/test/java/io/fabric8/crd/example/complex/ComplexStatus.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/ComplexStatus.java new file mode 100644 index 00000000000..30f9baa2daa --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.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(name = "Message") + 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/src/test/java/io/fabric8/crd/example/complex/ServiceConfiguration.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/ServiceConfiguration.java new file mode 100644 index 00000000000..211ce6da93b --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.example.complex; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import io.fabric8.crd.example.complex.k8s.ObjectMeta; +import io.fabric8.crd.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/src/test/java/io/fabric8/crd/example/complex/StatefulSetConfiguration.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/StatefulSetConfiguration.java new file mode 100644 index 00000000000..f918af183b2 --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.example.complex; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import io.fabric8.crd.example.complex.k8s.ObjectMeta; +import io.fabric8.crd.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/src/test/java/io/fabric8/crd/example/complex/k8s/ObjectMeta.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/k8s/ObjectMeta.java new file mode 100644 index 00000000000..ed426376c14 --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.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/src/test/java/io/fabric8/crd/example/complex/k8s/ServiceSpec.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/k8s/ServiceSpec.java new file mode 100644 index 00000000000..399fa9939d4 --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.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/src/test/java/io/fabric8/crd/example/complex/k8s/StatefulSetSpec.java b/crd-generator/api/src/test/java/io/fabric8/crd/example/complex/k8s/StatefulSetSpec.java new file mode 100644 index 00000000000..f6d2a97172f --- /dev/null +++ b/crd-generator/api/src/test/java/io/fabric8/crd/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.crd.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/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java index 216967a623b..c4459afcacc 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CRDGeneratorTest.java @@ -18,9 +18,14 @@ import io.fabric8.crd.example.basic.Basic; import io.fabric8.crd.example.basic.BasicSpec; import io.fabric8.crd.example.basic.BasicStatus; +import io.fabric8.crd.example.complex.Complex; import io.fabric8.crd.example.cyclic.Cyclic; import io.fabric8.crd.example.cyclic.CyclicList; -import io.fabric8.crd.example.inherited.*; +import io.fabric8.crd.example.inherited.BaseSpec; +import io.fabric8.crd.example.inherited.BaseStatus; +import io.fabric8.crd.example.inherited.Child; +import io.fabric8.crd.example.inherited.ChildSpec; +import io.fabric8.crd.example.inherited.ChildStatus; import io.fabric8.crd.example.joke.Joke; import io.fabric8.crd.example.joke.JokeRequest; import io.fabric8.crd.example.joke.JokeRequestSpec; @@ -35,7 +40,13 @@ import io.fabric8.crd.example.simplest.SimplestStatus; import io.fabric8.crd.generator.CRDGenerator.AbstractCRDOutput; import io.fabric8.crd.generator.utils.Types; -import io.fabric8.kubernetes.api.model.apiextensions.v1.*; +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; @@ -44,16 +55,27 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileReader; import java.io.IOException; +import java.net.URL; +import java.nio.file.Files; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import static org.junit.jupiter.api.Assertions.*; +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; +import static org.junit.jupiter.api.Assertions.fail; class CRDGeneratorTest { @@ -138,9 +160,13 @@ void shouldProperlyRecordNumberOfGeneratedCRDs() { assertEquals(0, generator.generate()); assertEquals(0, generator.detailedGenerate().numberOfGeneratedCRDs()); + final List versions = new ArrayList<>(2); + versions.add("v1"); + versions.add("v1beta1"); + final CRDGenerationInfo info = generator .customResourceClasses(Simplest.class, Child.class, Joke.class, JokeRequest.class) - .forCRDVersions("v1", "v1beta1") + .forCRDVersions(versions) .withOutput(output).detailedGenerate(); assertEquals(4 * 2, info.numberOfGeneratedCRDs()); @@ -184,8 +210,8 @@ void shouldProperlyGenerateMultipleVersionsOfCRDs() { CustomResourceDefinitionSpec spec = definition.getSpec(); final List versions = spec.getVersions(); assertEquals(2, versions.size()); - assertTrue(versions.stream().filter(v -> v.getName().equals("v1")).count() == 1); - assertTrue(versions.stream().filter(v -> v.getName().equals("v2")).count() == 1); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v1")).count()); + assertEquals(1, versions.stream().filter(v -> v.getName().equals("v2")).count()); Class[] mustContainTraversedClasses = { Multiple.class, MultipleSpec.class, io.fabric8.crd.example.multiple.v2.Multiple.class, io.fabric8.crd.example.multiple.v2.MultipleSpec.class }; @@ -214,7 +240,7 @@ void generatingACycleShouldFail() { assertThrows( IllegalArgumentException.class, - () -> generator.detailedGenerate(), + generator::detailedGenerate, "An IllegalArgument Exception hasn't been thrown when generating a CRD with cyclic references"); } @@ -232,7 +258,7 @@ void generatingACycleInListShouldFail() { assertThrows( IllegalArgumentException.class, - () -> generator.detailedGenerate(), + generator::detailedGenerate, "An IllegalArgument Exception hasn't been thrown when generating a CRD with cyclic references"); } @@ -404,6 +430,52 @@ void checkCRDGenerator() { }); } + @Test + void checkGenerationIsDeterministic() throws Exception { + // generated CRD + final File outputDir = Files.createTempDirectory("crd-").toFile(); + final CustomResourceInfo info = CustomResourceInfo.fromClass(Complex.class); + final CRDGenerationInfo crdInfo = newCRDGenerator().inOutputDir(outputDir).forCRDVersions(info.version()) + .customResources(info).customResourceClasses(Complex.class).detailedGenerate(); + final File crdFile = new File(crdInfo.getCRDInfos(info.crdName()).get(info.version()).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 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 String skipCommentsAndEmptyLines(final BufferedReader reader) throws IOException { + String line = reader.readLine(); + while (line.startsWith("#") || line.isEmpty()) { + line = reader.readLine(); + } + return line; + } + private CustomResourceDefinitionVersion checkCRD(Class> customResource, String kind, String plural, Scope scope, Class... traversedClasses) { @@ -474,7 +546,7 @@ private static class TestCRDOutput extends AbstractCRDOutput infos = new ConcurrentHashMap<>(); @Override - protected ByteArrayOutputStream createStreamFor(String crdName) throws IOException { + protected ByteArrayOutputStream createStreamFor(String crdName) { return new ByteArrayOutputStream(); } diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CustomResourceInfoTest.java b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CustomResourceInfoTest.java index e10119984cd..1c43dbabe2b 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/generator/CustomResourceInfoTest.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/generator/CustomResourceInfoTest.java @@ -31,11 +31,9 @@ public class CustomResourceInfoTest { public static class Spec { - } public static class Status { - } private static final String GROUP = "sample.fabric8.io"; @@ -45,13 +43,11 @@ public static class Status { @Version(VERSION) @ShortNames("s") public static class ClusteredCR extends CustomResource { - } @Group(GROUP) @Version(VERSION) public static class NamespacedCR extends CustomResource implements Namespaced { - } @Test @@ -68,12 +64,13 @@ void shouldBeProperlyScoped() { } @Test - void shouldProperlyCreateCustomResourceInfo() throws Exception { + 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.getCanonicalName(), specClassName); // todo: check that we can load and instantiate class from the returned class name @@ -82,6 +79,7 @@ void shouldProperlyCreateCustomResourceInfo() throws Exception { * Object o = specClass.getDeclaredConstructor().newInstance(); * assertNotNull(o); */ + assertTrue(info.statusClassName().isPresent()); assertEquals(Status.class.getCanonicalName(), info.statusClassName().get()); assertEquals(HasMetadata.getSingular(ClusteredCR.class), info.singular()); assertEquals(HasMetadata.getPlural(ClusteredCR.class), info.plural()); diff --git a/crd-generator/api/src/test/java/io/fabric8/crd/generator/ResourcesTest.java b/crd-generator/api/src/test/java/io/fabric8/crd/generator/ResourcesTest.java index 2bd2042ea9c..b28ef9a8c70 100644 --- a/crd-generator/api/src/test/java/io/fabric8/crd/generator/ResourcesTest.java +++ b/crd-generator/api/src/test/java/io/fabric8/crd/generator/ResourcesTest.java @@ -42,14 +42,13 @@ public void shouldSupportMultiplePrinterColumns() { } @Test - public void shouldSupportMultipleSortPrinterColums() { + public void shouldSupportMultipleSortPrinterColumns() { Resources r = new Resources(); SortPrinterColumnsDecorator dec1 = new SortPrinterColumnsDecorator("my-crd", "v1"); SortPrinterColumnsDecorator dec2 = new SortPrinterColumnsDecorator("my-crd", "v2"); r.decorate(dec1); r.decorate(dec2); assertEquals(2, r.getDecorators().size()); - r.getDecorators().stream().forEach(d -> System.out.println(d)); assertTrue(r.getDecorators().contains(dec1)); assertTrue(r.getDecorators().contains(dec2)); } diff --git a/crd-generator/api/src/test/resources/complexkinds.example.com-v1.yml b/crd-generator/api/src/test/resources/complexkinds.example.com-v1.yml new file mode 100644 index 00000000000..a1b7ed14ba5 --- /dev/null +++ b/crd-generator/api/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: {}