Skip to content
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

Fix exception handling in AsyncDefaultAdmissionRequestMutator #284

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
Expand Up @@ -25,10 +25,9 @@ public AdmissionController(AdmissionRequestHandler admissionRequestHandler) {

public AdmissionReview handle(AdmissionReview admissionReview) {
var response = admissionRequestHandler.handle(admissionReview.getRequest());
AdmissionReview responseAdmissionReview = new AdmissionReview();
var responseAdmissionReview = new AdmissionReview();
responseAdmissionReview.setResponse(response);
response.setUid(admissionReview.getRequest().getUid());
return responseAdmissionReview;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,4 @@
public interface AdmissionRequestHandler {

AdmissionResponse handle(AdmissionRequest admissionRequest);

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ public class AdmissionUtils {

private AdmissionUtils() {}

public static AdmissionResponse allowedAdmissionResponse() {
var admissionResponse = new AdmissionResponse();
admissionResponse.setAllowed(true);
return admissionResponse;
}

public static AdmissionResponse notAllowedExceptionToAdmissionResponse(
NotAllowedException notAllowedException) {
AdmissionResponse admissionResponse = new AdmissionResponse();
var admissionResponse = new AdmissionResponse();
admissionResponse.setAllowed(false);
admissionResponse.setStatus(notAllowedException.getStatus());
return admissionResponse;
Expand All @@ -33,17 +39,16 @@ public static KubernetesResource getTargetResource(AdmissionRequest admissionReq

public static AdmissionResponse admissionResponseFromMutation(KubernetesResource originalResource,
KubernetesResource mutatedResource) {
AdmissionResponse admissionResponse = new AdmissionResponse();
var admissionResponse = new AdmissionResponse();
admissionResponse.setAllowed(true);
admissionResponse.setPatchType(JSON_PATCH);
var originalResNode = mapper.valueToTree(originalResource);
var mutatedResNode = mapper.valueToTree(mutatedResource);

var diff = JsonDiff.asJson(originalResNode, mutatedResNode);
String base64Diff =
var base64Diff =
Base64.getEncoder().encodeToString(diff.toString().getBytes(StandardCharsets.UTF_8));
admissionResponse.setPatch(base64Diff);
return admissionResponse;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,22 @@
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionReview;
import io.javaoperatorsdk.webhook.admission.mutation.AsyncDefaultAdmissionRequestMutator;
import io.javaoperatorsdk.webhook.admission.mutation.AsyncMutator;
import io.javaoperatorsdk.webhook.admission.mutation.Mutator;
import io.javaoperatorsdk.webhook.admission.validation.AsyncDefaultAdmissionRequestValidator;
import io.javaoperatorsdk.webhook.admission.validation.Validator;

public class AsyncAdmissionController<T extends KubernetesResource> {

private final AsyncAdmissionRequestHandler requestHandler;

public AsyncAdmissionController(AsyncMutator<T> mutator) {
public AsyncAdmissionController(Mutator<T> mutator) {
this(new AsyncDefaultAdmissionRequestMutator<>(mutator));
}

public AsyncAdmissionController(AsyncMutator<T> asyncMutator) {
this(new AsyncDefaultAdmissionRequestMutator<>(asyncMutator));
}

public AsyncAdmissionController(Validator<T> validator) {
this(new AsyncDefaultAdmissionRequestValidator<>(validator));
}
Expand All @@ -28,11 +33,10 @@ public AsyncAdmissionController(AsyncAdmissionRequestHandler requestHandler) {
public CompletionStage<AdmissionReview> handle(AdmissionReview admissionReview) {
return requestHandler.handle(admissionReview.getRequest())
.thenApply(r -> {
AdmissionReview responseAdmissionReview = new AdmissionReview();
var responseAdmissionReview = new AdmissionReview();
responseAdmissionReview.setResponse(r);
r.setUid(admissionReview.getRequest().getUid());
return responseAdmissionReview;
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,4 @@
public interface AsyncAdmissionRequestHandler {

CompletionStage<AdmissionResponse> handle(AdmissionRequest admissionRequest);

}
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.javaoperatorsdk.webhook.admission.mutation;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.CompletionStage;

import io.fabric8.kubernetes.api.model.KubernetesResource;
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionRequest;
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionResponse;
import io.javaoperatorsdk.webhook.admission.AdmissionUtils;
import io.javaoperatorsdk.webhook.admission.AsyncAdmissionRequestHandler;
import io.javaoperatorsdk.webhook.admission.NotAllowedException;
import io.javaoperatorsdk.webhook.admission.Operation;
Expand All @@ -15,37 +15,48 @@

import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.admissionResponseFromMutation;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.getTargetResource;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.notAllowedExceptionToAdmissionResponse;

public class AsyncDefaultAdmissionRequestMutator<T extends KubernetesResource>
implements AsyncAdmissionRequestHandler {

private final AsyncMutator<T> mutator;
private final AsyncMutator<T> asyncMutator;
private final Cloner<T> cloner;

public AsyncDefaultAdmissionRequestMutator(AsyncMutator<T> mutator) {
public AsyncDefaultAdmissionRequestMutator(Mutator<T> mutator) {
this(mutator, new ObjectMapperCloner<>());
}

public AsyncDefaultAdmissionRequestMutator(AsyncMutator<T> mutator, Cloner<T> cloner) {
this.mutator = mutator;
public AsyncDefaultAdmissionRequestMutator(Mutator<T> mutator, Cloner<T> cloner) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the internal convenience constructor to enable the use of a Mutator for an async endpoint.

this((AsyncMutator<T>) (resource, operation) -> CompletableFuture.supplyAsync(
() -> mutator.mutate(resource, operation)), cloner);
}

public AsyncDefaultAdmissionRequestMutator(AsyncMutator<T> asyncMutator) {
this(asyncMutator, new ObjectMapperCloner<>());
}

public AsyncDefaultAdmissionRequestMutator(AsyncMutator<T> asyncMutator, Cloner<T> cloner) {
this.asyncMutator = asyncMutator;
this.cloner = cloner;
}

@Override
@SuppressWarnings("unchecked")
public CompletionStage<AdmissionResponse> handle(AdmissionRequest admissionRequest) {
Operation operation = Operation.valueOf(admissionRequest.getOperation());
var operation = Operation.valueOf(admissionRequest.getOperation());
var originalResource = (T) getTargetResource(admissionRequest, operation);
var clonedResource = cloner.clone(originalResource);
CompletionStage<AdmissionResponse> admissionResponse;
try {
var mutatedResource = mutator.mutate(clonedResource, operation);
admissionResponse =
mutatedResource.thenApply(mr -> admissionResponseFromMutation(originalResource, mr));
} catch (NotAllowedException e) {
admissionResponse = CompletableFuture
.supplyAsync(() -> AdmissionUtils.notAllowedExceptionToAdmissionResponse(e));
}
return admissionResponse;
return asyncMutator.mutate(clonedResource, operation)
.thenApply(resource -> admissionResponseFromMutation(originalResource, resource))
.exceptionally(e -> {
if (e instanceof CompletionException) {
Copy link
Contributor Author

@Donnerbart Donnerbart Nov 19, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the actual fix.

if (e.getCause() instanceof NotAllowedException) {
return notAllowedExceptionToAdmissionResponse((NotAllowedException) e.getCause());
}
throw new IllegalStateException(e.getCause());
}
throw new IllegalStateException(e);
});
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@
public interface AsyncMutator<T extends KubernetesResource> {

CompletionStage<T> mutate(T resource, Operation operation) throws NotAllowedException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionRequest;
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionResponse;
import io.javaoperatorsdk.webhook.admission.AdmissionRequestHandler;
import io.javaoperatorsdk.webhook.admission.AdmissionUtils;
import io.javaoperatorsdk.webhook.admission.NotAllowedException;
import io.javaoperatorsdk.webhook.admission.Operation;
import io.javaoperatorsdk.webhook.clone.Cloner;
import io.javaoperatorsdk.webhook.clone.ObjectMapperCloner;

import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.admissionResponseFromMutation;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.getTargetResource;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.notAllowedExceptionToAdmissionResponse;

public class DefaultAdmissionRequestMutator<T extends KubernetesResource>
implements AdmissionRequestHandler {
Expand All @@ -29,18 +29,16 @@ public DefaultAdmissionRequestMutator(Mutator<T> mutator, Cloner<T> cloner) {
}

@Override
@SuppressWarnings("unchecked")
public AdmissionResponse handle(AdmissionRequest admissionRequest) {
Operation operation = Operation.valueOf(admissionRequest.getOperation());
var operation = Operation.valueOf(admissionRequest.getOperation());
var originalResource = (T) getTargetResource(admissionRequest, operation);
var clonedResource = cloner.clone(originalResource);
AdmissionResponse admissionResponse;
try {
var mutatedResource = mutator.mutate(clonedResource, operation);
admissionResponse = admissionResponseFromMutation(originalResource, mutatedResource);
return admissionResponseFromMutation(originalResource, mutatedResource);
} catch (NotAllowedException e) {
admissionResponse = AdmissionUtils.notAllowedExceptionToAdmissionResponse(e);
return notAllowedExceptionToAdmissionResponse(e);
}
return admissionResponse;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,4 @@
public interface Mutator<T extends KubernetesResource> {

T mutate(T resource, Operation operation) throws NotAllowedException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import io.fabric8.kubernetes.api.model.KubernetesResource;
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionRequest;
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionResponse;
import io.javaoperatorsdk.webhook.admission.AdmissionUtils;
import io.javaoperatorsdk.webhook.admission.AsyncAdmissionRequestHandler;
import io.javaoperatorsdk.webhook.admission.NotAllowedException;
import io.javaoperatorsdk.webhook.admission.Operation;

import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.allowedAdmissionResponse;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.getTargetResource;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.notAllowedExceptionToAdmissionResponse;

public class AsyncDefaultAdmissionRequestValidator<T extends KubernetesResource>
implements AsyncAdmissionRequestHandler {
Expand All @@ -26,30 +27,20 @@ public AsyncDefaultAdmissionRequestValidator(Validator<T> validator) {
@Override
@SuppressWarnings("unchecked")
public CompletionStage<AdmissionResponse> handle(AdmissionRequest admissionRequest) {
Operation operation = Operation.valueOf(admissionRequest.getOperation());
var operation = Operation.valueOf(admissionRequest.getOperation());
var originalResource = (T) getTargetResource(admissionRequest, operation);

var asyncValidate =
CompletableFuture.runAsync(() -> validator.validate(originalResource, operation));
return asyncValidate
.thenApply(v -> allowedAdmissionResponse())
.exceptionally(e -> {
if (e instanceof CompletionException) {
if (e.getCause() instanceof NotAllowedException) {
return AdmissionUtils.notAllowedExceptionToAdmissionResponse(
(NotAllowedException) e.getCause());
} else {
throw new IllegalStateException(e.getCause());
return notAllowedExceptionToAdmissionResponse((NotAllowedException) e.getCause());
}
} else {
throw new IllegalStateException(e);
throw new IllegalStateException(e.getCause());
}
throw new IllegalStateException(e);
});
}

private AdmissionResponse allowedAdmissionResponse() {
AdmissionResponse admissionResponse = new AdmissionResponse();
admissionResponse.setAllowed(true);
return admissionResponse;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionRequest;
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionResponse;
import io.javaoperatorsdk.webhook.admission.AdmissionRequestHandler;
import io.javaoperatorsdk.webhook.admission.AdmissionUtils;
import io.javaoperatorsdk.webhook.admission.NotAllowedException;
import io.javaoperatorsdk.webhook.admission.Operation;

import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.allowedAdmissionResponse;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.getTargetResource;
import static io.javaoperatorsdk.webhook.admission.AdmissionUtils.notAllowedExceptionToAdmissionResponse;

public class DefaultAdmissionRequestValidator<T extends KubernetesResource>
implements AdmissionRequestHandler {
Expand All @@ -20,22 +21,15 @@ public DefaultAdmissionRequestValidator(Validator<T> validator) {
}

@Override
@SuppressWarnings("unchecked")
public AdmissionResponse handle(AdmissionRequest admissionRequest) {
Operation operation = Operation.valueOf(admissionRequest.getOperation());
var operation = Operation.valueOf(admissionRequest.getOperation());
var originalResource = (T) getTargetResource(admissionRequest, operation);
AdmissionResponse admissionResponse;
try {
validator.validate(originalResource, operation);
admissionResponse = allowedAdmissionResponse();
return allowedAdmissionResponse();
} catch (NotAllowedException e) {
admissionResponse = AdmissionUtils.notAllowedExceptionToAdmissionResponse(e);
return notAllowedExceptionToAdmissionResponse(e);
}
return admissionResponse;
}

private AdmissionResponse allowedAdmissionResponse() {
AdmissionResponse admissionResponse = new AdmissionResponse();
admissionResponse.setAllowed(true);
return admissionResponse;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,4 @@
public interface Validator<T extends KubernetesResource> {

void validate(T resource, Operation operation) throws NotAllowedException;

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,4 @@ public interface Cloner<R> {
* @return a deep copy of the given object if it isn't {@code null}, {@code null} otherwise
*/
R clone(R object);

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class ObjectMapperCloner<T> implements Cloner<T> {
private final ObjectMapper objectMapper = new ObjectMapper();

@Override
@SuppressWarnings("unchecked")
public T clone(T object) {
if (object == null) {
return null;
Expand Down
Loading