Skip to content

Commit

Permalink
Fix exception handling in AsyncDefaultAdmissionRequestMutator (#284)
Browse files Browse the repository at this point in the history

Signed-off-by: David Sondermann <[email protected]>
  • Loading branch information
Donnerbart authored Nov 20, 2024
1 parent b0a5838 commit 2133d28
Show file tree
Hide file tree
Showing 59 changed files with 421 additions and 297 deletions.
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) {
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) {
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

0 comments on commit 2133d28

Please sign in to comment.