Skip to content

Commit 5aa1bc4

Browse files
committed
feature: working spring boot example
1 parent e79322f commit 5aa1bc4

File tree

7 files changed

+197
-8
lines changed

7 files changed

+197
-8
lines changed

core/src/main/java/io/javaoperatorsdk/admissioncontroller/AdmissionUtils.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ public static AdmissionResponse admissionResponseFromMutation(KubernetesResource
3535
AdmissionResponse admissionResponse = new AdmissionResponse();
3636
admissionResponse.setAllowed(true);
3737
admissionResponse.setPatchType(JSON_PATCH);
38+
Status status = new Status();
39+
status.setCode(200);
40+
admissionResponse.setStatus(status);
3841
var originalResNode = mapper.valueToTree(originalResource);
3942
var mutatedResNode = mapper.valueToTree(mutatedResource);
4043

core/src/main/java/io/javaoperatorsdk/admissioncontroller/NotAllowedException.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ public NotAllowedException(Throwable cause, Status status) {
1919
this.status = status;
2020
}
2121

22-
public NotAllowedException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace, Status status) {
22+
public NotAllowedException(String message, Throwable cause, boolean enableSuppression,
23+
boolean writableStackTrace, Status status) {
2324
super(message, cause, enableSuppression, writableStackTrace);
2425
this.status = status;
2526
}

pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
<maven-jar-plugin.version>3.2.0</maven-jar-plugin.version>
5252
<maven-compiler-plugin.version>3.8.1</maven-compiler-plugin.version>
5353
<json-patch.version>1.13</json-patch.version>
54+
<maven-javadoc-plugin.version>3.3.1</maven-javadoc-plugin.version>
5455
</properties>
5556

5657
<modules>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package io.javaoperatorsdk.admissioncontroller.sample.springbootsample;
22

3-
import io.fabric8.kubernetes.api.model.HasMetadata;
3+
import io.fabric8.kubernetes.api.model.Pod;
44
import io.fabric8.kubernetes.api.model.admission.v1.AdmissionReview;
55
import io.javaoperatorsdk.admissioncontroller.AdmissionController;
66
import org.springframework.beans.factory.annotation.Autowired;
7+
import org.springframework.beans.factory.annotation.Qualifier;
8+
import org.springframework.http.HttpStatus;
9+
import org.springframework.http.ResponseEntity;
710
import org.springframework.web.bind.annotation.PostMapping;
811
import org.springframework.web.bind.annotation.RequestBody;
912
import org.springframework.web.bind.annotation.ResponseBody;
@@ -12,17 +15,27 @@
1215
@RestController
1316
public class AdmissionEndpoint {
1417

15-
private final AdmissionController<HasMetadata> addLabelMutationController;
18+
private final AdmissionController<Pod> addLabelMutationController;
19+
private final AdmissionController<Pod> validatingController;
1620

1721
@Autowired
18-
public AdmissionEndpoint(AdmissionController<HasMetadata> addLabelMutationController) {
22+
public AdmissionEndpoint(@Qualifier("mutatingController") AdmissionController<Pod> addLabelMutationController,
23+
@Qualifier("validatingController") AdmissionController<Pod> validatingController) {
1924
this.addLabelMutationController = addLabelMutationController;
25+
this.validatingController = validatingController;
2026
}
2127

2228
@PostMapping("/mutate")
2329
@ResponseBody
24-
public AdmissionReview admissionReview(@RequestBody AdmissionReview admissionReview) {
25-
return addLabelMutationController.handle(admissionReview);
30+
public ResponseEntity<AdmissionReview> mutate(@RequestBody AdmissionReview admissionReview) {
31+
AdmissionReview resultReview = addLabelMutationController.handle(admissionReview);
32+
return new ResponseEntity<>(resultReview, HttpStatus.valueOf(resultReview.getResponse().getStatus().getCode()));
33+
}
34+
35+
@PostMapping("/validate")
36+
public ResponseEntity<AdmissionReview> validate(@RequestBody AdmissionReview admissionReview) {
37+
AdmissionReview resultReview = validatingController.handle(admissionReview);
38+
return new ResponseEntity<>(resultReview, HttpStatus.valueOf(resultReview.getResponse().getStatus().getCode()));
2639
}
2740

2841
}
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
11
package io.javaoperatorsdk.admissioncontroller.sample.springbootsample;
22

33
import io.fabric8.kubernetes.api.model.HasMetadata;
4+
import io.fabric8.kubernetes.api.model.Pod;
45
import io.javaoperatorsdk.admissioncontroller.AdmissionController;
6+
import io.javaoperatorsdk.admissioncontroller.NotAllowedException;
57
import org.springframework.context.annotation.Bean;
68
import org.springframework.context.annotation.Configuration;
79

810
@Configuration
911
public class Config {
1012

13+
public static final String APP_NAME_LABEL_KEY = "app.kubernetes.io/name";
14+
1115
@Bean
12-
public AdmissionController<HasMetadata> admissionController() {
16+
public AdmissionController<Pod> mutatingController() {
1317
return new AdmissionController<>((resource, operation) -> {
14-
resource.getMetadata().getLabels().putIfAbsent("app.kubernetes.io/name", "mutation-test");
18+
resource.getMetadata().getLabels().putIfAbsent(APP_NAME_LABEL_KEY, "mutation-test");
1519
return resource;
1620
});
1721
}
1822

23+
@Bean
24+
public AdmissionController<Pod> validatingController() {
25+
return new AdmissionController<>((resource, operation) -> {
26+
if (resource.getMetadata().getLabels().get(APP_NAME_LABEL_KEY) == null) {
27+
throw new NotAllowedException("Missing label: " + APP_NAME_LABEL_KEY);
28+
}
29+
});
30+
}
31+
1932
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.javaoperatorsdk.admissioncontroller.sample.springbootsample;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.springframework.beans.factory.annotation.Autowired;
5+
import org.springframework.beans.factory.annotation.Value;
6+
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
7+
import org.springframework.boot.test.context.SpringBootTest;
8+
import org.springframework.core.io.Resource;
9+
import org.springframework.http.MediaType;
10+
import org.springframework.test.web.servlet.MockMvc;
11+
12+
import java.nio.file.Files;
13+
14+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
15+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
16+
17+
@SpringBootTest
18+
@AutoConfigureMockMvc
19+
public class AdmissionEndpointTest {
20+
21+
@Autowired
22+
private MockMvc mockMvc;
23+
24+
@Value("classpath:admission-request.json")
25+
private Resource request;
26+
27+
28+
@Test
29+
public void testValidation() throws Exception {
30+
mockMvc.perform(post("/validate").contentType(MediaType.APPLICATION_JSON)
31+
.content(new String(Files.readAllBytes(request.getFile().toPath()))))
32+
.andExpect(status().isForbidden());
33+
}
34+
35+
@Test
36+
public void testMutation() throws Exception {
37+
mockMvc.perform(post("/mutate").contentType(MediaType.APPLICATION_JSON)
38+
.content(new String(Files.readAllBytes(request.getFile().toPath()))))
39+
.andExpect(status().isOk());
40+
}
41+
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
{
2+
"kind": "AdmissionReview",
3+
"apiVersion": "admission.k8s.io/v1beta1",
4+
"request": {
5+
"uid": "0df28fbd-5f5f-11e8-bc74-36e6bb280816",
6+
"kind": {
7+
"group": "",
8+
"version": "v1",
9+
"kind": "Pod"
10+
},
11+
"resource": {
12+
"group": "",
13+
"version": "v1",
14+
"resource": "pods"
15+
},
16+
"namespace": "dummy",
17+
"operation": "CREATE",
18+
"userInfo": {
19+
"username": "system:serviceaccount:kube-system:replicaset-controller",
20+
"uid": "a7e0ab33-5f29-11e8-8a3c-36e6bb280816",
21+
"groups": [
22+
"system:serviceaccounts",
23+
"system:serviceaccounts:kube-system",
24+
"system:authenticated"
25+
]
26+
},
27+
"object": {
28+
"apiVersion": "v1",
29+
"kind": "Pod",
30+
"metadata": {
31+
"generateName": "nginx-deployment-6c54bd5869-",
32+
"creationTimestamp": null,
33+
"labels": {
34+
"app": "nginx",
35+
"pod-template-hash": "2710681425"
36+
},
37+
"annotations": {
38+
"openshift.io/scc": "restricted"
39+
},
40+
"ownerReferences": [
41+
{
42+
"apiVersion": "extensions/v1beta1",
43+
"kind": "ReplicaSet",
44+
"name": "nginx-deployment-6c54bd5869",
45+
"uid": "16c2b355-5f5d-11e8-ac91-36e6bb280816",
46+
"controller": true,
47+
"blockOwnerDeletion": true
48+
}
49+
]
50+
},
51+
"spec": {
52+
"volumes": [
53+
{
54+
"name": "default-token-tq5lq",
55+
"secret": {
56+
"secretName": "default-token-tq5lq"
57+
}
58+
}
59+
],
60+
"containers": [
61+
{
62+
"name": "nginx",
63+
"image": "nginx:1.7.9",
64+
"ports": [
65+
{
66+
"containerPort": 80,
67+
"protocol": "TCP"
68+
}
69+
],
70+
"resources": {},
71+
"volumeMounts": [
72+
{
73+
"name": "default-token-tq5lq",
74+
"readOnly": true,
75+
"mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
76+
}
77+
],
78+
"terminationMessagePath": "/dev/termination-log",
79+
"terminationMessagePolicy": "File",
80+
"imagePullPolicy": "IfNotPresent",
81+
"securityContext": {
82+
"capabilities": {
83+
"drop": [
84+
"KILL",
85+
"MKNOD",
86+
"SETGID",
87+
"SETUID"
88+
]
89+
},
90+
"runAsUser": 1000080000
91+
}
92+
}
93+
],
94+
"restartPolicy": "Always",
95+
"terminationGracePeriodSeconds": 30,
96+
"dnsPolicy": "ClusterFirst",
97+
"serviceAccountName": "default",
98+
"serviceAccount": "default",
99+
"securityContext": {
100+
"seLinuxOptions": {
101+
"level": "s0:c9,c4"
102+
},
103+
"fsGroup": 1000080000
104+
},
105+
"imagePullSecrets": [
106+
{
107+
"name": "default-dockercfg-kksdv"
108+
}
109+
],
110+
"schedulerName": "default-scheduler"
111+
},
112+
"status": {}
113+
},
114+
"oldObject": null
115+
}
116+
}

0 commit comments

Comments
 (0)