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

Add support for AdditionalPrinterColumn #10

Closed
wants to merge 1 commit into from
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,10 @@
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 io.fabric8.crdv2.generator.AnnotationUtils.consumeRepeatingAnnotation;
import static java.util.Optional.ofNullable;

/**
Expand Down Expand Up @@ -148,17 +148,6 @@ private T resolveRoot(Class<?> definition) {
resolvingContext.objectMapper.getSerializationConfig().constructType(definition), schema, null);
}

/**
* Walks up the class hierarchy to consume the repeating annotation
*/
private static <A extends Annotation> void consumeRepeatingAnnotation(Class<?> beanClass, Class<A> annotation,
Consumer<A> consumer) {
while (beanClass != null && beanClass != Object.class) {
Stream.of(beanClass.getAnnotationsByType(annotation)).forEach(consumer);
beanClass = beanClass.getSuperclass();
}
}

Optional<Field> getFieldForMethod(BeanProperty beanProperty) {
AnnotatedElement annotated = beanProperty.getMember().getAnnotated();
if (annotated instanceof Method) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package io.fabric8.crdv2.generator;

import java.lang.annotation.Annotation;
import java.util.LinkedList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;

public class AnnotationUtils {
private AnnotationUtils() {
throw new IllegalStateException("Utility class");
}

/**
* Walks up the class hierarchy to consume the repeating annotation
*/
public static <A extends Annotation> void consumeRepeatingAnnotation(
Class<?> beanClass,
Class<A> annotation,
Consumer<A> consumer) {

while (beanClass != null && beanClass != Object.class) {
Stream.of(beanClass.getAnnotationsByType(annotation)).forEach(consumer);
beanClass = beanClass.getSuperclass();
}
}

public static <A extends Annotation> List<A> findRepeatingAnnotations(Class<?> clazz, Class<A> annotation) {
List<A> list = new LinkedList<>();
consumeRepeatingAnnotation(clazz, annotation, list::add);
return list;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,28 @@
*/
package io.fabric8.crdv2.generator.v1;

import io.fabric8.crd.generator.annotation.AdditionalPrinterColumn;
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.CustomResourceColumnDefinition;
import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceColumnDefinitionBuilder;
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.Collection;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
Expand All @@ -42,43 +47,34 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.fabric8.crdv2.generator.AnnotationUtils.findRepeatingAnnotations;
import static io.fabric8.kubernetes.client.utils.Utils.emptyToNull;

public class CustomResourceHandler extends AbstractCustomResourceHandler {

private Queue<Map.Entry<CustomResourceDefinition, Set<String>>> 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();
public void handle(CustomResourceInfo crInfo, ResolvingContext resolvingContext) {
final String name = crInfo.crdName();
final String version = crInfo.version();

JsonSchema resolver = new JsonSchema(resolvingContext, config.definition());
JsonSchema resolver = new JsonSchema(resolvingContext, crInfo.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())
.withStorage(crInfo.storage())
.withServed(crInfo.served())
.withDeprecated(crInfo.deprecated() ? true : null)
.withDeprecationWarning(crInfo.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();
}
});
builder.addAllToAdditionalPrinterColumns(findAllPrinterColumns(resolver, crInfo));

resolver.getSinglePath(SpecReplicas.class).ifPresent(path -> {
builder.editOrNewSubresources().editOrNewScale().withSpecReplicasPath(path).endScale().endSubresources();
Expand All @@ -92,24 +88,24 @@ public void addPrinterColumn(String path, String column, String format, int prio
builder.editOrNewSubresources().editOrNewScale().withLabelSelectorPath(path).endScale().endSubresources();
});

if (config.statusClassName().isPresent()) {
if (crInfo.statusClassName().isPresent()) {
builder.editOrNewSubresources().withNewStatus().endStatus().endSubresources();
}

CustomResourceDefinition crd = new CustomResourceDefinitionBuilder()
.withNewMetadata()
.withName(name)
.withAnnotations(CRDUtils.toMap(config.annotations()))
.withLabels(CRDUtils.toMap(config.labels()))
.withAnnotations(CRDUtils.toMap(crInfo.annotations()))
.withLabels(CRDUtils.toMap(crInfo.labels()))
.endMetadata()
.withNewSpec()
.withScope(config.scope().value())
.withGroup(config.group())
.withScope(crInfo.scope().value())
.withGroup(crInfo.group())
.withNewNames()
.withKind(config.kind())
.withShortNames(config.shortNames())
.withPlural(config.plural())
.withSingular(config.singular())
.withKind(crInfo.kind())
.withShortNames(crInfo.shortNames())
.withPlural(crInfo.plural())
.withSingular(crInfo.singular())
.endNames()
.addToVersions(builder.build())
.endSpec()
Expand Down Expand Up @@ -156,4 +152,53 @@ private Map.Entry<CustomResourceDefinition, Set<String>> combine(
allDependentClasses);
}

private Collection<CustomResourceColumnDefinition> findAllPrinterColumns(
JsonSchema resolver, CustomResourceInfo crInfo) {

return Stream.of(findPrinterColumns(resolver), findPrinterColumns(crInfo))
.flatMap(Collection::stream)
.sorted(Comparator.comparing(CustomResourceColumnDefinition::getJsonPath))
.collect(Collectors.toList());
}

/**
* Find top level printer columns
*
* @param crInfo the details about the custom resource
* @return printer columns
*/
private Collection<CustomResourceColumnDefinition> findPrinterColumns(CustomResourceInfo crInfo) {
return findRepeatingAnnotations(crInfo.definition(), AdditionalPrinterColumn.class).stream()
.map(annotation -> new CustomResourceColumnDefinitionBuilder()
.withType(annotation.type().getValue())
.withName(emptyToNull(annotation.name()))
.withJsonPath(annotation.jsonPath())
.withFormat(annotation.format() != AdditionalPrinterColumn.Format.NONE
? annotation.format().getValue() : null)
.withDescription(emptyToNull(annotation.description()))
.withPriority(annotation.priority())
.build())
.collect(Collectors.toList());
}

/**
* Find printer columns in schema
*
* @param resolver the JsonSchema resolver
* @return printer columns
*/
private Collection<CustomResourceColumnDefinition> findPrinterColumns(JsonSchema resolver) {
List<CustomResourceColumnDefinition> result = new LinkedList<>();
handlePrinterColumns(resolver, (path, column, format, priority, type, description) ->
result.add(new CustomResourceColumnDefinitionBuilder()
.withType(type)
.withName(column)
.withJsonPath(path)
.withFormat(emptyToNull(format))
.withDescription(emptyToNull(description))
.withPriority(priority)
.build()));
return result;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.fabric8.crd.generator.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that allows additionalPrinterColumns entries to be created with arbitrary JSONPaths.
*/
@Repeatable(AdditionalPrinterColumns.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface AdditionalPrinterColumn {

String jsonPath();

String name() default "";

Type type() default Type.STRING;

Format format() default Format.NONE;

String description() default "";

int priority() default 0;

// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#type
enum Type {

STRING("string"),
INTEGER("integer"),
NUMBER("number"),
BOOLEAN("boolean"),
DATE("date");

public final String value;

Type(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}

// https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#format
enum Format {

NONE(""),
INT32("int32"),
INT64("int64"),
FLOAT("float"),
DOUBLE("double"),
BYTE("byte"),
BINARY("binary"),
DATE("date"),
DATE_TIME("date-time"),
PASSWORD("password");

public final String value;

Format(String value) {
this.value = value;
}

public String getValue() {
return value;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package io.fabric8.crd.generator.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface AdditionalPrinterColumns {
AdditionalPrinterColumn[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ public static String coalesce(String... items) {
* Utility method to generate UUIDs.
* This is taken from Spring Framework's <a href=
* "https://github.com/spring-projects/spring-framework/blob/a4db0e7448287028d228d46fe7b4df202150958a/spring-core/src/main/java/org/springframework/util/SimpleIdGenerator.java#L35">SimpleIdGenerator</a>
*
*
* @return generated UUID
*/
public static UUID generateId() {
Expand Down Expand Up @@ -303,6 +303,10 @@ public static boolean isNotNullOrEmpty(String[] array) {
return !(array == null || array.length == 0);
}

public static String emptyToNull(String value) {
return isNotNullOrEmpty(value) ? null : value;
}

public static <T> boolean isNotNull(T... refList) {
return Optional.ofNullable(refList)
.map(refs -> Stream.of(refs).allMatch(Objects::nonNull))
Expand Down