Skip to content

feat: add non-apt-tied behavior annotation and configuration to core #1013

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

Merged
merged 2 commits into from
Mar 10, 2022
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package io.javaoperatorsdk.operator.api.config;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.ReconcilerUtils;
import io.javaoperatorsdk.operator.api.config.dependent.DependentResourceSpec;
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResource;
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependentResourceConfig;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilter;
import io.javaoperatorsdk.operator.processing.event.source.controller.ResourceEventFilters;

@SuppressWarnings("rawtypes")
public class AnnotationControllerConfiguration<R extends HasMetadata>
implements io.javaoperatorsdk.operator.api.config.ControllerConfiguration<R> {

protected final Reconciler<R> reconciler;
private final ControllerConfiguration annotation;
private ConfigurationService service;
private List<DependentResourceSpec> specs;

public AnnotationControllerConfiguration(Reconciler<R> reconciler) {
this.reconciler = reconciler;
this.annotation = reconciler.getClass().getAnnotation(ControllerConfiguration.class);
}

@Override
public String getName() {
return ReconcilerUtils.getNameFor(reconciler);
}

@Override
public String getFinalizer() {
if (annotation == null || annotation.finalizerName().isBlank()) {
return ReconcilerUtils.getDefaultFinalizerName(getResourceClass());
} else {
final var finalizer = annotation.finalizerName();
if (ReconcilerUtils.isFinalizerValid(finalizer)) {
return finalizer;
} else {
throw new IllegalArgumentException(
finalizer
+ " is not a valid finalizer. See https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#finalizers for details");
}
}
}

@Override
public boolean isGenerationAware() {
return valueOrDefault(
annotation, ControllerConfiguration::generationAwareEventProcessing, true);
}

@Override
public Set<String> getNamespaces() {
return Set.of(valueOrDefault(annotation, ControllerConfiguration::namespaces, new String[] {}));
}

@Override
@SuppressWarnings("unchecked")
public Class<R> getResourceClass() {
return (Class<R>) Utils.getFirstTypeArgumentFromInterface(reconciler.getClass());
}

@Override
public String getLabelSelector() {
return valueOrDefault(annotation, ControllerConfiguration::labelSelector, "");
}

@Override
public ConfigurationService getConfigurationService() {
return service;
}

@Override
public void setConfigurationService(ConfigurationService service) {
this.service = service;
}

@Override
public String getAssociatedReconcilerClassName() {
return reconciler.getClass().getCanonicalName();
}

@SuppressWarnings("unchecked")
@Override
public ResourceEventFilter<R> getEventFilter() {
ResourceEventFilter<R> answer = null;

Class<ResourceEventFilter<R>>[] filterTypes =
(Class<ResourceEventFilter<R>>[]) valueOrDefault(annotation,
ControllerConfiguration::eventFilters, new Object[] {});
if (filterTypes.length > 0) {
for (var filterType : filterTypes) {
try {
ResourceEventFilter<R> filter = filterType.getConstructor().newInstance();

if (answer == null) {
answer = filter;
} else {
answer = answer.and(filter);
}
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
}
return answer != null ? answer : ResourceEventFilters.passthrough();
}

@Override
public Optional<Duration> reconciliationMaxInterval() {
if (annotation.reconciliationMaxInterval() != null) {
if (annotation.reconciliationMaxInterval().interval() <= 0) {
return Optional.empty();
}
return Optional.of(
Duration.of(
annotation.reconciliationMaxInterval().interval(),
annotation.reconciliationMaxInterval().timeUnit().toChronoUnit()));
} else {
return io.javaoperatorsdk.operator.api.config.ControllerConfiguration.super.reconciliationMaxInterval();
}
}

public static <T> T valueOrDefault(
ControllerConfiguration controllerConfiguration,
Function<ControllerConfiguration, T> mapper,
T defaultValue) {
if (controllerConfiguration == null) {
return defaultValue;
} else {
return mapper.apply(controllerConfiguration);
}
}

@SuppressWarnings({"rawtypes", "unchecked"})
@Override
public List<DependentResourceSpec> getDependentResources() {
if (specs == null) {
final var dependents =
valueOrDefault(annotation, ControllerConfiguration::dependents, new Dependent[] {});
if (dependents.length == 0) {
return Collections.emptyList();
}

specs = new ArrayList<>(dependents.length);
for (Dependent dependent : dependents) {
final Class<? extends DependentResource> dependentType = dependent.type();
if (KubernetesDependentResource.class.isAssignableFrom(dependentType)) {
final var kubeDependent = dependentType.getAnnotation(KubernetesDependent.class);
final var namespaces =
Utils.valueOrDefault(
kubeDependent,
KubernetesDependent::namespaces,
this.getNamespaces().toArray(new String[0]));
final var labelSelector =
Utils.valueOrDefault(kubeDependent, KubernetesDependent::labelSelector, null);
final var addOwnerReference =
Utils.valueOrDefault(
kubeDependent,
KubernetesDependent::addOwnerReference,
KubernetesDependent.ADD_OWNER_REFERENCE_DEFAULT);
KubernetesDependentResourceConfig config =
new KubernetesDependentResourceConfig(
addOwnerReference, namespaces, labelSelector, getConfigurationService());
specs.add(new DependentResourceSpec(dependentType, config));
} else {
specs.add(new DependentResourceSpec(dependentType));
}
}
}
return specs;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;

public class BaseConfigurationService extends AbstractConfigurationService {

private static final String LOGGER_NAME = "Default ConfigurationService implementation";
Expand All @@ -12,6 +15,10 @@ public BaseConfigurationService(Version version) {
super(version);
}

public BaseConfigurationService() {
this(Utils.loadFromProperties());
}

@Override
protected void logMissingReconcilerWarning(String reconcilerKey, String reconcilersNameMessage) {
logger.warn("Configuration for reconciler '{}' was not found. {}", reconcilerKey,
Expand All @@ -25,4 +32,41 @@ public String getLoggerName() {
protected Logger getLogger() {
return logger;
}

@Override
public <R extends HasMetadata> ControllerConfiguration<R> getConfigurationFor(
Reconciler<R> reconciler) {
var config = super.getConfigurationFor(reconciler);
if (config == null) {
if (createIfNeeded()) {
// create the configuration on demand and register it
config = configFor(reconciler);
register(config);
getLogger().info(
"Created configuration for reconciler {} with name {}",
reconciler.getClass().getName(),
config.getName());
}
} else {
// check that we don't have a reconciler name collision
final var newControllerClassName = reconciler.getClass().getCanonicalName();
if (!config.getAssociatedReconcilerClassName().equals(newControllerClassName)) {
throwExceptionOnNameCollision(newControllerClassName, config);
}
}
return config;
}

protected <R extends HasMetadata> ControllerConfiguration<R> configFor(Reconciler<R> reconciler) {
return new AnnotationControllerConfiguration<>(reconciler);
}

protected boolean createIfNeeded() {
return true;
}

@Override
public boolean checkCRDAndValidateLocalModel() {
return Utils.shouldCheckCRDAndValidateLocalModel();
}
}
Loading