diff --git a/.github/workflows/master-snapshot-release.yml b/.github/workflows/master-snapshot-release.yml index 67d63ef2..566a3d6b 100644 --- a/.github/workflows/master-snapshot-release.yml +++ b/.github/workflows/master-snapshot-release.yml @@ -27,7 +27,7 @@ jobs: cache: 'maven' - name: Run unit tests run: ./mvnw ${MAVEN_ARGS} -B test --file pom.xml - - name: Run integration tests + - name: Package run: ./mvnw ${MAVEN_ARGS} -B package --file pom.xml release-snapshot: runs-on: ubuntu-latest diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 89729ae3..a703cc67 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -31,7 +31,7 @@ jobs: ./mvnw ${MAVEN_ARGS} impsort:check --file pom.xml - name: Run unit tests run: ./mvnw ${MAVEN_ARGS} -B test --file pom.xml - spring-boot-integration-test: + spring-boot-e2e-tests: runs-on: ubuntu-latest needs: build strategy: @@ -46,59 +46,50 @@ jobs: distribution: ${{ matrix.distribution }} java-version: ${{ matrix.java }} cache: 'maven' - - name: Build - run: ./mvnw ${MAVEN_ARGS} clean install -DskipTests - - name: Kubernetes KinD Cluster - uses: container-tools/kind-action@v1 + - name: Setup Minikube-Kubernetes + uses: manusa/actions-setup-minikube@v2.7.1 with: - version: v0.11.1 - registry: true - - name: Install Cert-Manager + minikube version: v1.25.2 + kubernetes version: v1.23.6 + github token: ${{ secrets.GITHUB_TOKEN }} + driver: docker + - name: Run E2E Test run: | - OS=$(go env GOOS); ARCH=$(go env GOARCH); curl -sSL -o kubectl-cert-manager.tar.gz https://github.com/cert-manager/cert-manager/releases/download/v1.7.2/kubectl-cert_manager-$OS-$ARCH.tar.gz - tar xzf kubectl-cert-manager.tar.gz - sudo mv kubectl-cert_manager /usr/local/bin - kubectl cert-manager x install - - name: Run Integration Test + set -x + eval $(minikube -p minikube docker-env) + ./mvnw ${MAVEN_ARGS} clean install -DskipTests + cd samples/spring-boot + pwd + ./mvnw ${MAVEN_ARGS} jib:dockerBuild -DskipTests + ./mvnw ${MAVEN_ARGS} test -Pend-to-end-tests + + quarkus-e2e-tests: + runs-on: ubuntu-latest + needs: build + strategy: + matrix: + java: [ 11 ] + distribution: [ temurin ] + steps: + - uses: actions/checkout@v2 + - name: Set up Java and Maven + uses: actions/setup-java@v2 + with: + distribution: ${{ matrix.distribution }} + java-version: ${{ matrix.java }} + cache: 'maven' + - name: Setup Minikube-Kubernetes + uses: manusa/actions-setup-minikube@v2.7.1 + with: + minikube version: v1.25.2 + kubernetes version: v1.23.6 + github token: ${{ secrets.GITHUB_TOKEN }} + driver: docker + - name: Run E2E Test run: | set -x - - # Create namespace - kubectl create namespace test - kubectl config set-context --current --namespace=test - - # Generate manifests and image - cd samples/spring-boot - ./mvnw ${MAVEN_ARGS} clean install -Ddekorate.jib.registry=$KIND_REGISTRY -Ddekorate.jib.group=tests -Ddekorate.jib.version=latest -Ddekorate.jib.autoPushEnabled=true -DskipTests - - # Install manifests - kubectl apply -f target/classes/META-INF/dekorate/kubernetes.yml - - # Wait until the service is started - kubectl wait --for=condition=available --timeout=600s deployment/spring-boot-sample - - # First test: verify validating webhook works - ## Install webhook - kubectl apply -f k8s/validating-webhook-configuration.yml - - ## Wait some time to let Cert-Manager to inject issuers - sleep 10 - - ## Test Pod with missing label: it should fail - K8S_MESSAGE=$(kubectl apply -f k8s/create-pod-with-missing-label-example.yml 2>&1 || true) - if [[ $K8S_MESSAGE != *"Missing label"* ]]; then - echo "The validation webhook didn't work. Message: $K8S_MESSAGE" - exit 1 - fi - - # Second test: verify mutating webhook works - ## Install webhook - kubectl apply -f k8s/mutating-webhook-configuration.yml - - ## Test the same Pod can now be installed because the mutating webhook adds the missing label - kubectl apply -f k8s/create-pod-with-missing-label-example.yml - K8S_MESSAGE=`kubectl get pod pod-with-missing-label -o yaml | grep app.kubernetes.io/name` - if [[ $K8S_MESSAGE != *"mutation-test"* ]]; then - echo "The mutating webhook didn't work. Message: $K8S_MESSAGE" - exit 1 - fi \ No newline at end of file + eval $(minikube -p minikube docker-env) + ./mvnw clean install -DskipTests + cd samples/quarkus + ./mvnw install -Dquarkus.container-image.build=true -DskipTests + ./mvnw ${MAVEN_ARGS} test -Pend-to-end-tests \ No newline at end of file diff --git a/delme.yaml b/delme.yaml new file mode 100644 index 00000000..a3e33f63 --- /dev/null +++ b/delme.yaml @@ -0,0 +1,21 @@ +# TODO del this +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: minimal-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / + labels: + app.kubernetes.io/name: "app" +spec: + ingressClassName: nginx-example + rules: + - http: + paths: + - path: /testpath + pathType: Prefix + backend: + service: + name: test + port: + number: 80 \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0386f69e..25454665 100644 --- a/pom.xml +++ b/pom.xml @@ -134,6 +134,11 @@ certmanager-annotations ${dekorate.version} + + io.dekorate + kubernetes-annotations + ${dekorate.version} + io.dekorate jib-annotations diff --git a/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/AdmissionControllers.java b/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/AdmissionControllers.java index d062a3dc..c4a70e7b 100644 --- a/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/AdmissionControllers.java +++ b/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/AdmissionControllers.java @@ -3,7 +3,7 @@ import java.util.HashMap; import java.util.concurrent.CompletableFuture; -import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; import io.javaoperatorsdk.webhook.admission.NotAllowedException; @@ -14,68 +14,69 @@ public class AdmissionControllers { public static final String ERROR_MESSAGE = "Some error happened"; - public static final String APP_NAME_LABEL_KEY = "app.kubernetes.io/name"; + public static final String VALIDATION_TARGET_LABEL = "app.kubernetes.io/name"; + public static final String MUTATION_TARGET_LABEL = "app.kubernetes.io/id"; - public static AdmissionController mutatingController() { + public static AdmissionController mutatingController() { return new AdmissionController<>((resource, operation) -> { if (resource.getMetadata().getLabels() == null) { resource.getMetadata().setLabels(new HashMap<>()); } - resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test"); + resource.getMetadata().getLabels().putIfAbsent(MUTATION_TARGET_LABEL, "mutation-test"); return resource; }); } - public static AdmissionController validatingController() { + public static AdmissionController validatingController() { return new AdmissionController<>((resource, operation) -> { if (resource.getMetadata().getLabels() == null - || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { - throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); + || resource.getMetadata().getLabels().get(VALIDATION_TARGET_LABEL) == null) { + throw new NotAllowedException("Missing label: " + VALIDATION_TARGET_LABEL); } }); } - public static AsyncAdmissionController asyncMutatingController() { + public static AsyncAdmissionController asyncMutatingController() { return new AsyncAdmissionController<>( - (AsyncMutator) (resource, operation) -> CompletableFuture.supplyAsync(() -> { + (AsyncMutator) (resource, operation) -> CompletableFuture.supplyAsync(() -> { if (resource.getMetadata().getLabels() == null) { resource.getMetadata().setLabels(new HashMap<>()); } - resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test"); + resource.getMetadata().getLabels().putIfAbsent(MUTATION_TARGET_LABEL, "mutation-test"); return resource; })); } - public static AsyncAdmissionController asyncValidatingController() { + public static AsyncAdmissionController asyncValidatingController() { return new AsyncAdmissionController<>((resource, operation) -> { if (resource.getMetadata().getLabels() == null - || resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) { - throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY); + || resource.getMetadata().getLabels().get(VALIDATION_TARGET_LABEL) == null) { + throw new NotAllowedException("Missing label: " + VALIDATION_TARGET_LABEL); } }); } - public static AdmissionController errorMutatingController() { - return new AdmissionController<>((Validator) (resource, operation) -> { + public static AdmissionController errorMutatingController() { + return new AdmissionController<>((Validator) (resource, operation) -> { throw new IllegalStateException(ERROR_MESSAGE); }); } - public static AdmissionController errorValidatingController() { - return new AdmissionController<>((Mutator) (resource, operation) -> { + public static AdmissionController errorValidatingController() { + return new AdmissionController<>((Mutator) (resource, operation) -> { throw new IllegalStateException(ERROR_MESSAGE); }); } - public static AsyncAdmissionController errorAsyncMutatingController() { - return new AsyncAdmissionController<>((AsyncMutator) (resource, operation) -> { + public static AsyncAdmissionController errorAsyncMutatingController() { + return new AsyncAdmissionController<>((AsyncMutator) (resource, operation) -> { throw new IllegalStateException(ERROR_MESSAGE); }); } - public static AsyncAdmissionController errorAsyncValidatingController() { - return new AsyncAdmissionController<>((Validator) (resource, operation) -> { + public static AsyncAdmissionController errorAsyncValidatingController() { + return new AsyncAdmissionController<>((Validator) (resource, operation) -> { throw new IllegalStateException(ERROR_MESSAGE); }); } diff --git a/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/Utils.java b/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/Utils.java new file mode 100644 index 00000000..7a6c825b --- /dev/null +++ b/samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/Utils.java @@ -0,0 +1,63 @@ +package io.javaoperatorsdk.webhook.sample.commons; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import io.fabric8.kubernetes.api.model.networking.v1.*; +import io.fabric8.kubernetes.client.KubernetesClient; + +import static io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers.VALIDATION_TARGET_LABEL; + +public class Utils { + + public static final int SPIN_UP_GRACE_PERIOD = 120; + + public static void applyAndWait(KubernetesClient client, String path) { + try (FileInputStream fileInputStream = new FileInputStream(path)) { + applyAndWait(client, fileInputStream); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + + public static void applyAndWait(KubernetesClient client, InputStream is) { + var resources = client.load(is).get(); + client.resourceList(resources).createOrReplace(); + client.resourceList(resources).waitUntilReady(3, TimeUnit.MINUTES); + } + + public static void addRequiredLabels(Ingress ingress) { + ingress.getMetadata().setLabels(Map.of(VALIDATION_TARGET_LABEL, "val")); + } + + public static Ingress testIngress(String name) { + return new IngressBuilder() + .withNewMetadata() + .withName(name) + .endMetadata() + .withSpec(new IngressSpecBuilder() + .withIngressClassName("sample") + .withRules(new IngressRuleBuilder() + .withHttp(new HTTPIngressRuleValueBuilder() + .withPaths(new HTTPIngressPathBuilder() + .withPath("/test") + .withPathType("Prefix") + .withBackend(new IngressBackendBuilder() + .withService(new IngressServiceBackendBuilder() + .withName("service") + .withPort(new ServiceBackendPortBuilder() + .withNumber(80) + .build()) + .build()) + .build()) + .build()) + .build()) + .build()) + .build()) + .build(); + } + +} diff --git a/samples/commons/src/main/resources/admission-request.json b/samples/commons/src/main/resources/admission-request.json index 072f583a..244f1f76 100644 --- a/samples/commons/src/main/resources/admission-request.json +++ b/samples/commons/src/main/resources/admission-request.json @@ -25,91 +25,37 @@ ] }, "object": { - "apiVersion": "v1", - "kind": "Pod", + "apiVersion": "networking.k8s.io/v1", + "kind": "Ingress", "metadata": { - "generateName": "nginx-deployment-6c54bd5869-", - "creationTimestamp": null, - "labels": { - "app": "nginx", - "pod-template-hash": "2710681425" - }, + "name": "minimal-ingress", "annotations": { - "openshift.io/scc": "restricted" - }, - "ownerReferences": [ - { - "apiVersion": "extensions/v1beta1", - "kind": "ReplicaSet", - "name": "nginx-deployment-6c54bd5869", - "uid": "16c2b355-5f5d-11e8-ac91-36e6bb280816", - "controller": true, - "blockOwnerDeletion": true - } - ] + "nginx.ingress.kubernetes.io/rewrite-target": "/" + } }, "spec": { - "volumes": [ - { - "name": "default-token-tq5lq", - "secret": { - "secretName": "default-token-tq5lq" - } - } - ], - "containers": [ + "ingressClassName": "nginx-example", + "rules": [ { - "name": "nginx", - "image": "nginx:1.7.9", - "ports": [ - { - "containerPort": 80, - "protocol": "TCP" - } - ], - "resources": {}, - "volumeMounts": [ - { - "name": "default-token-tq5lq", - "readOnly": true, - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" - } - ], - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "imagePullPolicy": "IfNotPresent", - "securityContext": { - "capabilities": { - "drop": [ - "KILL", - "MKNOD", - "SETGID", - "SETUID" - ] - }, - "runAsUser": 1000080000 + "http": { + "paths": [ + { + "path": "/testpath", + "pathType": "Prefix", + "backend": { + "service": { + "name": "test", + "port": { + "number": 80 + } + } + } + } + ] } } - ], - "restartPolicy": "Always", - "terminationGracePeriodSeconds": 30, - "dnsPolicy": "ClusterFirst", - "serviceAccountName": "default", - "serviceAccount": "default", - "securityContext": { - "seLinuxOptions": { - "level": "s0:c9,c4" - }, - "fsGroup": 1000080000 - }, - "imagePullSecrets": [ - { - "name": "default-dockercfg-kksdv" - } - ], - "schedulerName": "default-scheduler" - }, - "status": {} + ] + } }, "oldObject": null } diff --git a/samples/manual-test.http b/samples/manual-test.http index d34c53bd..1f7482ef 100644 --- a/samples/manual-test.http +++ b/samples/manual-test.http @@ -1,4 +1,4 @@ -POST http://localhost:8080/mutate HTTP/1.1 +POST http://192.168.49.2:31599/validate HTTP/1.1 Content-Type: application/json { @@ -28,91 +28,37 @@ Content-Type: application/json ] }, "object": { - "apiVersion": "v1", - "kind": "Pod", + "apiVersion": "networking.k8s.io/v1", + "kind": "Ingress", "metadata": { - "generateName": "nginx-deployment-6c54bd5869-", - "creationTimestamp": null, - "labels": { - "app": "nginx", - "pod-template-hash": "2710681425" - }, + "name": "minimal-ingress", "annotations": { - "openshift.io/scc": "restricted" - }, - "ownerReferences": [ - { - "apiVersion": "extensions/v1beta1", - "kind": "ReplicaSet", - "name": "nginx-deployment-6c54bd5869", - "uid": "16c2b355-5f5d-11e8-ac91-36e6bb280816", - "controller": true, - "blockOwnerDeletion": true - } - ] + "nginx.ingress.kubernetes.io/rewrite-target": "/" + } }, "spec": { - "volumes": [ - { - "name": "default-token-tq5lq", - "secret": { - "secretName": "default-token-tq5lq" - } - } - ], - "containers": [ + "ingressClassName": "nginx-example", + "rules": [ { - "name": "nginx", - "image": "nginx:1.7.9", - "ports": [ - { - "containerPort": 80, - "protocol": "TCP" - } - ], - "resources": {}, - "volumeMounts": [ - { - "name": "default-token-tq5lq", - "readOnly": true, - "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount" - } - ], - "terminationMessagePath": "/dev/termination-log", - "terminationMessagePolicy": "File", - "imagePullPolicy": "IfNotPresent", - "securityContext": { - "capabilities": { - "drop": [ - "KILL", - "MKNOD", - "SETGID", - "SETUID" - ] - }, - "runAsUser": 1000080000 + "http": { + "paths": [ + { + "path": "/testpath", + "pathType": "Prefix", + "backend": { + "service": { + "name": "test", + "port": { + "number": 80 + } + } + } + } + ] } } - ], - "restartPolicy": "Always", - "terminationGracePeriodSeconds": 30, - "dnsPolicy": "ClusterFirst", - "serviceAccountName": "default", - "serviceAccount": "default", - "securityContext": { - "seLinuxOptions": { - "level": "s0:c9,c4" - }, - "fsGroup": 1000080000 - }, - "imagePullSecrets": [ - { - "name": "default-dockercfg-kksdv" - } - ], - "schedulerName": "default-scheduler" - }, - "status": {} + ] + } }, "oldObject": null } diff --git a/samples/quarkus/k8s/mutating-webhook-configuration.yml b/samples/quarkus/k8s/mutating-webhook-configuration.yml new file mode 100644 index 00000000..5ba8a6d9 --- /dev/null +++ b/samples/quarkus/k8s/mutating-webhook-configuration.yml @@ -0,0 +1,23 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: MutatingWebhookConfiguration +metadata: + name: "mutating.quarkus.example.com" + annotations: + cert-manager.io/inject-ca-from: default/quarkus-sample +webhooks: + - name: "mutating.quarkus.example.com" + rules: + - apiGroups: ["networking.k8s.io"] + apiVersions: ["v1"] + operations: ["*"] + resources: ["ingresses"] + scope: "Namespaced" + clientConfig: + service: + namespace: "default" + name: "quarkus-sample" + path: "/mutate" + port: 443 + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 5 \ No newline at end of file diff --git a/samples/quarkus/k8s/validating-webhook-configuration.yml b/samples/quarkus/k8s/validating-webhook-configuration.yml new file mode 100644 index 00000000..6474e024 --- /dev/null +++ b/samples/quarkus/k8s/validating-webhook-configuration.yml @@ -0,0 +1,23 @@ +apiVersion: admissionregistration.k8s.io/v1 +kind: ValidatingWebhookConfiguration +metadata: + name: "validating.quarkus.example.com" + annotations: + cert-manager.io/inject-ca-from: default/quarkus-sample +webhooks: + - name: "validating.quarkus.example.com" + rules: + - apiGroups: ["networking.k8s.io"] + apiVersions: ["v1"] + operations: ["*"] + resources: ["ingresses"] + scope: "Namespaced" + clientConfig: + service: + namespace: "default" + name: "quarkus-sample" + path: "/validate" + port: 443 + admissionReviewVersions: ["v1"] + sideEffects: None + timeoutSeconds: 5 \ No newline at end of file diff --git a/samples/quarkus/pom.xml b/samples/quarkus/pom.xml index b4b97203..591af2b6 100644 --- a/samples/quarkus/pom.xml +++ b/samples/quarkus/pom.xml @@ -46,8 +46,8 @@ ${project.version} - io.fabric8 kubernetes-client + io.fabric8 @@ -55,6 +55,19 @@ io.quarkus quarkus-kubernetes-client + + io.quarkus + quarkus-minikube + + + org.assertj + assertj-core + test + + + io.quarkus + quarkus-container-image-jib + io.quarkus quarkus-junit5 @@ -69,16 +82,31 @@ org.jboss.resteasy resteasy-jackson2-provider + + io.quarkus + quarkus-kubernetes + + + io.quarkiverse.certmanager + quarkus-certmanager + 0.0.2 + io.javaoperatorsdk.admissioncontroller.sample sample-commons ${project.version} - - - io.fabric8 - kubernetes-client - - + + + kubernetes-client + io.fabric8 + + + + + org.awaitility + awaitility + ${awaitility.version} + test diff --git a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionControllerConfig.java b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionControllerConfig.java index 28b09f0d..8fec52b1 100644 --- a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionControllerConfig.java +++ b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionControllerConfig.java @@ -3,7 +3,7 @@ import javax.inject.Named; import javax.inject.Singleton; -import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; import io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers; @@ -17,25 +17,25 @@ public class AdmissionControllerConfig { @Singleton @Named(MUTATING_CONTROLLER) - public AdmissionController mutatingController() { + public AdmissionController mutatingController() { return AdmissionControllers.mutatingController(); } @Singleton @Named(VALIDATING_CONTROLLER) - public AdmissionController validatingController() { + public AdmissionController validatingController() { return AdmissionControllers.validatingController(); } @Singleton @Named(ASYNC_MUTATING_CONTROLLER) - public AsyncAdmissionController asyncMutatingController() { + public AsyncAdmissionController asyncMutatingController() { return AdmissionControllers.asyncMutatingController(); } @Singleton @Named(ASYNC_VALIDATING_CONTROLLER) - public AsyncAdmissionController asyncValidatingController() { + public AsyncAdmissionController asyncValidatingController() { return AdmissionControllers.asyncValidatingController(); } } diff --git a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpoint.java b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpoint.java index 7ad6cef4..4f98da38 100644 --- a/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpoint.java +++ b/samples/quarkus/src/main/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpoint.java @@ -5,8 +5,8 @@ import javax.ws.rs.*; import javax.ws.rs.core.MediaType; -import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.admission.v1.AdmissionReview; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; import io.smallrye.mutiny.Uni; @@ -19,17 +19,17 @@ public class AdmissionEndpoint { public static final String ASYNC_MUTATE_PATH = "async-mutate"; public static final String ASYNC_VALIDATE_PATH = "async-validate"; - private final AdmissionController mutationController; - private final AdmissionController validationController; - private final AsyncAdmissionController asyncMutationController; - private final AsyncAdmissionController asyncValidationController; + private final AdmissionController mutationController; + private final AdmissionController validationController; + private final AsyncAdmissionController asyncMutationController; + private final AsyncAdmissionController asyncValidationController; @Inject public AdmissionEndpoint( - @Named(AdmissionControllerConfig.MUTATING_CONTROLLER) AdmissionController mutationController, - @Named(AdmissionControllerConfig.VALIDATING_CONTROLLER) AdmissionController validationController, - @Named(AdmissionControllerConfig.ASYNC_MUTATING_CONTROLLER) AsyncAdmissionController asyncMutationController, - @Named(AdmissionControllerConfig.ASYNC_VALIDATING_CONTROLLER) AsyncAdmissionController asyncValidationController) { + @Named(AdmissionControllerConfig.MUTATING_CONTROLLER) AdmissionController mutationController, + @Named(AdmissionControllerConfig.VALIDATING_CONTROLLER) AdmissionController validationController, + @Named(AdmissionControllerConfig.ASYNC_MUTATING_CONTROLLER) AsyncAdmissionController asyncMutationController, + @Named(AdmissionControllerConfig.ASYNC_VALIDATING_CONTROLLER) AsyncAdmissionController asyncValidationController) { this.mutationController = mutationController; this.validationController = validationController; this.asyncMutationController = asyncMutationController; diff --git a/samples/spring-boot/src/main/resources/kubernetes/common.yml b/samples/quarkus/src/main/kubernetes/common.yml similarity index 88% rename from samples/spring-boot/src/main/resources/kubernetes/common.yml rename to samples/quarkus/src/main/kubernetes/common.yml index 3c76a673..336ec2fe 100644 --- a/samples/spring-boot/src/main/resources/kubernetes/common.yml +++ b/samples/quarkus/src/main/kubernetes/common.yml @@ -5,4 +5,4 @@ metadata: name: pkcs12-pass data: password: c3VwZXJzZWNyZXQ= -type: Opaque +type: Opaque \ No newline at end of file diff --git a/samples/quarkus/src/main/resources/application.properties b/samples/quarkus/src/main/resources/application.properties index e69de29b..add08155 100644 --- a/samples/quarkus/src/main/resources/application.properties +++ b/samples/quarkus/src/main/resources/application.properties @@ -0,0 +1,22 @@ +#quarkus.http.insecure-requests=disabled +quarkus.http.port=80 +quarkus.http.ssl-port=443 + +quarkus.kubernetes.image-pull-policy=IfNotPresent +quarkus.kubernetes.ports."tls".container-port=443 + +## To generate the Certificate and the Issuer resources +quarkus.certificate.secret-name=tls-secret +quarkus.certificate.dns-names=quarkus-sample.default.svc,localhost +quarkus.certificate.self-signed.enabled=true +quarkus.certificate.subject.organizations=Dekorate,Community +quarkus.certificate.duration=2160h0m0s +quarkus.certificate.renew-before=360h0m0s +quarkus.certificate.private-key.algorithm=RSA +quarkus.certificate.private-key.encoding=PKCS8 +quarkus.certificate.private-key.size=2048 +quarkus.certificate.keystores.pkcs12.create=true +quarkus.certificate.keystores.pkcs12.password-secret-ref.name=pkcs12-pass +quarkus.certificate.keystores.pkcs12.password-secret-ref.key=password +quarkus.certificate.usages=server auth,client auth + diff --git a/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdditionalAdmissionConfig.java b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdditionalAdmissionConfig.java index c3c16ddb..5969c4d3 100644 --- a/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdditionalAdmissionConfig.java +++ b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdditionalAdmissionConfig.java @@ -3,7 +3,7 @@ import javax.inject.Named; import javax.inject.Singleton; -import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; import io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers; @@ -17,25 +17,25 @@ public class AdditionalAdmissionConfig { @Singleton @Named(ERROR_MUTATING_CONTROLLER) - public AdmissionController errorMutatingController() { + public AdmissionController errorMutatingController() { return AdmissionControllers.errorMutatingController(); } @Singleton @Named(ERROR_VALIDATING_CONTROLLER) - public AdmissionController errorValidatingController() { + public AdmissionController errorValidatingController() { return AdmissionControllers.errorValidatingController(); } @Singleton @Named(ERROR_ASYNC_MUTATING_CONTROLLER) - public AsyncAdmissionController errorAsyncMutatingController() { + public AsyncAdmissionController errorAsyncMutatingController() { return AdmissionControllers.errorAsyncMutatingController(); } @Singleton @Named(ERROR_ASYNC_VALIDATING_CONTROLLER) - public AsyncAdmissionController errorAsyncValidatingController() { + public AsyncAdmissionController errorAsyncValidatingController() { return AdmissionControllers.errorAsyncValidatingController(); } } diff --git a/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionAdditionalTestEndpoint.java b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionAdditionalTestEndpoint.java index fac1f02d..2b005243 100644 --- a/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionAdditionalTestEndpoint.java +++ b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionAdditionalTestEndpoint.java @@ -8,8 +8,8 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; -import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.admission.v1.AdmissionReview; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; import io.smallrye.mutiny.Uni; @@ -22,17 +22,17 @@ public class AdmissionAdditionalTestEndpoint { public static final String ERROR_MUTATE_PATH = "error-mutate"; public static final String ERROR_VALIDATE_PATH = "error-validate"; - private final AdmissionController errorMutationController; - private final AdmissionController errorValidationController; - private final AsyncAdmissionController errorAsyncMutationController; - private final AsyncAdmissionController errorAsyncValidationController; + private final AdmissionController errorMutationController; + private final AdmissionController errorValidationController; + private final AsyncAdmissionController errorAsyncMutationController; + private final AsyncAdmissionController errorAsyncValidationController; @Inject public AdmissionAdditionalTestEndpoint( - @Named(AdditionalAdmissionConfig.ERROR_MUTATING_CONTROLLER) AdmissionController errorMutationController, - @Named(AdditionalAdmissionConfig.ERROR_VALIDATING_CONTROLLER) AdmissionController errorValidationController, - @Named(AdditionalAdmissionConfig.ERROR_ASYNC_MUTATING_CONTROLLER) AsyncAdmissionController errorAsyncMutationController, - @Named(AdditionalAdmissionConfig.ERROR_ASYNC_VALIDATING_CONTROLLER) AsyncAdmissionController errorAsyncValidationController) { + @Named(AdditionalAdmissionConfig.ERROR_MUTATING_CONTROLLER) AdmissionController errorMutationController, + @Named(AdditionalAdmissionConfig.ERROR_VALIDATING_CONTROLLER) AdmissionController errorValidationController, + @Named(AdditionalAdmissionConfig.ERROR_ASYNC_MUTATING_CONTROLLER) AsyncAdmissionController errorAsyncMutationController, + @Named(AdditionalAdmissionConfig.ERROR_ASYNC_VALIDATING_CONTROLLER) AsyncAdmissionController errorAsyncValidationController) { this.errorMutationController = errorMutationController; this.errorValidationController = errorValidationController; this.errorAsyncMutationController = errorAsyncMutationController; diff --git a/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionControllerE2E.java b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionControllerE2E.java new file mode 100644 index 00000000..2c93fa10 --- /dev/null +++ b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionControllerE2E.java @@ -0,0 +1,73 @@ +package io.javaoperatorsdk.webhook.admission.sample.quarkus.admission; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.time.Duration; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.networking.v1.*; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.KubernetesClientException; + +import static io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers.MUTATION_TARGET_LABEL; +import static io.javaoperatorsdk.webhook.sample.commons.Utils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class AdmissionControllerE2E { + + private KubernetesClient client = new KubernetesClientBuilder().build(); + + @BeforeAll + static void deployService() throws IOException { + try (KubernetesClient client = new KubernetesClientBuilder().build(); + InputStream certManager = + new URL( + "https://github.com/cert-manager/cert-manager/releases/download/v1.10.1/cert-manager.yaml") + .openStream()) { + applyAndWait(client, certManager); + applyAndWait(client, "target/kubernetes/minikube.yml"); + applyAndWait(client, "k8s/validating-webhook-configuration.yml"); + applyAndWait(client, "k8s/mutating-webhook-configuration.yml"); + } + } + + @Test + void validationHook() { + var ingressWithLabel = testIngress("normal-add-test"); + addRequiredLabels(ingressWithLabel); + await().atMost(Duration.ofSeconds(SPIN_UP_GRACE_PERIOD)).untilAsserted(() -> { + Ingress res = null; + try { + // this can be since coredns in minikube can take some time + res = client.network().v1().ingresses().resource(ingressWithLabel).createOrReplace(); + } catch (KubernetesClientException e) { + } + assertThat(res).isNotNull(); + }); + assertThrows(KubernetesClientException.class, + () -> client.network().v1().ingresses().resource(testIngress("validate-test")) + .createOrReplace()); + } + + @Test + void mutationHook() { + var ingressWithLabel = testIngress("mutation-test"); + addRequiredLabels(ingressWithLabel); + await().atMost(Duration.ofSeconds(SPIN_UP_GRACE_PERIOD)).untilAsserted(() -> { + Ingress res = null; + try { + // this can be since coredns in minikube can take some time + res = client.network().v1().ingresses().resource(ingressWithLabel).createOrReplace(); + } catch (KubernetesClientException e) { + } + assertThat(res).isNotNull(); + assertThat(res.getMetadata().getLabels()).containsKey(MUTATION_TARGET_LABEL); + }); + } +} diff --git a/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpointTest.java b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpointTest.java index d9d0c5b5..9a9047df 100644 --- a/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpointTest.java +++ b/samples/quarkus/src/test/java/io/javaoperatorsdk/webhook/admission/sample/quarkus/admission/AdmissionEndpointTest.java @@ -18,7 +18,7 @@ class AdmissionEndpointTest { public static final String MUTATION_RESPONSE = - "{\"apiVersion\":\"admission.k8s.io/v1\",\"kind\":\"AdmissionReview\",\"response\":{\"allowed\":true,\"patch\":\"W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2xhYmVscy9hcHAua3ViZXJuZXRlcy5pb34xbmFtZSIsInZhbHVlIjoibXV0YXRpb24tdGVzdCJ9XQ==\",\"patchType\":\"JSONPatch\",\"uid\":\"0df28fbd-5f5f-11e8-bc74-36e6bb280816\"}}"; + "{\"apiVersion\":\"admission.k8s.io/v1\",\"kind\":\"AdmissionReview\",\"response\":{\"allowed\":true,\"patch\":\"W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2xhYmVscyIsInZhbHVlIjp7ImFwcC5rdWJlcm5ldGVzLmlvL2lkIjoibXV0YXRpb24tdGVzdCJ9fV0=\",\"patchType\":\"JSONPatch\",\"uid\":\"0df28fbd-5f5f-11e8-bc74-36e6bb280816\"}}"; public static final String VALIDATE_RESPONSE = "{\"apiVersion\":\"admission.k8s.io/v1\",\"kind\":\"AdmissionReview\",\"response\":{\"allowed\":false,\"status\":{\"apiVersion\":\"v1\",\"kind\":\"Status\",\"code\":403,\"message\":\"Missing label: app.kubernetes.io/name\"},\"uid\":\"0df28fbd-5f5f-11e8-bc74-36e6bb280816\"}}"; diff --git a/samples/spring-boot/k8s/mutating-webhook-configuration.yml b/samples/spring-boot/k8s/mutating-webhook-configuration.yml index fc424d06..a1ce2122 100644 --- a/samples/spring-boot/k8s/mutating-webhook-configuration.yml +++ b/samples/spring-boot/k8s/mutating-webhook-configuration.yml @@ -1,20 +1,20 @@ apiVersion: admissionregistration.k8s.io/v1 kind: MutatingWebhookConfiguration metadata: - name: "pod-mutating.spring-boot.example.com" + name: "mutating.spring-boot.example.com" annotations: - cert-manager.io/inject-ca-from: test/spring-boot-sample + cert-manager.io/inject-ca-from: default/spring-boot-sample webhooks: - - name: "pod-mutating.spring-boot.example.com" + - name: "mutating.spring-boot.example.com" rules: - - apiGroups: [""] + - apiGroups: ["networking.k8s.io"] apiVersions: ["v1"] - operations: ["CREATE"] - resources: ["pods"] + operations: ["*"] + resources: ["ingresses"] scope: "Namespaced" clientConfig: service: - namespace: "test" + namespace: "default" name: "spring-boot-sample" path: "/mutate" admissionReviewVersions: ["v1"] diff --git a/samples/spring-boot/k8s/validating-webhook-configuration.yml b/samples/spring-boot/k8s/validating-webhook-configuration.yml index dac1c42a..1c899f4a 100644 --- a/samples/spring-boot/k8s/validating-webhook-configuration.yml +++ b/samples/spring-boot/k8s/validating-webhook-configuration.yml @@ -1,20 +1,20 @@ apiVersion: admissionregistration.k8s.io/v1 kind: ValidatingWebhookConfiguration metadata: - name: "pod-policy.spring-boot.example.com" + name: "validating.spring-boot.example.com" annotations: - cert-manager.io/inject-ca-from: test/spring-boot-sample + cert-manager.io/inject-ca-from: default/spring-boot-sample webhooks: - - name: "pod-policy.spring-boot.example.com" + - name: "validating.spring-boot.example.com" rules: - - apiGroups: [""] + - apiGroups: ["networking.k8s.io"] apiVersions: ["v1"] - operations: ["CREATE"] - resources: ["pods"] + operations: ["*"] + resources: ["ingresses"] scope: "Namespaced" clientConfig: service: - namespace: "test" + namespace: "default" name: "spring-boot-sample" path: "/validate" admissionReviewVersions: ["v1"] diff --git a/samples/spring-boot/pom.xml b/samples/spring-boot/pom.xml index c94aea0a..2364a341 100644 --- a/samples/spring-boot/pom.xml +++ b/samples/spring-boot/pom.xml @@ -76,6 +76,12 @@ assertj-core test + + org.awaitility + awaitility + ${awaitility.version} + test + @@ -89,7 +95,7 @@ gcr.io/distroless/java:11 - spring-boot-sample + test/spring-boot-sample:${project.version} diff --git a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionConfig.java b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionConfig.java index 42b2f094..9e6d91ba 100644 --- a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionConfig.java +++ b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionConfig.java @@ -3,7 +3,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; import io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers; @@ -12,22 +12,22 @@ public class AdmissionConfig { @Bean - public AdmissionController mutatingController() { + public AdmissionController mutatingController() { return AdmissionControllers.mutatingController(); } @Bean - public AdmissionController validatingController() { + public AdmissionController validatingController() { return AdmissionControllers.validatingController(); } @Bean - public AsyncAdmissionController asyncMutatingController() { + public AsyncAdmissionController asyncMutatingController() { return AdmissionControllers.asyncMutatingController(); } @Bean - public AsyncAdmissionController asyncValidatingController() { + public AsyncAdmissionController asyncValidatingController() { return AdmissionControllers.asyncValidatingController(); } } diff --git a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpoint.java b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpoint.java index 5e5b7b2a..ebdbfef1 100644 --- a/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpoint.java +++ b/samples/spring-boot/src/main/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpoint.java @@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.admission.v1.AdmissionReview; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; @@ -22,17 +22,17 @@ public class AdmissionEndpoint { public static final String ASYNC_MUTATE_PATH = "async-mutate"; public static final String ASYNC_VALIDATE_PATH = "async-validate"; - private final AdmissionController mutatingController; - private final AdmissionController validatingController; - private final AsyncAdmissionController asyncMutatingController; - private final AsyncAdmissionController asyncValidatingController; + private final AdmissionController mutatingController; + private final AdmissionController validatingController; + private final AsyncAdmissionController asyncMutatingController; + private final AsyncAdmissionController asyncValidatingController; @Autowired public AdmissionEndpoint( - @Qualifier("mutatingController") AdmissionController mutationController, - @Qualifier("validatingController") AdmissionController validatingController, - @Qualifier("asyncMutatingController") AsyncAdmissionController asyncMutatingController, - @Qualifier("asyncValidatingController") AsyncAdmissionController asyncValidatingController) { + @Qualifier("mutatingController") AdmissionController mutationController, + @Qualifier("validatingController") AdmissionController validatingController, + @Qualifier("asyncMutatingController") AsyncAdmissionController asyncMutatingController, + @Qualifier("asyncValidatingController") AsyncAdmissionController asyncValidatingController) { this.mutatingController = mutationController; this.validatingController = validatingController; this.asyncMutatingController = asyncMutatingController; diff --git a/samples/spring-boot/src/main/resources/application.properties b/samples/spring-boot/src/main/resources/application.properties index 9bc6156a..9587382a 100644 --- a/samples/spring-boot/src/main/resources/application.properties +++ b/samples/spring-boot/src/main/resources/application.properties @@ -5,10 +5,10 @@ server.ssl.key-store-type=PKCS12 dekorate.jib.from=openjdk:11 ## To include the keystore secret dekorate.options.input-path=kubernetes - +dekorate.jib.group=test ## To generate the Certificate and the Issuer resources dekorate.certificate.secret-name=tls-secret -dekorate.certificate.dnsNames=spring-boot-sample.test.svc,localhost +dekorate.certificate.dnsNames=spring-boot-sample.default.svc,localhost dekorate.certificate.self-signed.enabled=true dekorate.certificate.subject.organizations=Dekorate,Community dekorate.certificate.duration=2160h0m0s diff --git a/samples/spring-boot/src/main/resources/kubernetes/common.yaml b/samples/spring-boot/src/main/resources/kubernetes/common.yaml new file mode 100644 index 00000000..336ec2fe --- /dev/null +++ b/samples/spring-boot/src/main/resources/kubernetes/common.yaml @@ -0,0 +1,8 @@ +--- +apiVersion: v1 +kind: Secret +metadata: + name: pkcs12-pass +data: + password: c3VwZXJzZWNyZXQ= +type: Opaque \ No newline at end of file diff --git a/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdditionalAdmissionConfig.java b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdditionalAdmissionConfig.java index 7bc198ec..6e20545a 100644 --- a/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdditionalAdmissionConfig.java +++ b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdditionalAdmissionConfig.java @@ -3,7 +3,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; import io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers; @@ -12,22 +12,22 @@ public class AdditionalAdmissionConfig { @Bean - public AdmissionController errorMutatingController() { + public AdmissionController errorMutatingController() { return AdmissionControllers.errorMutatingController(); } @Bean - public AdmissionController errorValidatingController() { + public AdmissionController errorValidatingController() { return AdmissionControllers.errorValidatingController(); } @Bean - public AsyncAdmissionController errorAsyncMutatingController() { + public AsyncAdmissionController errorAsyncMutatingController() { return AdmissionControllers.errorAsyncMutatingController(); } @Bean - public AsyncAdmissionController errorAsyncValidatingController() { + public AsyncAdmissionController errorAsyncValidatingController() { return AdmissionControllers.errorAsyncValidatingController(); } } diff --git a/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionAdditionalTestEndpoint.java b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionAdditionalTestEndpoint.java index 0b2ba6eb..172e7ff8 100644 --- a/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionAdditionalTestEndpoint.java +++ b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionAdditionalTestEndpoint.java @@ -7,8 +7,8 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.RestController; -import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.admission.v1.AdmissionReview; +import io.fabric8.kubernetes.api.model.networking.v1.Ingress; import io.javaoperatorsdk.webhook.admission.AdmissionController; import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController; @@ -22,17 +22,17 @@ public class AdmissionAdditionalTestEndpoint { public static final String ERROR_ASYNC_MUTATE_PATH = "error-async-mutate"; public static final String ERROR_ASYNC_VALIDATE_PATH = "error-async-validate"; - private final AdmissionController errorMutatingController; - private final AdmissionController errorValidatingController; - private final AsyncAdmissionController errorAsyncMutatingController; - private final AsyncAdmissionController errorAsyncValidatingController; + private final AdmissionController errorMutatingController; + private final AdmissionController errorValidatingController; + private final AsyncAdmissionController errorAsyncMutatingController; + private final AsyncAdmissionController errorAsyncValidatingController; @Autowired public AdmissionAdditionalTestEndpoint( - @Qualifier("errorMutatingController") AdmissionController errorMutatingController, - @Qualifier("errorValidatingController") AdmissionController errorValidatingController, - @Qualifier("errorAsyncMutatingController") AsyncAdmissionController errorAsyncMutatingController, - @Qualifier("errorAsyncValidatingController") AsyncAdmissionController errorAsyncValidatingController) { + @Qualifier("errorMutatingController") AdmissionController errorMutatingController, + @Qualifier("errorValidatingController") AdmissionController errorValidatingController, + @Qualifier("errorAsyncMutatingController") AsyncAdmissionController errorAsyncMutatingController, + @Qualifier("errorAsyncValidatingController") AsyncAdmissionController errorAsyncValidatingController) { this.errorMutatingController = errorMutatingController; this.errorValidatingController = errorValidatingController; this.errorAsyncMutatingController = errorAsyncMutatingController; diff --git a/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionControllerE2E.java b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionControllerE2E.java new file mode 100644 index 00000000..6c08d2cc --- /dev/null +++ b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionControllerE2E.java @@ -0,0 +1,73 @@ +package io.javaoperatorsdk.webhook.sample.springboot.admission; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.time.Duration; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import io.fabric8.kubernetes.api.model.networking.v1.*; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.KubernetesClientBuilder; +import io.fabric8.kubernetes.client.KubernetesClientException; + +import static io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers.MUTATION_TARGET_LABEL; +import static io.javaoperatorsdk.webhook.sample.commons.Utils.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class AdmissionControllerE2E { + + private KubernetesClient client = new KubernetesClientBuilder().build(); + + @BeforeAll + static void deployService() throws IOException { + try (KubernetesClient client = new KubernetesClientBuilder().build(); + InputStream certManager = + new URL( + "https://github.com/cert-manager/cert-manager/releases/download/v1.10.1/cert-manager.yaml") + .openStream()) { + applyAndWait(client, certManager); + applyAndWait(client, "target/classes/META-INF/dekorate/kubernetes.yml"); + applyAndWait(client, "k8s/validating-webhook-configuration.yml"); + applyAndWait(client, "k8s/mutating-webhook-configuration.yml"); + } + } + + @Test + void validationHook() { + var ingressWithLabel = testIngress("normal-add-test"); + addRequiredLabels(ingressWithLabel); + await().atMost(Duration.ofSeconds(SPIN_UP_GRACE_PERIOD)).untilAsserted(() -> { + Ingress res = null; + try { + // this can be since coredns in minikube can take some time + res = client.network().v1().ingresses().resource(ingressWithLabel).createOrReplace(); + } catch (KubernetesClientException e) { + } + assertThat(res).isNotNull(); + }); + assertThrows(KubernetesClientException.class, + () -> client.network().v1().ingresses().resource(testIngress("validate-test")) + .createOrReplace()); + } + + @Test + void mutationHook() { + var ingressWithLabel = testIngress("mutation-test"); + addRequiredLabels(ingressWithLabel); + await().atMost(Duration.ofSeconds(SPIN_UP_GRACE_PERIOD)).untilAsserted(() -> { + Ingress res = null; + try { + res = client.network().v1().ingresses().resource(ingressWithLabel).createOrReplace(); + } catch (KubernetesClientException e) { + } + assertThat(res).isNotNull(); + assertThat(res.getMetadata().getLabels()).containsKey(MUTATION_TARGET_LABEL); + }); + } + +} diff --git a/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpointTest.java b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpointTest.java index 505d27e3..07fcd378 100644 --- a/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpointTest.java +++ b/samples/spring-boot/src/test/java/io/javaoperatorsdk/webhook/sample/springboot/admission/AdmissionEndpointTest.java @@ -77,7 +77,7 @@ public void testMutate(String path) { .body(request()) .exchange() .expectStatus().isOk().expectBody().json( - "{\"apiVersion\":\"admission.k8s.io/v1\",\"kind\":\"AdmissionReview\",\"response\":{\"allowed\":true,\"patch\":\"W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2xhYmVscy9hcHAua3ViZXJuZXRlcy5pb34xbmFtZSIsInZhbHVlIjoibXV0YXRpb24tdGVzdCJ9XQ==\",\"patchType\":\"JSONPatch\",\"uid\":\"0df28fbd-5f5f-11e8-bc74-36e6bb280816\"}}"); + "{\"apiVersion\":\"admission.k8s.io/v1\",\"kind\":\"AdmissionReview\",\"response\":{\"allowed\":true,\"patch\":\"W3sib3AiOiJhZGQiLCJwYXRoIjoiL21ldGFkYXRhL2xhYmVscyIsInZhbHVlIjp7ImFwcC5rdWJlcm5ldGVzLmlvL2lkIjoibXV0YXRpb24tdGVzdCJ9fV0=\",\"patchType\":\"JSONPatch\",\"uid\":\"0df28fbd-5f5f-11e8-bc74-36e6bb280816\"}}"); } public void testValidate(String path) {