Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix #5388: Generate deterministic CRDs #5390

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ private TypeDef visitTypeDefInParallel(TypeDef def, List<Visitor<TypeDefBuilder>
* @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);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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());
}
}

Expand Down Expand Up @@ -653,6 +653,7 @@ private T internalFromImpl(String name, TypeRef typeRef, Set<String> 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);
Expand Down Expand Up @@ -695,6 +696,7 @@ private T internalFromImpl(String name, TypeRef typeRef, Set<String> visited, In
.filter(Property::isEnumConstant)
.map(this::extractUpdatedNameFromJacksonPropertyIfPresent)
.filter(Objects::nonNull)
.sorted()
.map(JsonNodeFactory.instance::textNode)
.toArray(JsonNode[]::new);
return enumProperty(enumValues);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -42,17 +58,16 @@ public class CRDGenerator {
private boolean parallel;
private Map<String, CustomResourceInfo> 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();
Expand Down Expand Up @@ -105,7 +120,10 @@ Map<String, AbstractCustomResourceHandler> getHandlers() {
return handlers;
}

public CRDGenerator customResourceClasses(Class<? extends CustomResource>... crClasses) {
// this is public API, so we cannot change the signature, so there is no way to prevent the possible heap pollution
Donnerbart marked this conversation as resolved.
Show resolved Hide resolved
// (we also cannot use @SafeVarargs, because that requires the method to be final, which is another signature change)
@SuppressWarnings("unchecked")
public final CRDGenerator customResourceClasses(Class<? extends CustomResource<?, ?>>... crClasses) {
return customResources(Stream.of(crClasses).map(CustomResourceInfo::fromClass).toArray(CustomResourceInfo[]::new));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ public class CustomResourceInfo {
private final Scope scope;
private final TypeDef definition;
private final String crClassName;
private final Optional<String> specClassName;
private final Optional<String> statusClassName;
private final String specClassName;
private final String statusClassName;
private final String id;
private final int hash;

Expand All @@ -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;
Expand Down Expand Up @@ -125,11 +125,11 @@ public String crClassName() {
}

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

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

public TypeDef definition() {
Expand All @@ -144,9 +144,9 @@ public String[] labels() {
return labels;
}

public static CustomResourceInfo fromClass(Class<? extends CustomResource> customResource) {
public static CustomResourceInfo fromClass(Class<? extends CustomResource<?, ?>> customResource) {
try {
final CustomResource instance = customResource.getDeclaredConstructor().newInstance();
final CustomResource<?, ?> instance = customResource.getDeclaredConstructor().newInstance();

final String[] shortNames = CustomResource.getShortNames(customResource);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@ public InternalSchemaSwaps branchDepths() {
public InternalSchemaSwaps branchAnnotations() {
Map<Key, Value> 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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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<ComplexSpec, ComplexStatus> implements Namespaced {
}
Original file line number Diff line number Diff line change
@@ -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<ServiceConfiguration> 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<ServiceConfiguration> getServices() {
return services;
}

public void setServices(List<ServiceConfiguration> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading