Skip to content

Commit ace65ab

Browse files
authored
feat: E2E for admission controls (#49)
1 parent c88ecf1 commit ace65ab

File tree

30 files changed

+544
-315
lines changed

30 files changed

+544
-315
lines changed

.github/workflows/master-snapshot-release.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
cache: 'maven'
2828
- name: Run unit tests
2929
run: ./mvnw ${MAVEN_ARGS} -B test --file pom.xml
30-
- name: Run integration tests
30+
- name: Package
3131
run: ./mvnw ${MAVEN_ARGS} -B package --file pom.xml
3232
release-snapshot:
3333
runs-on: ubuntu-latest

.github/workflows/pr.yml

+44-53
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
./mvnw ${MAVEN_ARGS} impsort:check --file pom.xml
3232
- name: Run unit tests
3333
run: ./mvnw ${MAVEN_ARGS} -B test --file pom.xml
34-
spring-boot-integration-test:
34+
spring-boot-e2e-tests:
3535
runs-on: ubuntu-latest
3636
needs: build
3737
strategy:
@@ -46,59 +46,50 @@ jobs:
4646
distribution: ${{ matrix.distribution }}
4747
java-version: ${{ matrix.java }}
4848
cache: 'maven'
49-
- name: Build
50-
run: ./mvnw ${MAVEN_ARGS} clean install -DskipTests
51-
- name: Kubernetes KinD Cluster
52-
uses: container-tools/kind-action@v1
49+
- name: Setup Minikube-Kubernetes
50+
uses: manusa/[email protected]
5351
with:
54-
version: v0.11.1
55-
registry: true
56-
- name: Install Cert-Manager
52+
minikube version: v1.25.2
53+
kubernetes version: v1.23.6
54+
github token: ${{ secrets.GITHUB_TOKEN }}
55+
driver: docker
56+
- name: Run E2E Test
5757
run: |
58-
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
59-
tar xzf kubectl-cert-manager.tar.gz
60-
sudo mv kubectl-cert_manager /usr/local/bin
61-
kubectl cert-manager x install
62-
- name: Run Integration Test
58+
set -x
59+
eval $(minikube -p minikube docker-env)
60+
./mvnw ${MAVEN_ARGS} clean install -DskipTests
61+
cd samples/spring-boot
62+
pwd
63+
./mvnw ${MAVEN_ARGS} jib:dockerBuild -DskipTests
64+
./mvnw ${MAVEN_ARGS} test -Pend-to-end-tests
65+
66+
quarkus-e2e-tests:
67+
runs-on: ubuntu-latest
68+
needs: build
69+
strategy:
70+
matrix:
71+
java: [ 11 ]
72+
distribution: [ temurin ]
73+
steps:
74+
- uses: actions/checkout@v2
75+
- name: Set up Java and Maven
76+
uses: actions/setup-java@v2
77+
with:
78+
distribution: ${{ matrix.distribution }}
79+
java-version: ${{ matrix.java }}
80+
cache: 'maven'
81+
- name: Setup Minikube-Kubernetes
82+
uses: manusa/[email protected]
83+
with:
84+
minikube version: v1.25.2
85+
kubernetes version: v1.23.6
86+
github token: ${{ secrets.GITHUB_TOKEN }}
87+
driver: docker
88+
- name: Run E2E Test
6389
run: |
6490
set -x
65-
66-
# Create namespace
67-
kubectl create namespace test
68-
kubectl config set-context --current --namespace=test
69-
70-
# Generate manifests and image
71-
cd samples/spring-boot
72-
./mvnw ${MAVEN_ARGS} clean install -Ddekorate.jib.registry=$KIND_REGISTRY -Ddekorate.jib.group=tests -Ddekorate.jib.version=latest -Ddekorate.jib.autoPushEnabled=true -DskipTests
73-
74-
# Install manifests
75-
kubectl apply -f target/classes/META-INF/dekorate/kubernetes.yml
76-
77-
# Wait until the service is started
78-
kubectl wait --for=condition=available --timeout=600s deployment/spring-boot-sample
79-
80-
# First test: verify validating webhook works
81-
## Install webhook
82-
kubectl apply -f k8s/validating-webhook-configuration.yml
83-
84-
## Wait some time to let Cert-Manager to inject issuers
85-
sleep 10
86-
87-
## Test Pod with missing label: it should fail
88-
K8S_MESSAGE=$(kubectl apply -f k8s/create-pod-with-missing-label-example.yml 2>&1 || true)
89-
if [[ $K8S_MESSAGE != *"Missing label"* ]]; then
90-
echo "The validation webhook didn't work. Message: $K8S_MESSAGE"
91-
exit 1
92-
fi
93-
94-
# Second test: verify mutating webhook works
95-
## Install webhook
96-
kubectl apply -f k8s/mutating-webhook-configuration.yml
97-
98-
## Test the same Pod can now be installed because the mutating webhook adds the missing label
99-
kubectl apply -f k8s/create-pod-with-missing-label-example.yml
100-
K8S_MESSAGE=`kubectl get pod pod-with-missing-label -o yaml | grep app.kubernetes.io/name`
101-
if [[ $K8S_MESSAGE != *"mutation-test"* ]]; then
102-
echo "The mutating webhook didn't work. Message: $K8S_MESSAGE"
103-
exit 1
104-
fi
91+
eval $(minikube -p minikube docker-env)
92+
./mvnw clean install -DskipTests
93+
cd samples/quarkus
94+
./mvnw install -Dquarkus.container-image.build=true -DskipTests
95+
./mvnw ${MAVEN_ARGS} test -Pend-to-end-tests

delme.yaml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# TODO del this
2+
apiVersion: networking.k8s.io/v1
3+
kind: Ingress
4+
metadata:
5+
name: minimal-ingress
6+
annotations:
7+
nginx.ingress.kubernetes.io/rewrite-target: /
8+
labels:
9+
app.kubernetes.io/name: "app"
10+
spec:
11+
ingressClassName: nginx-example
12+
rules:
13+
- http:
14+
paths:
15+
- path: /testpath
16+
pathType: Prefix
17+
backend:
18+
service:
19+
name: test
20+
port:
21+
number: 80

pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,11 @@
134134
<artifactId>certmanager-annotations</artifactId>
135135
<version>${dekorate.version}</version>
136136
</dependency>
137+
<dependency>
138+
<groupId>io.dekorate</groupId>
139+
<artifactId>kubernetes-annotations</artifactId>
140+
<version>${dekorate.version}</version>
141+
</dependency>
137142
<dependency>
138143
<groupId>io.dekorate</groupId>
139144
<artifactId>jib-annotations</artifactId>

samples/commons/src/main/java/io/javaoperatorsdk/webhook/sample/commons/AdmissionControllers.java

+22-21
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import java.util.HashMap;
44
import java.util.concurrent.CompletableFuture;
55

6-
import io.fabric8.kubernetes.api.model.Pod;
6+
import io.fabric8.kubernetes.api.model.networking.v1.Ingress;
77
import io.javaoperatorsdk.webhook.admission.AdmissionController;
88
import io.javaoperatorsdk.webhook.admission.AsyncAdmissionController;
99
import io.javaoperatorsdk.webhook.admission.NotAllowedException;
@@ -14,68 +14,69 @@
1414
public class AdmissionControllers {
1515

1616
public static final String ERROR_MESSAGE = "Some error happened";
17-
public static final String APP_NAME_LABEL_KEY = "app.kubernetes.io/name";
17+
public static final String VALIDATION_TARGET_LABEL = "app.kubernetes.io/name";
18+
public static final String MUTATION_TARGET_LABEL = "app.kubernetes.io/id";
1819

19-
public static AdmissionController<Pod> mutatingController() {
20+
public static AdmissionController<Ingress> mutatingController() {
2021
return new AdmissionController<>((resource, operation) -> {
2122
if (resource.getMetadata().getLabels() == null) {
2223
resource.getMetadata().setLabels(new HashMap<>());
2324
}
24-
resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test");
25+
resource.getMetadata().getLabels().putIfAbsent(MUTATION_TARGET_LABEL, "mutation-test");
2526
return resource;
2627
});
2728
}
2829

29-
public static AdmissionController<Pod> validatingController() {
30+
public static AdmissionController<Ingress> validatingController() {
3031
return new AdmissionController<>((resource, operation) -> {
3132
if (resource.getMetadata().getLabels() == null
32-
|| resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) {
33-
throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY);
33+
|| resource.getMetadata().getLabels().get(VALIDATION_TARGET_LABEL) == null) {
34+
throw new NotAllowedException("Missing label: " + VALIDATION_TARGET_LABEL);
3435
}
3536
});
3637
}
3738

38-
public static AsyncAdmissionController<Pod> asyncMutatingController() {
39+
public static AsyncAdmissionController<Ingress> asyncMutatingController() {
3940
return new AsyncAdmissionController<>(
40-
(AsyncMutator<Pod>) (resource, operation) -> CompletableFuture.supplyAsync(() -> {
41+
(AsyncMutator<Ingress>) (resource, operation) -> CompletableFuture.supplyAsync(() -> {
4142
if (resource.getMetadata().getLabels() == null) {
4243
resource.getMetadata().setLabels(new HashMap<>());
4344
}
44-
resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test");
45+
resource.getMetadata().getLabels().putIfAbsent(MUTATION_TARGET_LABEL, "mutation-test");
4546
return resource;
4647
}));
4748
}
4849

49-
public static AsyncAdmissionController<Pod> asyncValidatingController() {
50+
public static AsyncAdmissionController<Ingress> asyncValidatingController() {
5051
return new AsyncAdmissionController<>((resource, operation) -> {
5152
if (resource.getMetadata().getLabels() == null
52-
|| resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) {
53-
throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY);
53+
|| resource.getMetadata().getLabels().get(VALIDATION_TARGET_LABEL) == null) {
54+
throw new NotAllowedException("Missing label: " + VALIDATION_TARGET_LABEL);
5455
}
5556
});
5657
}
5758

5859

59-
public static AdmissionController<Pod> errorMutatingController() {
60-
return new AdmissionController<>((Validator<Pod>) (resource, operation) -> {
60+
public static AdmissionController<Ingress> errorMutatingController() {
61+
return new AdmissionController<>((Validator<Ingress>) (resource, operation) -> {
6162
throw new IllegalStateException(ERROR_MESSAGE);
6263
});
6364
}
6465

65-
public static AdmissionController<Pod> errorValidatingController() {
66-
return new AdmissionController<>((Mutator<Pod>) (resource, operation) -> {
66+
public static AdmissionController<Ingress> errorValidatingController() {
67+
return new AdmissionController<>((Mutator<Ingress>) (resource, operation) -> {
6768
throw new IllegalStateException(ERROR_MESSAGE);
6869
});
6970
}
7071

71-
public static AsyncAdmissionController<Pod> errorAsyncMutatingController() {
72-
return new AsyncAdmissionController<>((AsyncMutator<Pod>) (resource, operation) -> {
72+
public static AsyncAdmissionController<Ingress> errorAsyncMutatingController() {
73+
return new AsyncAdmissionController<>((AsyncMutator<Ingress>) (resource, operation) -> {
7374
throw new IllegalStateException(ERROR_MESSAGE);
7475
});
7576
}
7677

77-
public static AsyncAdmissionController<Pod> errorAsyncValidatingController() {
78-
return new AsyncAdmissionController<>((Validator<Pod>) (resource, operation) -> {
78+
public static AsyncAdmissionController<Ingress> errorAsyncValidatingController() {
79+
return new AsyncAdmissionController<>((Validator<Ingress>) (resource, operation) -> {
7980
throw new IllegalStateException(ERROR_MESSAGE);
8081
});
8182
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package io.javaoperatorsdk.webhook.sample.commons;
2+
3+
import java.io.FileInputStream;
4+
import java.io.IOException;
5+
import java.io.InputStream;
6+
import java.util.Map;
7+
import java.util.concurrent.TimeUnit;
8+
9+
import io.fabric8.kubernetes.api.model.networking.v1.*;
10+
import io.fabric8.kubernetes.client.KubernetesClient;
11+
12+
import static io.javaoperatorsdk.webhook.sample.commons.AdmissionControllers.VALIDATION_TARGET_LABEL;
13+
14+
public class Utils {
15+
16+
public static final int SPIN_UP_GRACE_PERIOD = 120;
17+
18+
public static void applyAndWait(KubernetesClient client, String path) {
19+
try (FileInputStream fileInputStream = new FileInputStream(path)) {
20+
applyAndWait(client, fileInputStream);
21+
} catch (IOException e) {
22+
throw new IllegalStateException(e);
23+
}
24+
}
25+
26+
public static void applyAndWait(KubernetesClient client, InputStream is) {
27+
var resources = client.load(is).get();
28+
client.resourceList(resources).createOrReplace();
29+
client.resourceList(resources).waitUntilReady(3, TimeUnit.MINUTES);
30+
}
31+
32+
public static void addRequiredLabels(Ingress ingress) {
33+
ingress.getMetadata().setLabels(Map.of(VALIDATION_TARGET_LABEL, "val"));
34+
}
35+
36+
public static Ingress testIngress(String name) {
37+
return new IngressBuilder()
38+
.withNewMetadata()
39+
.withName(name)
40+
.endMetadata()
41+
.withSpec(new IngressSpecBuilder()
42+
.withIngressClassName("sample")
43+
.withRules(new IngressRuleBuilder()
44+
.withHttp(new HTTPIngressRuleValueBuilder()
45+
.withPaths(new HTTPIngressPathBuilder()
46+
.withPath("/test")
47+
.withPathType("Prefix")
48+
.withBackend(new IngressBackendBuilder()
49+
.withService(new IngressServiceBackendBuilder()
50+
.withName("service")
51+
.withPort(new ServiceBackendPortBuilder()
52+
.withNumber(80)
53+
.build())
54+
.build())
55+
.build())
56+
.build())
57+
.build())
58+
.build())
59+
.build())
60+
.build();
61+
}
62+
63+
}

0 commit comments

Comments
 (0)