Skip to content

Commit

Permalink
feat: adapt code to Workflow now being a separate annotation
Browse files Browse the repository at this point in the history
Signed-off-by: Chris Laprun <[email protected]>
  • Loading branch information
metacosm committed Mar 22, 2024
1 parent 0d211ad commit 87a5580
Show file tree
Hide file tree
Showing 20 changed files with 196 additions and 95 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,20 @@
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.Workflow;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.quarkiverse.operatorsdk.annotations.CSVMetadata;
import io.quarkiverse.operatorsdk.annotations.CSVMetadata.Annotations;
import io.quarkiverse.operatorsdk.annotations.CSVMetadata.Annotations.Annotation;
import io.quarkiverse.operatorsdk.annotations.CSVMetadata.RequiredCRD;

@CSVMetadata(bundleName = "third-operator", requiredCRDs = @RequiredCRD(kind = SecondExternal.KIND, name = "externalagains."
+ SecondExternal.GROUP, version = SecondExternal.VERSION), replaces = "1.0.0", annotations = @Annotations(skipRange = ">=1.0.0 <1.0.3", capabilities = "Test", others = @Annotation(name = "foo", value = "bar")))
@ControllerConfiguration(name = ThirdReconciler.NAME, dependents = {
@Workflow(dependents = {
@Dependent(type = ExternalDependentResource.class),
@Dependent(type = PodDependentResource.class)
})
@CSVMetadata(bundleName = "third-operator", requiredCRDs = @RequiredCRD(kind = SecondExternal.KIND, name = "externalagains."
+ SecondExternal.GROUP, version = SecondExternal.VERSION), replaces = "1.0.0", annotations = @Annotations(skipRange = ">=1.0.0 <1.0.3", capabilities = "Test", others = @Annotation(name = "foo", value = "bar")))
@ControllerConfiguration(name = ThirdReconciler.NAME)
public class ThirdReconciler implements Reconciler<Third> {

public static final String NAME = "third";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Ignore;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.Workflow;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.quarkiverse.operatorsdk.annotations.AdditionalRBACRules;
import io.quarkiverse.operatorsdk.annotations.RBACRule;
Expand All @@ -22,6 +23,7 @@ private Constants() {
public static final DotName CUSTOM_RESOURCE = DotName.createSimple(CustomResource.class.getName());
public static final DotName HAS_METADATA = DotName.createSimple(HasMetadata.class.getName());
public static final DotName CONTROLLER_CONFIGURATION = DotName.createSimple(ControllerConfiguration.class.getName());
public static final DotName WORKFLOW = DotName.createSimple(Workflow.class.getName());
public static final DotName DEPENDENT_RESOURCE = DotName.createSimple(DependentResource.class.getName());
public static final DotName CONFIGURED = DotName.createSimple(Configured.class.getName());
public static final DotName ANNOTATION_CONFIGURABLE = DotName.createSimple(AnnotationConfigurable.class.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package io.quarkiverse.operatorsdk.common;

import static io.quarkiverse.operatorsdk.common.Constants.CONTROLLER_CONFIGURATION;
import static io.quarkiverse.operatorsdk.common.Constants.CUSTOM_RESOURCE;
import static io.quarkiverse.operatorsdk.common.Constants.HAS_METADATA;
import static io.quarkiverse.operatorsdk.common.Constants.RECONCILER;
import static io.quarkiverse.operatorsdk.common.Constants.*;

import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -48,10 +45,10 @@ protected void doAugment(IndexView index, Logger log, Map<String, Object> contex

// extract dependent information
final var reconciler = classInfo();
final var controllerAnnotation = reconciler.declaredAnnotation(CONTROLLER_CONFIGURATION);
final var workflow = reconciler.declaredAnnotation(WORKFLOW);
dependentResourceInfos = Collections.emptyList();
if (controllerAnnotation != null) {
final var dependents = controllerAnnotation.value("dependents");
if (workflow != null) {
final var dependents = workflow.value("dependents");
if (dependents != null) {
final var dependentAnnotations = dependents.asNestedArray();
var dependentResources = Collections.<String, DependentResourceAugmentedClassInfo> emptyMap();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ public static ClusterRole createClusterRole(QuarkusControllerConfiguration<?> cr
.endMetadata()
.addToRules(rule.build());

final Map<String, DependentResourceSpecMetadata<?, ?, ?>> dependentsMetadata = cri.getDependentsMetadata();
@SuppressWarnings("rawtypes")
final Map<String, DependentResourceSpecMetadata> dependentsMetadata = cri.dependentsMetadata();
dependentsMetadata.forEach((name, spec) -> {
final var dependentResourceClass = spec.getDependentResourceClass();
final var associatedResourceClass = spec.getDependentType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
import io.javaoperatorsdk.operator.processing.dependent.workflow.Condition;
import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflow;
import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowFactory;
import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflowSupport;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
import io.javaoperatorsdk.operator.processing.event.source.filter.OnAddFilter;
Expand All @@ -52,6 +51,7 @@
class QuarkusControllerConfigurationBuildStep {

static final Logger log = Logger.getLogger(QuarkusControllerConfigurationBuildStep.class.getName());
private static final ManagedWorkflowSupport workflowSupport = new ManagedWorkflowSupport();

private static final KubernetesDependentConverter KUBERNETES_DEPENDENT_CONVERTER = new KubernetesDependentConverter() {
@Override
Expand Down Expand Up @@ -235,15 +235,7 @@ static QuarkusControllerConfiguration createConfiguration(
final var primaryAsResource = primaryInfo.asResourceTargeting();
final var resourceClass = primaryInfo.loadAssociatedClass();
final String resourceFullName = primaryAsResource.fullResourceName();
// initialize dependent specs
final Map<String, DependentResourceSpecMetadata> dependentResources;
final var dependentResourceInfos = reconcilerInfo.getDependentResourceInfos();
final var hasDependents = !dependentResourceInfos.isEmpty();
if (hasDependents) {
dependentResources = new HashMap<>(dependentResourceInfos.size());
} else {
dependentResources = Collections.emptyMap();
}

configuration = new QuarkusControllerConfiguration(
reconcilerClassName,
name,
Expand All @@ -259,34 +251,48 @@ static QuarkusControllerConfiguration createConfiguration(
primaryAsResource.hasNonVoidStatus(),
maxReconciliationInterval,
onAddFilter, onUpdateFilter, genericFilter, retryClass, retryConfigurationClass, rateLimiterClass,
rateLimiterConfigurationClass, dependentResources, null, additionalRBACRules, fieldManager, itemStore);

if (hasDependents) {
dependentResourceInfos.forEach(dependent -> {
final var spec = createDependentResourceSpec(dependent, index, configuration);
final var dependentName = dependent.classInfo().name();
dependentResources.put(dependentName.toString(), spec);
});
}
rateLimiterConfigurationClass, additionalRBACRules, fieldManager, itemStore);

// compute workflow and set it (originally set to null in constructor)
final ManagedWorkflow workflow;
if (hasDependents) {
// make workflow bytecode serializable
final var original = ManagedWorkflowFactory.DEFAULT.workflowFor(configuration);
workflow = new QuarkusManagedWorkflow<>(original.getOrderedSpecs(),
original.hasCleaner());
} else {
workflow = QuarkusManagedWorkflow.noOpManagedWorkflow;
}
configuration.setWorkflow(workflow);
// compute workflow and set it
initializeWorkflowIfNeeded(configuration, reconcilerInfo, index);

// need to set the namespaces after the dependents have been set so that they can be properly updated if needed
// however, we need to do it in a way that doesn't reset whether the namespaces were set by the user or not
configuration.propagateNamespacesToDependents();

log.infov(
"Processed ''{0}'' reconciler named ''{1}'' for ''{2}'' resource (version ''{3}'')",
reconcilerClassName, name, resourceFullName, HasMetadata.getApiVersion(resourceClass));
return configuration;
}

private static <R extends HasMetadata> void initializeWorkflowIfNeeded(QuarkusControllerConfiguration<R> configuration,
ReconcilerAugmentedClassInfo reconcilerInfo, IndexView index) {
final var workflowAnnotation = reconcilerInfo.classInfo().declaredAnnotation(WORKFLOW);
@SuppressWarnings("unchecked")
QuarkusManagedWorkflow<R> workflow = QuarkusManagedWorkflow.noOpManagedWorkflow;
if (workflowAnnotation != null) {
final var dependentResourceInfos = reconcilerInfo.getDependentResourceInfos();
if (!dependentResourceInfos.isEmpty()) {
Map<String, DependentResourceSpecMetadata> dependentResources = new HashMap<>(dependentResourceInfos.size());
dependentResourceInfos.forEach(dependent -> {
final var spec = createDependentResourceSpec(dependent, index, configuration);
final var dependentName = dependent.classInfo().name();
dependentResources.put(dependentName.toString(), spec);
});

final var explicitInvocation = ConfigurationUtils.annotationValueOrDefault(
workflowAnnotation, "explicitInvocation", AnnotationValue::asBoolean,
() -> false);
// make workflow bytecode serializable
final var spec = new QuarkusWorkflowSpec(dependentResources, explicitInvocation);
final var original = workflowSupport.createWorkflow(spec);
workflow = new QuarkusManagedWorkflow<>(spec, original.getOrderedSpecs(), original.hasCleaner());
}
}
configuration.setWorkflow(workflow);
}

private static List<PolicyRule> extractAdditionalRBACRules(ClassInfo info) {
// if there are multiple annotations they should be found under an automatically generated AdditionalRBACRules
final var additionalRuleAnnotations = ConfigurationUtils.annotationValueOrDefault(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,18 @@
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
import io.javaoperatorsdk.operator.api.reconciler.Workflow;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.quarkiverse.operatorsdk.annotations.RBACRule;
import io.quarkiverse.operatorsdk.annotations.RBACVerbs;

@ControllerConfiguration(name = TestReconciler.NAME, dependents = {
@Workflow(dependents = {
@Dependent(type = CRUDConfigMap.class),
@Dependent(type = ReadOnlySecret.class),
@Dependent(type = CreateOnlyService.class),
@Dependent(type = NonKubeResource.class)
})
@ControllerConfiguration(name = TestReconciler.NAME)
@RBACRule(verbs = RBACVerbs.UPDATE, apiGroups = RBACRule.ALL, resources = RBACRule.ALL)
public class TestReconciler implements Reconciler<TestCR> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,15 +244,18 @@ private static String getDependentKeyFromNames(String controllerName, String dep
return controllerName + "#" + dependentName;
}

@SuppressWarnings({ "unchecked", "rawtypes" })
@SuppressWarnings({ "rawtypes" })
public DependentResourceSpecMetadata getDependentByName(String controllerName, String dependentName) {
return (DependentResourceSpecMetadata) controllerConfigurations()
.filter(cc -> controllerName.equals(cc.getName()))
.findFirst()
.flatMap(cc -> cc.getDependentResources().stream()
.filter(drs -> dependentName.equals(((DependentResourceSpec) drs).getName()))
.findFirst())
.orElse(null);
final ControllerConfiguration<?> cc = getFor(controllerName);
if (cc == null) {
return null;
} else {
return cc.getWorkflowSpec().flatMap(spec -> spec.getDependentResourceSpecs().stream()
.filter(r -> r.getName().equals(dependentName) && r instanceof DependentResourceSpecMetadata<?, ?, ?>)
.map(DependentResourceSpecMetadata.class::cast)
.findFirst())
.orElse(null);
}
}

@SuppressWarnings("rawtypes")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceConfigurationProvider;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
import io.javaoperatorsdk.operator.api.config.workflow.WorkflowSpec;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.processing.dependent.workflow.ManagedWorkflow;
import io.javaoperatorsdk.operator.processing.event.rate.LinearRateLimiter;
import io.javaoperatorsdk.operator.processing.event.rate.RateLimiter;
import io.javaoperatorsdk.operator.processing.event.source.filter.GenericFilter;
Expand Down Expand Up @@ -68,10 +68,9 @@ public DefaultRateLimiter(Duration refreshPeriod, int limitForPeriod) {
private Set<String> namespaces;
private boolean wereNamespacesSet;
private String labelSelector;
private final Map<String, DependentResourceSpecMetadata<?, ?, ?>> dependentsMetadata;
private Retry retry;
private RateLimiter rateLimiter;
private ManagedWorkflow<R> workflow;
private QuarkusManagedWorkflow<R> workflow;
private QuarkusConfigurationService parent;
private ExternalGradualRetryConfiguration gradualRetry;

Expand All @@ -92,7 +91,6 @@ public QuarkusControllerConfiguration(
OnAddFilter<R> onAddFilter, OnUpdateFilter<R> onUpdateFilter, GenericFilter<R> genericFilter,
Class<? extends Retry> retryClass, Class<? extends Annotation> retryConfigurationClass,
Class<? extends RateLimiter> rateLimiterClass, Class<? extends Annotation> rateLimiterConfigurationClass,
Map<String, DependentResourceSpecMetadata<?, ?, ?>> dependentsMetadata, ManagedWorkflow<R> workflow,
List<PolicyRule> additionalRBACRules, String fieldManager, ItemStore<R> nullableItemStore) {
this.associatedReconcilerClassName = associatedReconcilerClassName;
this.name = name;
Expand All @@ -102,8 +100,6 @@ public QuarkusControllerConfiguration(
this.resourceClass = resourceClass;
this.informerListLimit = Optional.ofNullable(nullableInformerListLimit);
this.additionalRBACRules = additionalRBACRules;
this.dependentsMetadata = dependentsMetadata;
this.workflow = workflow;
setNamespaces(namespaces);
this.wereNamespacesSet = wereNamespacesSet;
setFinalizer(finalizerName);
Expand Down Expand Up @@ -194,17 +190,34 @@ public Set<String> getNamespaces() {
return namespaces;
}

@SuppressWarnings("unchecked")
void setNamespaces(Collection<String> namespaces) {
void setNamespaces(Set<String> namespaces) {
if (!namespaces.equals(this.namespaces)) {
this.namespaces = namespaces.stream().map(String::trim).collect(Collectors.toSet());
this.namespaces = sanitizeNamespaces(namespaces);
wereNamespacesSet = true;
// propagate namespace changes to the dependents' config if needed
this.dependentsMetadata.forEach((name, spec) -> {
propagateNamespacesToDependents();
}
}

private static Set<String> sanitizeNamespaces(Set<String> namespaces) {
return namespaces.stream().map(String::trim).collect(Collectors.toSet());
}

/**
* Record potentially user-set namespaces, updating the dependent resources, which should have been set before this method
* is called. Note that this method won't affect the status of whether the namespaces were set by the user or not, as this
* should have been recorded already when the instance was created.
* This method, while public for visibility purpose from the deployment module, should be considered internal and *NOT* be
* called from user code.
*/
@SuppressWarnings("unchecked")
public void propagateNamespacesToDependents() {
if (workflow != null) {
dependentsMetadata().forEach((unused, spec) -> {
final var config = spec.getDependentResourceConfig();
if (config instanceof QuarkusKubernetesDependentResourceConfig) {
final var qConfig = (QuarkusKubernetesDependentResourceConfig) config;
qConfig.setNamespaces(this.namespaces);
qConfig.setNamespaces(namespaces);
}
});
}
Expand Down Expand Up @@ -234,18 +247,19 @@ public boolean isStatusPresentAndNotVoid() {
}

public boolean areDependentsImpactedBy(Set<String> changedClasses) {
return dependentsMetadata.keySet().parallelStream().anyMatch(changedClasses::contains);
return dependentsMetadata().keySet().parallelStream().anyMatch(changedClasses::contains);
}

public boolean needsDependentBeansCreation() {
final var dependentsMetadata = dependentsMetadata();
return dependentsMetadata != null && !dependentsMetadata.isEmpty();
}

public ManagedWorkflow<R> getWorkflow() {
public QuarkusManagedWorkflow<R> getWorkflow() {
return workflow;
}

public void setWorkflow(ManagedWorkflow<R> workflow) {
public void setWorkflow(QuarkusManagedWorkflow<R> workflow) {
this.workflow = workflow;
}

Expand All @@ -255,8 +269,8 @@ public Object getConfigurationFor(DependentResourceSpec dependentResourceSpec) {
}

@Override
public List<DependentResourceSpec> getDependentResources() {
return dependentsMetadata.values().parallelStream().collect(Collectors.toList());
public Optional<WorkflowSpec> getWorkflowSpec() {
return workflow.getGenericSpec();
}

@Override
Expand Down Expand Up @@ -328,10 +342,8 @@ public Class<? extends RateLimiter> getRateLimiterClass() {
return rateLimiterClass;
}

// for Quarkus' RecordableConstructor
@SuppressWarnings("unused")
public Map<String, DependentResourceSpecMetadata<?, ?, ?>> getDependentsMetadata() {
return dependentsMetadata;
public Map<String, DependentResourceSpecMetadata> dependentsMetadata() {
return workflow.getSpec().map(QuarkusWorkflowSpec::getDependentResourceSpecMetadata).orElse(Collections.emptyMap());
}

void initAnnotationConfigurables(Reconciler<R> reconciler) {
Expand Down
Loading

0 comments on commit 87a5580

Please sign in to comment.