Skip to content

WIP: feat: make it possible to externally configure the operator #228

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

Closed
wants to merge 6 commits 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
@@ -1,89 +1,118 @@
package io.javaoperatorsdk.operator;

import io.javaoperatorsdk.operator.api.ResourceController;
import io.javaoperatorsdk.operator.processing.EventDispatcher;
import io.javaoperatorsdk.operator.processing.EventScheduler;
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import io.fabric8.kubernetes.api.model.apiextensions.v1beta1.CustomResourceDefinition;
import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.CustomResourceDoneable;
import io.fabric8.kubernetes.client.CustomResourceList;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext;
import io.fabric8.kubernetes.client.dsl.internal.CustomResourceOperationsImpl;
import io.fabric8.kubernetes.internal.KubernetesDeserializer;
import io.fabric8.openshift.client.DefaultOpenShiftClient;
import io.javaoperatorsdk.operator.api.ResourceController;
import io.javaoperatorsdk.operator.config.ClientConfiguration;
import io.javaoperatorsdk.operator.config.Configuration;
import io.javaoperatorsdk.operator.config.OperatorConfiguration;
import io.javaoperatorsdk.operator.processing.EventDispatcher;
import io.javaoperatorsdk.operator.processing.EventScheduler;
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

@SuppressWarnings("rawtypes")
public class Operator {

private final static Logger log = LoggerFactory.getLogger(Operator.class);
private final KubernetesClient k8sClient;
private final Configuration config;
private Map<Class<? extends CustomResource>, CustomResourceOperationsImpl> customResourceClients = new HashMap<>();

public Operator(KubernetesClient k8sClient) {
this.k8sClient = k8sClient;
this.config = Configuration.defaultConfiguration();
}


public <R extends CustomResource> void registerControllerForAllNamespaces(ResourceController<R> controller) throws OperatorException {
registerController(controller, true, GenericRetry.defaultLimitedExponentialRetry());

public Operator() {
this(Configuration.defaultConfiguration());
}

public <R extends CustomResource> void registerControllerForAllNamespaces(ResourceController<R> controller, Retry retry) throws OperatorException {
registerController(controller, true, retry);

public Operator(Configuration config) {
this.config = config;
ConfigBuilder cb = new ConfigBuilder();

final ClientConfiguration clientCfg = config.getClientConfiguration();
cb.withTrustCerts(clientCfg.isTrustSelfSignedCertificates());
if (StringUtils.isNotBlank(clientCfg.getUsername())) {
cb.withUsername(clientCfg.getUsername());
}
if (StringUtils.isNotBlank(clientCfg.getPassword())) {
cb.withUsername(clientCfg.getPassword());
}
if (StringUtils.isNotBlank(clientCfg.getMasterUrl())) {
cb.withMasterUrl(clientCfg.getMasterUrl());
}

config.getOperatorConfiguration().getWatchedNamespaceIfUnique().ifPresent(cb::withNamespace);
this.k8sClient = clientCfg.isOpenshift() ? new DefaultOpenShiftClient(cb.build()) : new DefaultKubernetesClient(cb.build());
}

public <R extends CustomResource> void registerController(ResourceController<R> controller, String... targetNamespaces) throws OperatorException {
registerController(controller, false, GenericRetry.defaultLimitedExponentialRetry(), targetNamespaces);
public KubernetesClient getClient() {
return k8sClient;
}

public <R extends CustomResource> void registerController(ResourceController<R> controller, Retry retry, String... targetNamespaces) throws OperatorException {
registerController(controller, false, retry, targetNamespaces);
public <R extends CustomResource> void registerController(ResourceController<R> controller) throws OperatorException {
registerController(controller, GenericRetry.defaultLimitedExponentialRetry());
}

@SuppressWarnings("rawtypes")
private <R extends CustomResource> void registerController(ResourceController<R> controller,
boolean watchAllNamespaces, Retry retry, String... targetNamespaces) throws OperatorException {
public <R extends CustomResource> void registerController(ResourceController<R> controller, Retry retry) throws OperatorException {
Class<R> resClass = ControllerUtils.getCustomResourceClass(controller);
CustomResourceDefinitionContext crd = getCustomResourceDefinitionForController(controller);
KubernetesDeserializer.registerCustomKind(crd.getVersion(), crd.getKind(), resClass);
String finalizer = ControllerUtils.getFinalizer(controller);
MixedOperation client = k8sClient.customResources(crd, resClass, CustomResourceList.class, ControllerUtils.getCustomResourceDoneableClass(controller));
EventDispatcher eventDispatcher = new EventDispatcher(controller,
finalizer, new EventDispatcher.CustomResourceFacade(client), ControllerUtils.getGenerationEventProcessing(controller));
finalizer, new EventDispatcher.CustomResourceFacade(client), ControllerUtils.getGenerationEventProcessing(controller));
EventScheduler eventScheduler = new EventScheduler(eventDispatcher, retry);
registerWatches(controller, client, resClass, watchAllNamespaces, targetNamespaces, eventScheduler);
registerWatches(controller, client, resClass, eventScheduler);
}


private <R extends CustomResource> void registerWatches(ResourceController<R> controller, MixedOperation client,
Class<R> resClass,
boolean watchAllNamespaces, String[] targetNamespaces, EventScheduler eventScheduler) {

EventScheduler eventScheduler) {
CustomResourceOperationsImpl crClient = (CustomResourceOperationsImpl) client;
if (watchAllNamespaces) {
final OperatorConfiguration operatorCfg = config.getOperatorConfiguration();
final String namespaces;
if (operatorCfg.isWatchingAllNamespaces()) {
crClient.inAnyNamespace().watch(eventScheduler);
} else if (targetNamespaces.length == 0) {
namespaces = "all namespaces";
} else if (operatorCfg.isWatchingCurrentNamespace()) {
client.watch(eventScheduler);
namespaces = "client namespace (" + crClient.getNamespace() + ")";
} else {
for (String targetNamespace : targetNamespaces) {
final Set<String> cfgNamespaces = operatorCfg.getNamespaces();
for (String targetNamespace : cfgNamespaces) {
crClient.inNamespace(targetNamespace).watch(eventScheduler);
log.debug("Registered controller for namespace: {}", targetNamespace);
}
namespaces = String.join(", ", cfgNamespaces);
}
customResourceClients.put(resClass, (CustomResourceOperationsImpl) client);
log.info("Registered Controller: '{}' for CRD: '{}' for namespaces: {}", controller.getClass().getSimpleName(),
resClass, targetNamespaces.length == 0 ? "[all/client namespace]" : Arrays.toString(targetNamespaces));
resClass, namespaces);
}

private CustomResourceDefinitionContext getCustomResourceDefinitionForController(ResourceController controller) {
String crdName = ControllerUtils.getCrdName(controller);
CustomResourceDefinition customResourceDefinition = k8sClient.customResourceDefinitions().withName(crdName).get();
Expand All @@ -93,20 +122,20 @@ private CustomResourceDefinitionContext getCustomResourceDefinitionForController
CustomResourceDefinitionContext context = CustomResourceDefinitionContext.fromCrd(customResourceDefinition);
return context;
}

public Map<Class<? extends CustomResource>, CustomResourceOperationsImpl> getCustomResourceClients() {
return customResourceClients;
}

public <T extends CustomResource, L extends CustomResourceList<T>, D extends CustomResourceDoneable<T>> CustomResourceOperationsImpl<T, L, D>
getCustomResourceClients(Class<T> customResourceClass) {
return customResourceClients.get(customResourceClass);
}

private String getKind(CustomResourceDefinition crd) {
return crd.getSpec().getNames().getKind();
}

private String getApiVersion(CustomResourceDefinition crd) {
return crd.getSpec().getGroup() + "/" + crd.getSpec().getVersion();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.javaoperatorsdk.operator.config;

public class ClientConfiguration {
private boolean openshift = false;
private String username;
private String password;
private String masterUrl;
private boolean trustSelfSignedCertificates = false;

public boolean isOpenshift() {
return openshift;
}

public ClientConfiguration setOpenshift(boolean openshift) {
this.openshift = openshift;
return this;
}

public String getUsername() {
return username;
}

public ClientConfiguration setUsername(String username) {
this.username = username;
return this;
}

public String getPassword() {
return password;
}

public ClientConfiguration setPassword(String password) {
this.password = password;
return this;
}

public String getMasterUrl() {
return masterUrl;
}

public ClientConfiguration setMasterUrl(String masterUrl) {
this.masterUrl = masterUrl;
return this;
}

public boolean isTrustSelfSignedCertificates() {
return trustSelfSignedCertificates;
}

public ClientConfiguration setTrustSelfSignedCertificates(boolean trustSelfSignedCertificates) {
this.trustSelfSignedCertificates = trustSelfSignedCertificates;
return this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package io.javaoperatorsdk.operator.config;

import java.util.Optional;

import io.fabric8.kubernetes.client.ConfigBuilder;
import io.fabric8.kubernetes.client.DefaultKubernetesClient;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.openshift.client.DefaultOpenShiftClient;
import io.javaoperatorsdk.operator.processing.retry.GenericRetry;
import io.javaoperatorsdk.operator.processing.retry.Retry;

public class Configuration {
private final ClientConfiguration client = new ClientConfiguration();
private final OperatorConfiguration operator = new OperatorConfiguration();
private final RetryConfiguration retry = new RetryConfiguration();

public ClientConfiguration getClientConfiguration() {
return client;
}

public OperatorConfiguration getOperatorConfiguration() {
return operator;
}

public RetryConfiguration getRetryConfiguration() {
return retry;
}

public KubernetesClient getConfiguredClient() {
return getClientFor(getClientConfiguration(), getOperatorConfiguration());
}

public static KubernetesClient getClientFor(ClientConfiguration clientCfg, OperatorConfiguration operatorCfg) {
ConfigBuilder cb = new ConfigBuilder();

cb.withTrustCerts(clientCfg.isTrustSelfSignedCertificates());
trimmedPropertyIfPresent(clientCfg.getUsername()).ifPresent(cb::withUsername);
trimmedPropertyIfPresent(clientCfg.getPassword()).ifPresent(cb::withPassword);
trimmedPropertyIfPresent(clientCfg.getMasterUrl()).ifPresent(cb::withMasterUrl);

operatorCfg.getWatchedNamespaceIfUnique().ifPresent(cb::withNamespace);
return clientCfg.isOpenshift() ? new DefaultOpenShiftClient(cb.build()) : new DefaultKubernetesClient(cb.build());
}

public static Retry getRetryFor(RetryConfiguration retryCfg) {
GenericRetry retry = new GenericRetry();
Optional.ofNullable(retryCfg.getInitialInterval()).ifPresent(retry::setInitialInterval);
Optional.ofNullable(retryCfg.getIntervalMultiplier()).ifPresent(retry::setIntervalMultiplier);
Optional.ofNullable(retryCfg.getMaxAttempts()).ifPresent(retry::setMaxAttempts);
Optional.ofNullable(retryCfg.getMaxInterval()).ifPresent(retry::setMaxInterval);
return retry;
}

private static Optional<String> trimmedPropertyIfPresent(String string) {
return Optional.ofNullable(string).map(String::trim);
}

public static Configuration defaultConfiguration() {
return new Configuration();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package io.javaoperatorsdk.operator.config;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Optional;
import java.util.Set;

public class OperatorConfiguration {

private Set<String> namespaces = new HashSet<>();
public static String ALL_NAMESPACES = "all_namespaces";

public Set<String> getNamespaces() {
return namespaces;
}

public OperatorConfiguration setNamespaces(Set<String> namespaces) {
this.namespaces = namespaces;
return this;
}

public OperatorConfiguration addWatchedNamespaces(String... namespaces) {
this.namespaces.addAll(Arrays.asList(namespaces));
return this;
}

public OperatorConfiguration watchAllNamespaces() {
this.namespaces.add(ALL_NAMESPACES);
return this;
}

public boolean isWatchingAllNamespaces() {
return namespaces.contains(ALL_NAMESPACES);
}

public boolean isWatchingCurrentNamespace() {
return namespaces.isEmpty();
}

public Optional<String> getWatchedNamespaceIfUnique() {
return namespaces.stream().findFirst();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package io.javaoperatorsdk.operator.config;

public class RetryConfiguration {
private Integer maxAttempts;
private Long initialInterval;
private Double intervalMultiplier;
private Long maxInterval;
private Long maxElapsedTime;

public Integer getMaxAttempts() {
return maxAttempts;
}

public RetryConfiguration setMaxAttempts(Integer maxAttempts) {
this.maxAttempts = maxAttempts;
return this;
}

public Long getInitialInterval() {
return initialInterval;
}

public RetryConfiguration setInitialInterval(Long initialInterval) {
this.initialInterval = initialInterval;
return this;
}

public Double getIntervalMultiplier() {
return intervalMultiplier;
}

public RetryConfiguration setIntervalMultiplier(Double intervalMultiplier) {
this.intervalMultiplier = intervalMultiplier;
return this;
}

public Long getMaxInterval() {
return maxInterval;
}

public RetryConfiguration setMaxInterval(Long maxInterval) {
this.maxInterval = maxInterval;
return this;
}

public Long getMaxElapsedTime() {
return maxElapsedTime;
}

public RetryConfiguration setMaxElapsedTime(Long maxElapsedTime) {
this.maxElapsedTime = maxElapsedTime;
return this;
}
}
Loading