Skip to content

Commit

Permalink
feat: allow CRD generation of all detected CRs (#399)
Browse files Browse the repository at this point in the history
Fixes #90
  • Loading branch information
metacosm committed Sep 13, 2022
1 parent e9cf1a6 commit e9d1569
Show file tree
Hide file tree
Showing 18 changed files with 474 additions and 133 deletions.
3 changes: 0 additions & 3 deletions .github/scripts/testJokeSample.sh
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
NAMESPACE="${1}"

# Apply "3rd-party" CRD: joke is not an owned resource by our operator, so it's not generated
kubectl apply -f samples/joke/src/main/k8s/jokes.samples.javaoperatorsdk.io-v1.yml

# Test operator by creating a Joke Request resource
kubectl apply -f samples/joke/src/main/k8s/jokerequest.yml

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.quarkiverse.operatorsdk.common;

import java.util.Map;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
Expand All @@ -13,7 +15,7 @@ public AnnotationConfigurableAugmentedClassInfo(ClassInfo classInfo) {
}

@Override
protected boolean augmentIfKept(IndexView index, Logger log) {
protected boolean augmentIfKept(IndexView index, Logger log, Map<String, Object> context) {
// record associated configuration class
associatedConfigurationClass = typeAt(0).name();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package io.quarkiverse.operatorsdk.common;

import static io.quarkiverse.operatorsdk.common.Constants.ANNOTATION_CONFIGURABLE;
import static io.quarkiverse.operatorsdk.common.Constants.CUSTOM_RESOURCE;
import static io.quarkiverse.operatorsdk.common.Constants.DEPENDENT_RESOURCE;
import static io.quarkiverse.operatorsdk.common.Constants.RECONCILER;

import java.util.Collections;
import java.util.Map;
import java.util.stream.Stream;

import org.jboss.jandex.DotName;
Expand All @@ -29,24 +32,39 @@ public static boolean isStatusNotVoid(String statusClassName) {
* @return a stream of {@link ReconcilerAugmentedClassInfo} providing information about processable reconcilers
*/
public static Stream<ReconcilerAugmentedClassInfo> getKnownReconcilers(IndexView index, Logger log) {
return getProcessableExtensionsOf(RECONCILER, index, log)
return getProcessableImplementationsOf(RECONCILER, index, log, Collections.emptyMap())
.map(ReconcilerAugmentedClassInfo.class::cast);
}

public static Stream<? extends SelectiveAugmentedClassInfo> getProcessableExtensionsOf(DotName extendedOrImplementedClass,
IndexView index, Logger log) {
return index.getAllKnownImplementors(extendedOrImplementedClass).stream()
public static Stream<? extends SelectiveAugmentedClassInfo> getProcessableImplementationsOf(DotName interfaceType,
IndexView index, Logger log, Map<String, Object> context) {
return getProcessableImplementationsOrExtensionsOf(interfaceType, index, log, context, true);
}

public static Stream<? extends SelectiveAugmentedClassInfo> getProcessableSubClassesOf(DotName classType,
IndexView index, Logger log, Map<String, Object> context) {
return getProcessableImplementationsOrExtensionsOf(classType, index, log, context, false);
}

private static Stream<? extends SelectiveAugmentedClassInfo> getProcessableImplementationsOrExtensionsOf(
DotName implementedOrExtendedClass,
IndexView index, Logger log, Map<String, Object> context, boolean isInterface) {
final var extensions = isInterface ? index.getAllKnownImplementors(implementedOrExtendedClass)
: index.getAllKnownSubclasses(implementedOrExtendedClass);
return extensions.stream()
.map(classInfo -> {
if (RECONCILER.equals(extendedOrImplementedClass)) {
if (RECONCILER.equals(implementedOrExtendedClass)) {
return new ReconcilerAugmentedClassInfo(classInfo);
} else if (DEPENDENT_RESOURCE.equals(extendedOrImplementedClass)) {
} else if (DEPENDENT_RESOURCE.equals(implementedOrExtendedClass)) {
return new DependentResourceAugmentedClassInfo(classInfo);
} else if (ANNOTATION_CONFIGURABLE.equals(extendedOrImplementedClass)) {
} else if (ANNOTATION_CONFIGURABLE.equals(implementedOrExtendedClass)) {
return new AnnotationConfigurableAugmentedClassInfo(classInfo);
} else if (CUSTOM_RESOURCE.equals(implementedOrExtendedClass)) {
return new ResourceTargetingAugmentedClassInfo(classInfo, null);
} else {
throw new IllegalArgumentException("Don't know how to process " + extendedOrImplementedClass);
throw new IllegalArgumentException("Don't know how to process " + implementedOrExtendedClass);
}
})
.filter(fci -> fci.keepAugmented(index, log));
.filter(fci -> fci.keepAugmented(index, log, context));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import static io.quarkiverse.operatorsdk.common.Constants.DEPENDENT_RESOURCE;

import java.util.Map;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.IndexView;
import org.jboss.logging.Logger;
Expand All @@ -13,7 +15,7 @@ public DependentResourceAugmentedClassInfo(ClassInfo classInfo) {
}

@Override
protected boolean augmentIfKept(IndexView index, Logger log) {
protected boolean augmentIfKept(IndexView index, Logger log, Map<String, Object> context) {
// only need to check the secondary resource type since the primary should have already been processed with the associated reconciler
final var secondaryTypeDN = typeAt(0).name();
registerForReflection(secondaryTypeDN.toString());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package io.quarkiverse.operatorsdk.common;

import static io.quarkiverse.operatorsdk.common.ConfigurationUtils.annotationValueOrDefault;

import java.util.Locale;

import org.jboss.jandex.AnnotationValue;
import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

import io.fabric8.kubernetes.api.Pluralize;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Kind;
import io.fabric8.kubernetes.model.annotation.Plural;
import io.fabric8.kubernetes.model.annotation.Singular;

public class HasMetadataUtils {

private static final DotName GROUP = DotName.createSimple(Group.class.getName());
private static final DotName PLURAL = DotName.createSimple(Plural.class.getName());
private static final DotName SINGULAR = DotName.createSimple(Singular.class.getName());
private static final DotName KIND = DotName.createSimple(Kind.class.getName());

public static String getFullResourceName(ClassInfo resourceCI) {
return HasMetadata.getFullResourceName(getPlural(resourceCI), getGroup(resourceCI));
}

public static String getPlural(ClassInfo resourceCI) {
return annotationValueOrDefault(resourceCI.classAnnotation(PLURAL),
"value",
value -> value.asString().toLowerCase(Locale.ROOT),
() -> Pluralize.toPlural(getSingular(resourceCI)));
}

public static String getGroup(ClassInfo resourceCI) {
return annotationValueOrDefault(resourceCI.classAnnotation(GROUP), "value",
AnnotationValue::asString, () -> null);
}

public static String getSingular(ClassInfo resourceCI) {
return annotationValueOrDefault(resourceCI.classAnnotation(SINGULAR), "value",
AnnotationValue::asString, () -> getKind(resourceCI).toLowerCase(Locale.ROOT));
}

public static String getKind(ClassInfo resourceCI) {
return annotationValueOrDefault(resourceCI.classAnnotation(KIND),
"value", AnnotationValue::asString, resourceCI::simpleName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package io.quarkiverse.operatorsdk.common;

import io.fabric8.kubernetes.api.model.HasMetadata;

public interface LoadableResourceHolder<T extends HasMetadata> {
String getAssociatedResourceTypeName();

Class<T> loadAssociatedClass();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,24 @@
import static io.quarkiverse.operatorsdk.common.Constants.HAS_METADATA;
import static io.quarkiverse.operatorsdk.common.Constants.RECONCILER;

import java.util.Map;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
import org.jboss.jandex.IndexView;
import org.jboss.logging.Logger;

import io.fabric8.kubernetes.api.model.HasMetadata;

/**
* Metadata about a processable reconciler implementation.
*/
public class ReconcilerAugmentedClassInfo extends SelectiveAugmentedClassInfo {
public class ReconcilerAugmentedClassInfo extends SelectiveAugmentedClassInfo implements LoadableResourceHolder<HasMetadata> {

private final String name;
private boolean isCR;
private boolean hasNonVoidStatus;
private SimpleLoadableResourceHolder<HasMetadata> holder;

public ReconcilerAugmentedClassInfo(ClassInfo classInfo) {
super(classInfo, RECONCILER, 1);
Expand All @@ -32,8 +37,8 @@ public String name() {
}

@Override
protected boolean augmentIfKept(IndexView index, Logger log) {
final var primaryTypeDN = typeAt(0).name();
protected boolean augmentIfKept(IndexView index, Logger log, Map<String, Object> context) {
final var primaryTypeDN = primaryTypeName();

// if we get CustomResource instead of a subclass, ignore the controller since we cannot do anything with it
if (primaryTypeDN.toString() == null || CUSTOM_RESOURCE.equals(primaryTypeDN)
Expand All @@ -53,14 +58,35 @@ protected boolean augmentIfKept(IndexView index, Logger log) {
isCR = crStatus.isCR;
hasNonVoidStatus = crStatus.hasNonVoidStatus;

final ClassInfo primaryCI = index.getClassByName(primaryTypeDN);
holder = new SimpleLoadableResourceHolder<>(primaryCI);

return true;
}

@Override
public String getAssociatedResourceTypeName() {
return holder.getAssociatedResourceTypeName();
}

@Override
public Class<HasMetadata> loadAssociatedClass() {
return holder.loadAssociatedClass();
}

public boolean isCRTargeting() {
return isCR;
}

public boolean hasNonVoidStatus() {
return hasNonVoidStatus;
}

public ResourceTargetingAugmentedClassInfo getAssociatedCustomResourceInfo() {
if (isCR) {
return new ResourceTargetingAugmentedClassInfo(holder.getResourceCI(), name);
}
throw new IllegalStateException("Cannot get associated Custom Resource information because '"
+ name + "' targets " + primaryTypeName().toString() + " primary resource which isn't a Custom Resource.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package io.quarkiverse.operatorsdk.common;

import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.IndexView;
import org.jboss.logging.Logger;

import io.fabric8.kubernetes.client.CustomResource;

public class ResourceTargetingAugmentedClassInfo extends SelectiveAugmentedClassInfo
implements LoadableResourceHolder<CustomResource<?, ?>> {
private final LoadableResourceHolder<CustomResource<?, ?>> holder;
private final String reconcilerName;
public static final String EXISTING_CRDS_KEY = "existing-crds-key";

protected ResourceTargetingAugmentedClassInfo(ClassInfo classInfo, String associatedReconcilerName) {
super(classInfo, Constants.CUSTOM_RESOURCE, 2);
this.reconcilerName = associatedReconcilerName;
this.holder = new SimpleLoadableResourceHolder<>(classInfo);
}

@Override
protected boolean augmentIfKept(IndexView index, Logger log, Map<String, Object> context) {
// only keep the information if the associated CRD hasn't already been generated
return Optional.ofNullable(context.get(EXISTING_CRDS_KEY))
.map(value -> {
@SuppressWarnings("unchecked")
Set<String> generated = (Set<String>) value;
return !generated.contains(getAssociatedResourceTypeName());
})
.orElse(true);
}

public String getAssociatedResourceTypeName() {
return holder.getAssociatedResourceTypeName();
}

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

public Class<CustomResource<?, ?>> loadAssociatedClass() {
return holder.loadAssociatedClass();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;
Expand Down Expand Up @@ -35,7 +36,7 @@ public ClassInfo classInfo() {
return classInfo;
}

boolean keepAugmented(IndexView index, Logger log) {
boolean keepAugmented(IndexView index, Logger log, Map<String, Object> context) {
final var targetClassName = extendedOrImplementedClassName();
final var consideredClassName = classInfo.name();
if (Modifier.isAbstract(classInfo.flags())) {
Expand All @@ -61,7 +62,7 @@ boolean keepAugmented(IndexView index, Logger log) {
}
this.types = typeParameters.toArray(Type[]::new);

return augmentIfKept(index, log);
return augmentIfKept(index, log, context);
}

public List<String> getClassNamesToRegisterForReflection() {
Expand All @@ -82,7 +83,8 @@ protected String extendedOrImplementedClassName() {
return extendedOrImplementedClass.local();
}

protected abstract boolean augmentIfKept(IndexView index, Logger log);
protected abstract boolean augmentIfKept(IndexView index, Logger log,
Map<String, Object> context);

protected CRStatus handlePossibleCR(DotName primaryTypeDN, IndexView index, Logger log) {
// register spec and status for reflection if we're targeting a CustomResource
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.quarkiverse.operatorsdk.common;

import static io.quarkiverse.operatorsdk.common.ClassLoadingUtils.loadClass;

import org.jboss.jandex.ClassInfo;

import io.fabric8.kubernetes.api.model.HasMetadata;

public class SimpleLoadableResourceHolder<T extends HasMetadata> implements LoadableResourceHolder<T> {

private Class<T> clazz;
private final String resourceTypeName;
private final String resourceClassName;
private final ClassInfo resourceCI;

public SimpleLoadableResourceHolder(ClassInfo resourceCI) {
this.resourceTypeName = HasMetadataUtils.getFullResourceName(resourceCI);
this.resourceClassName = resourceCI.name().toString();
this.resourceCI = resourceCI;
}

public String getAssociatedResourceTypeName() {
return resourceTypeName;
}

@SuppressWarnings("unchecked")
public Class<T> loadAssociatedClass() {
if (clazz == null) {
clazz = (Class<T>) loadClass(resourceClassName, HasMetadata.class);
}
return clazz;
}

public ClassInfo getResourceCI() {
return resourceCI;
}
}

0 comments on commit e9d1569

Please sign in to comment.