feat: id provider for external dependent resources by csviri · Pull Request #2970 · operator-framework/java-operator-sdk · GitHub
Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -149,16 +149,8 @@ public Optional<R> getSecondaryResource(P primary, Context<P> context) {
* @throws IllegalStateException if more than one candidate is found, in which case some other
* mechanism might be necessary to distinguish between candidate secondary resources
*/
protected Optional<R> selectTargetSecondaryResource(
Set<R> secondaryResources, P primary, Context<P> context) {
R desired = desired(primary, context);
var targetResources = secondaryResources.stream().filter(r -> r.equals(desired)).toList();
if (targetResources.size() > 1) {
throw new IllegalStateException(
"More than one secondary resource related to primary: " + targetResources);
}
return targetResources.isEmpty() ? Optional.empty() : Optional.of(targetResources.get(0));
}
protected abstract Optional<R> selectTargetSecondaryResource(
Set<R> secondaryResources, P primary, Context<P> context);

private void throwIfNull(R desired, P primary, String descriptor) {
if (desired == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@
*/
package io.javaoperatorsdk.operator.processing.dependent;

import java.util.List;
import java.util.Optional;
import java.util.Set;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.api.reconciler.Context;
import io.javaoperatorsdk.operator.api.reconciler.dependent.RecentOperationCacheFiller;
Expand Down Expand Up @@ -121,4 +125,30 @@ public void handleDeleteTargetResource(P primary, R resource, String key, Contex
protected InformerEventSource getExternalStateEventSource() {
return externalStateEventSource;
}

@Override
protected Optional<R> selectTargetSecondaryResource(
Set<R> secondaryResources, P primary, Context<P> context) {
R desired = desired(primary, context);
List<R> targetResources;
if (desired instanceof ExternalDependentIDProvider<?> desiredWithId) {
targetResources =
secondaryResources.stream()
.filter(
r ->
((ExternalDependentIDProvider<?>) r)
.externalResourceId()
.equals(desiredWithId.externalResourceId()))
.toList();
} else {
throw new IllegalStateException(
"Either implement ExternalDependentIDProvider or override this "
+ " (selectTargetSecondaryResource) method.");
}
if (targetResources.size() > 1) {
throw new IllegalStateException(
"More than one secondary resource related to primary: " + targetResources);
}
return targetResources.isEmpty() ? Optional.empty() : Optional.of(targetResources.get(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import io.fabric8.kubernetes.api.model.HasMetadata;
Expand Down Expand Up @@ -120,6 +121,13 @@ public Result<R> match(R resource, P primary, Context<P> context) {
return bulkDependentResource.match(resource, desired, primary, context);
}

@Override
protected Optional<R> selectTargetSecondaryResource(
Set<R> secondaryResources, P primary, Context<P> context) {
throw new IllegalStateException(
"BulkDependentResource should not call selectTargetSecondaryResource.");
}

@Override
protected void onCreated(P primary, R created, Context<P> context) {
asAbstractDependentResource().onCreated(primary, created, context);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright Java Operator SDK Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.javaoperatorsdk.operator.processing.dependent;

/**
* Provides the identifier for an object that represents an external resource. This ID is used to
* select target resource for a dependent resource from the resources returned by `{@link
* io.javaoperatorsdk.operator.api.reconciler.Context#getSecondaryResources(Class)}`.
*
* @param <T>
*/
public interface ExternalDependentIDProvider<T> {

T externalResourceId();
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import io.fabric8.kubernetes.api.model.apps.Deployment;
import io.fabric8.kubernetes.api.model.apps.DeploymentBuilder;
import io.fabric8.kubernetes.api.model.apps.DeploymentSpec;
import io.fabric8.kubernetes.api.model.apps.DeploymentStatus;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.http.HttpRequest;
Expand Down Expand Up @@ -130,29 +129,6 @@ void setsSpecCustomResourceWithReflection() {
assertThat(tomcat.getSpec().getReplicas()).isEqualTo(1);
}

@Test
void setsStatusWithReflection() {
Deployment deployment = new Deployment();
DeploymentStatus status = new DeploymentStatus();
status.setReplicas(2);

ReconcilerUtils.setStatus(deployment, status);

assertThat(deployment.getStatus().getReplicas()).isEqualTo(2);
}

@Test
void getsStatusWithReflection() {
Deployment deployment = new Deployment();
DeploymentStatus status = new DeploymentStatus();
status.setReplicas(2);
deployment.setStatus(status);

var res = ReconcilerUtils.getStatus(deployment);

assertThat(((DeploymentStatus) res).getReplicas()).isEqualTo(2);
}

@Test
void loadYamlAsBuilder() {
DeploymentBuilder builder =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package io.javaoperatorsdk.operator.processing.dependent;

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

import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -99,6 +100,20 @@ public Optional<ConfigMap> getSecondaryResource(
return Optional.ofNullable(secondary);
}

@Override
protected Optional<ConfigMap> selectTargetSecondaryResource(
Set<ConfigMap> secondaryResources,
TestCustomResource primary,
Context<TestCustomResource> context) {
if (secondaryResources.size() == 1) {
return Optional.of(secondaryResources.iterator().next());
} else if (secondaryResources.isEmpty()) {
return Optional.empty();
} else {
throw new IllegalStateException();
}
}

@Override
protected void onCreated(
TestCustomResource primary, ConfigMap created, Context<TestCustomResource> context) {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
class MultipleManagedExternalDependentSameTypeIT {

@RegisterExtension
LocallyRunOperatorExtension operator =
LocallyRunOperatorExtension extension =
LocallyRunOperatorExtension.builder()
.withReconciler(new MultipleManagedExternalDependentResourceReconciler())
.build();
Expand All @@ -42,15 +42,15 @@ class MultipleManagedExternalDependentSameTypeIT {

@Test
void handlesExternalCrudOperations() {
operator.create(testResource());
extension.create(testResource());
assertResourceCreatedWithData(DEFAULT_SPEC_VALUE);

var updatedResource = testResource();
updatedResource.getSpec().setValue(UPDATED_SPEC_VALUE);
operator.replace(updatedResource);
extension.replace(updatedResource);
assertResourceCreatedWithData(UPDATED_SPEC_VALUE);

operator.delete(testResource());
extension.delete(testResource());
assertExternalResourceDeleted();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,10 @@
import java.util.Objects;

import io.fabric8.kubernetes.api.model.HasMetadata;
import io.javaoperatorsdk.operator.processing.dependent.ExternalDependentIDProvider;
import io.javaoperatorsdk.operator.processing.event.ResourceID;

public class ExternalResource {
public class ExternalResource implements ExternalDependentIDProvider<String> {

public static final String EXTERNAL_RESOURCE_NAME_DELIMITER = "#";

Expand Down Expand Up @@ -80,4 +81,9 @@ public static String toExternalResourceId(HasMetadata primary) {
+ EXTERNAL_RESOURCE_NAME_DELIMITER
+ primary.getMetadata().getNamespace();
}

@Override
public String externalResourceId() {
return id;
}
}