[BAEL-5014] Kubernetes Admission Controller (#11044)
* [BAEL-4849] Article code * [BAEL-4968] Article code * [BAEL-4968] Article code * [BAEL-4968] Article code
This commit is contained in:
parent
3f18d3ae53
commit
d2035e86af
11
kubernetes/k8s-admission-controller/Dockerfile
Normal file
11
kubernetes/k8s-admission-controller/Dockerfile
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
FROM adoptopenjdk:11-jre-hotspot as builder
|
||||||
|
ARG JAR_FILE=target/*.jar
|
||||||
|
COPY ${JAR_FILE} application.jar
|
||||||
|
RUN java -Djarmode=layertools -jar application.jar extract
|
||||||
|
|
||||||
|
FROM adoptopenjdk:11-jre-hotspot
|
||||||
|
COPY --from=builder dependencies/ ./
|
||||||
|
COPY --from=builder snapshot-dependencies/ ./
|
||||||
|
COPY --from=builder spring-boot-loader/ ./
|
||||||
|
COPY --from=builder application/ ./
|
||||||
|
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
|
86
kubernetes/k8s-admission-controller/pom.xml
Normal file
86
kubernetes/k8s-admission-controller/pom.xml
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>com.baeldung</groupId>
|
||||||
|
<artifactId>parent-boot-2</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
<relativePath>./../../parent-boot-2</relativePath>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>k8s-admission-controller</artifactId>
|
||||||
|
<name>k8s-admission-controller</name>
|
||||||
|
<description>Demo project for Spring Boot</description>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-webflux</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.projectreactor</groupId>
|
||||||
|
<artifactId>reactor-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-devtools</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-configuration-processor</artifactId>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<goals>
|
||||||
|
<goal>repackage</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<excludes>
|
||||||
|
<exclude>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
</exclude>
|
||||||
|
</excludes>
|
||||||
|
<mainClass>com.baeldung.kubernetes.admission.Application</mainClass>
|
||||||
|
<layers>
|
||||||
|
<enabled>true</enabled>
|
||||||
|
</layers>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,17 @@
|
|||||||
|
package com.baeldung.kubernetes.admission;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
|
||||||
|
import com.baeldung.kubernetes.admission.config.AdmissionControllerProperties;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
@EnableConfigurationProperties(AdmissionControllerProperties.class)
|
||||||
|
public class Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.baeldung.kubernetes.admission.config;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author lighthouse.psevestre
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@ConfigurationProperties(prefix = "admission-controller")
|
||||||
|
@Data
|
||||||
|
public class AdmissionControllerProperties {
|
||||||
|
|
||||||
|
private boolean disabled;
|
||||||
|
private String annotation = "com.baeldung/wait-for-it";
|
||||||
|
private String waitForItImage = "willwill/wait-for-it";
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.baeldung.kubernetes.admission.controller;
|
||||||
|
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
import com.baeldung.kubernetes.admission.dto.AdmissionReviewResponse;
|
||||||
|
import com.baeldung.kubernetes.admission.service.AdmissionService;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class AdmissionReviewController {
|
||||||
|
|
||||||
|
private final AdmissionService admissionService;
|
||||||
|
|
||||||
|
@PostMapping(path = "/mutate")
|
||||||
|
public Mono<AdmissionReviewResponse> processAdmissionReviewRequest(@RequestBody Mono<ObjectNode> request) {
|
||||||
|
return request.map((body) -> admissionService.processAdmission(body));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.baeldung.kubernetes.admission.dto;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonInclude.Include;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result sent to the API server after reviewing and, possibly
|
||||||
|
* modifying the incoming request
|
||||||
|
*/
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class AdmissionReviewData {
|
||||||
|
|
||||||
|
final String uid;
|
||||||
|
final boolean allowed;
|
||||||
|
|
||||||
|
@JsonInclude(Include.NON_NULL)
|
||||||
|
final String patchType;
|
||||||
|
|
||||||
|
@JsonInclude(Include.NON_NULL)
|
||||||
|
final String patch;
|
||||||
|
|
||||||
|
@JsonInclude(Include.NON_NULL)
|
||||||
|
final AdmissionStatus status;
|
||||||
|
}
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.baeldung.kubernetes.admission.dto;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exceção utilizada para reportar erros de validação no manifesto recebido para admissão
|
||||||
|
* @author lighthouse.psevestre
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class AdmissionReviewException extends RuntimeException {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
private final int code;
|
||||||
|
|
||||||
|
public AdmissionReviewException(int code, String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AdmissionReviewException(String message) {
|
||||||
|
super(message);
|
||||||
|
this.code = 400;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCode() {
|
||||||
|
return code;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.baeldung.kubernetes.admission.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Builder.Default;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Response "envelope" sent back to the API Server
|
||||||
|
*/
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class AdmissionReviewResponse {
|
||||||
|
|
||||||
|
@Default
|
||||||
|
final String apiVersion = "admission.k8s.io/v1";
|
||||||
|
|
||||||
|
@Default
|
||||||
|
final String kind = "AdmissionReview";
|
||||||
|
|
||||||
|
final AdmissionReviewData response;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.baeldung.kubernetes.admission.dto;
|
||||||
|
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
@Data
|
||||||
|
public class AdmissionStatus {
|
||||||
|
|
||||||
|
int code;
|
||||||
|
String message;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,218 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package com.baeldung.kubernetes.admission.service;
|
||||||
|
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import com.baeldung.kubernetes.admission.config.AdmissionControllerProperties;
|
||||||
|
import com.baeldung.kubernetes.admission.dto.AdmissionReviewData;
|
||||||
|
import com.baeldung.kubernetes.admission.dto.AdmissionReviewException;
|
||||||
|
import com.baeldung.kubernetes.admission.dto.AdmissionReviewResponse;
|
||||||
|
import com.baeldung.kubernetes.admission.dto.AdmissionStatus;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process an incoming admission request and add the "wait-for-it" init container
|
||||||
|
* if there's an appropriate annotation
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class AdmissionService {
|
||||||
|
|
||||||
|
private final AdmissionControllerProperties admissionControllerProperties;
|
||||||
|
private final ObjectMapper om;
|
||||||
|
|
||||||
|
public AdmissionReviewResponse processAdmission(ObjectNode body) {
|
||||||
|
|
||||||
|
String uid = body.path("request")
|
||||||
|
.required("uid")
|
||||||
|
.asText();
|
||||||
|
|
||||||
|
log.info("[I42] processAdmission: uid={}",uid);
|
||||||
|
if ( log.isDebugEnabled()) {
|
||||||
|
log.debug("processAdmission: body={}", body.toPrettyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get request annotations
|
||||||
|
JsonNode annotations = body.path("request")
|
||||||
|
.path("object")
|
||||||
|
.path("metadata")
|
||||||
|
.path("annotations");
|
||||||
|
log.info("processAdmision: annotations={}", annotations.toString());
|
||||||
|
|
||||||
|
AdmissionReviewData data;
|
||||||
|
try {
|
||||||
|
if (admissionControllerProperties.isDisabled()) {
|
||||||
|
log.info("[I58] 'disabled' option in effect. No changes to current request will be made");
|
||||||
|
data = createSimpleAllowedReview(body);
|
||||||
|
} else if (annotations.isMissingNode()) {
|
||||||
|
log.info("[I68] No annotations found in request. No changes will be made");
|
||||||
|
data = createSimpleAllowedReview(body);
|
||||||
|
} else {
|
||||||
|
data = processAnnotations(body, annotations);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("[I65] Review result: isAllowed=" + data.isAllowed());
|
||||||
|
log.info("[I64] AdmissionReviewData= {}", data);
|
||||||
|
|
||||||
|
return AdmissionReviewResponse.builder()
|
||||||
|
.apiVersion(body.required("apiVersion").asText())
|
||||||
|
.kind(body.required("kind").asText())
|
||||||
|
.response(data)
|
||||||
|
.build();
|
||||||
|
} catch (AdmissionReviewException ex) {
|
||||||
|
log.error("[E72] Error processing AdmissionRequest: code={}, message={}", ex.getCode(), ex.getMessage());
|
||||||
|
data = createRejectedAdmissionReview(body, ex.getCode(), ex.getMessage());
|
||||||
|
|
||||||
|
return AdmissionReviewResponse.builder()
|
||||||
|
.apiVersion(body.required("apiVersion").asText())
|
||||||
|
.kind(body.required("kind").asText())
|
||||||
|
.response(data)
|
||||||
|
.build();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
log.error("[E72] Unable to process AdmissionRequest: " + ex.getMessage(), ex);
|
||||||
|
data = createRejectedAdmissionReview(body, 500, ex.getMessage());
|
||||||
|
return AdmissionReviewResponse.builder()
|
||||||
|
.apiVersion(body.required("apiVersion").asText())
|
||||||
|
.kind(body.required("kind").asText())
|
||||||
|
.response(data)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param body
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected AdmissionReviewData createSimpleAllowedReview(ObjectNode body) {
|
||||||
|
AdmissionReviewData data;
|
||||||
|
String requestId = body.path("request")
|
||||||
|
.required("uid")
|
||||||
|
.asText();
|
||||||
|
|
||||||
|
data = AdmissionReviewData.builder()
|
||||||
|
.allowed(true)
|
||||||
|
.uid(requestId)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param body
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected AdmissionReviewData createRejectedAdmissionReview(ObjectNode body, int code, String message) {
|
||||||
|
AdmissionReviewData data;
|
||||||
|
String requestId = body.path("request")
|
||||||
|
.required("uid")
|
||||||
|
.asText();
|
||||||
|
|
||||||
|
AdmissionStatus status = AdmissionStatus.builder()
|
||||||
|
.code(code)
|
||||||
|
.message(message)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
data = AdmissionReviewData.builder()
|
||||||
|
.allowed(false)
|
||||||
|
.uid(requestId)
|
||||||
|
.status(status)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return data;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processa anotações incluídas no deployment
|
||||||
|
* @param annotations
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
protected AdmissionReviewData processAnnotations(ObjectNode body, JsonNode annotations) {
|
||||||
|
|
||||||
|
if (annotations.path(admissionControllerProperties.getAnnotation())
|
||||||
|
.isMissingNode()) {
|
||||||
|
log.info("[I78] processAnnotations: Annotation {} not found in deployment deployment.", admissionControllerProperties.getAnnotation());
|
||||||
|
return createSimpleAllowedReview(body);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
log.info("[I163] annotation found: {}", annotations.path(admissionControllerProperties.getAnnotation()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get wait-for-it arguments from the annotation
|
||||||
|
String waitForArgs = annotations.path(admissionControllerProperties.getAnnotation())
|
||||||
|
.asText();
|
||||||
|
|
||||||
|
log.info("[I169] waitForArgs={}", waitForArgs);
|
||||||
|
// Create a PATCH object
|
||||||
|
String patch = injectInitContainer(body, waitForArgs);
|
||||||
|
|
||||||
|
return AdmissionReviewData.builder()
|
||||||
|
.allowed(true)
|
||||||
|
.uid(body.path("request")
|
||||||
|
.required("uid")
|
||||||
|
.asText())
|
||||||
|
.patch(Base64.getEncoder()
|
||||||
|
.encodeToString(patch.getBytes()))
|
||||||
|
.patchType("JSONPatch")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the JSONPatch to be included in the admission response
|
||||||
|
* @param body
|
||||||
|
* @param waitForArgs
|
||||||
|
* @return JSONPatch string
|
||||||
|
*/
|
||||||
|
protected String injectInitContainer(ObjectNode body, String waitForArgs) {
|
||||||
|
|
||||||
|
// Recover original init containers from the request
|
||||||
|
JsonNode originalSpec = body.path("request")
|
||||||
|
.path("object")
|
||||||
|
.path("spec")
|
||||||
|
.path("template")
|
||||||
|
.path("spec")
|
||||||
|
.require();
|
||||||
|
|
||||||
|
JsonNode maybeInitContainers = originalSpec.path("initContainers");
|
||||||
|
ArrayNode initContainers =
|
||||||
|
maybeInitContainers.isMissingNode()?
|
||||||
|
om.createArrayNode():(ArrayNode) maybeInitContainers;
|
||||||
|
|
||||||
|
// Create the patch array
|
||||||
|
ArrayNode patchArray = om.createArrayNode();
|
||||||
|
ObjectNode addNode = patchArray.addObject();
|
||||||
|
|
||||||
|
addNode.put("op", "add");
|
||||||
|
addNode.put("path", "/spec/template/spec/initContainers");
|
||||||
|
ArrayNode values = addNode.putArray("value");
|
||||||
|
|
||||||
|
// Preserve original init containers
|
||||||
|
values.addAll(initContainers);
|
||||||
|
|
||||||
|
// append the "wait-for-it" container
|
||||||
|
ObjectNode wfi = values.addObject();
|
||||||
|
wfi.put("name", "wait-for-it-" + UUID.randomUUID()); // Create an unique name, JIC
|
||||||
|
wfi.put("image", admissionControllerProperties.getWaitForItImage());
|
||||||
|
|
||||||
|
ArrayNode args = wfi.putArray("args");
|
||||||
|
for (String s : waitForArgs.split("\\s")) {
|
||||||
|
args.add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
return patchArray.toString();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
package com.baeldung.kubernetes.admission.service;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.test.context.ActiveProfiles;
|
||||||
|
|
||||||
|
import com.baeldung.kubernetes.admission.config.AdmissionControllerProperties;
|
||||||
|
import com.baeldung.kubernetes.admission.dto.AdmissionReviewResponse;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
|
||||||
|
@SpringBootTest
|
||||||
|
@ActiveProfiles("test")
|
||||||
|
@EnableConfigurationProperties(AdmissionControllerProperties.class)
|
||||||
|
class AdmissionServiceUnitTest {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private ObjectMapper mapper;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private AdmissionService admissionService;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void whenAnnotationPresent_thenAddContainer() throws Exception {
|
||||||
|
|
||||||
|
InputStream is = this.getClass()
|
||||||
|
.getClassLoader()
|
||||||
|
.getResourceAsStream("test1.json");
|
||||||
|
JsonNode body = mapper.readTree(is);
|
||||||
|
AdmissionReviewResponse response = admissionService.processAdmission((ObjectNode) body);
|
||||||
|
assertNotNull(response);
|
||||||
|
assertNotNull(response.getResponse());
|
||||||
|
assertNotNull(response.getResponse());
|
||||||
|
assertTrue(response.getResponse()
|
||||||
|
.isAllowed());
|
||||||
|
|
||||||
|
String jsonResponse = mapper.writeValueAsString(response);
|
||||||
|
System.out.println(jsonResponse);
|
||||||
|
|
||||||
|
// Decode Patch data
|
||||||
|
String b64patch = response.getResponse()
|
||||||
|
.getPatch();
|
||||||
|
assertNotNull(b64patch);
|
||||||
|
byte[] patch = Base64.getDecoder()
|
||||||
|
.decode(b64patch);
|
||||||
|
|
||||||
|
JsonNode root = mapper.reader()
|
||||||
|
.readTree(new ByteArrayInputStream(patch));
|
||||||
|
assertTrue(root instanceof ArrayNode);
|
||||||
|
|
||||||
|
assertEquals(1, ((ArrayNode) root).size());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
23
kubernetes/k8s-admission-controller/src/test/k8s/nginx.yaml
Normal file
23
kubernetes/k8s-admission-controller/src/test/k8s/nginx.yaml
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: frontend
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
annotations:
|
||||||
|
com.baeldung/wait-for-it: "www.google.com:80"
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: nginx
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: nginx
|
||||||
|
spec:
|
||||||
|
containers:
|
||||||
|
- name: nginx
|
||||||
|
image: nginx:1.14.2
|
||||||
|
ports:
|
||||||
|
- containerPort: 80
|
@ -0,0 +1,84 @@
|
|||||||
|
{
|
||||||
|
"kind": "AdmissionReview",
|
||||||
|
"apiVersion": "admission.k8s.io/v1",
|
||||||
|
"request": {
|
||||||
|
"uid": "c46a6607-129d-425b-af2f-c6f87a0756da",
|
||||||
|
"kind": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "Deployment"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"resource": "deployments"
|
||||||
|
},
|
||||||
|
"requestKind": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "Deployment"
|
||||||
|
},
|
||||||
|
"requestResource": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"resource": "deployments"
|
||||||
|
},
|
||||||
|
"name": "test-deployment",
|
||||||
|
"namespace": "test-namespace",
|
||||||
|
"operation": "CREATE",
|
||||||
|
"object": {
|
||||||
|
"kind": "Deployment",
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-deployment",
|
||||||
|
"namespace": "test-namespace",
|
||||||
|
"annotations": {
|
||||||
|
"com.baeldung/wait-for-it": "www.google.com:80"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"replicas": 1,
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app": "test-app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-app",
|
||||||
|
"creationTimestamp": null,
|
||||||
|
"labels": {
|
||||||
|
"app": "test-app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"image": "test-app-image:latest",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"containerPort": 8080,
|
||||||
|
"protocol": "TCP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resources": {},
|
||||||
|
"terminationMessagePath": "/dev/termination-log",
|
||||||
|
"terminationMessagePolicy": "File",
|
||||||
|
"imagePullPolicy": "Always"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {}
|
||||||
|
},
|
||||||
|
"oldObject": null,
|
||||||
|
"dryRun": false,
|
||||||
|
"options": {
|
||||||
|
"kind": "CreateOptions",
|
||||||
|
"apiVersion": "meta.k8s.io/v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"kind": "AdmissionReview",
|
||||||
|
"apiVersion": "admission.k8s.io/v1",
|
||||||
|
"request": {
|
||||||
|
"uid": "c46a6607-129d-425b-af2f-c6f87a0756da",
|
||||||
|
"kind": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "Deployment"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"resource": "deployments"
|
||||||
|
},
|
||||||
|
"requestKind": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "Deployment"
|
||||||
|
},
|
||||||
|
"requestResource": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"resource": "deployments"
|
||||||
|
},
|
||||||
|
"name": "test-deployment",
|
||||||
|
"namespace": "test-namespace",
|
||||||
|
"operation": "CREATE",
|
||||||
|
"object": {
|
||||||
|
"kind": "Deployment",
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-deployment",
|
||||||
|
"namespace": "test-namespace",
|
||||||
|
"annotations": {
|
||||||
|
"com.baeldung/wait-for-it": "www.google.com:80"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"replicas": 1,
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app": "test-app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-app",
|
||||||
|
"creationTimestamp": null,
|
||||||
|
"labels": {
|
||||||
|
"app": "test-app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"initContainers": [],
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"image": "test-app-image:latest",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"containerPort": 8080,
|
||||||
|
"protocol": "TCP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resources": {},
|
||||||
|
"terminationMessagePath": "/dev/termination-log",
|
||||||
|
"terminationMessagePolicy": "File",
|
||||||
|
"imagePullPolicy": "Always"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {}
|
||||||
|
},
|
||||||
|
"oldObject": null,
|
||||||
|
"dryRun": false,
|
||||||
|
"options": {
|
||||||
|
"kind": "CreateOptions",
|
||||||
|
"apiVersion": "meta.k8s.io/v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
{
|
||||||
|
"kind": "AdmissionReview",
|
||||||
|
"apiVersion": "admission.k8s.io/v1",
|
||||||
|
"request": {
|
||||||
|
"uid": "c46a6607-129d-425b-af2f-c6f87a0756da",
|
||||||
|
"kind": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "Deployment"
|
||||||
|
},
|
||||||
|
"resource": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"resource": "deployments"
|
||||||
|
},
|
||||||
|
"requestKind": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "Deployment"
|
||||||
|
},
|
||||||
|
"requestResource": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"resource": "deployments"
|
||||||
|
},
|
||||||
|
"name": "test-deployment",
|
||||||
|
"namespace": "test-namespace",
|
||||||
|
"operation": "CREATE",
|
||||||
|
"object": {
|
||||||
|
"kind": "Deployment",
|
||||||
|
"apiVersion": "apps/v1",
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-deployment",
|
||||||
|
"namespace": "test-namespace",
|
||||||
|
"annotations": {
|
||||||
|
"com.baeldung/wait-for-it": "www.google.com:80"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"replicas": 1,
|
||||||
|
"selector": {
|
||||||
|
"matchLabels": {
|
||||||
|
"app": "test-app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"template": {
|
||||||
|
"metadata": {
|
||||||
|
"name": "test-app",
|
||||||
|
"creationTimestamp": null,
|
||||||
|
"labels": {
|
||||||
|
"app": "test-app"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
"initContainers": [
|
||||||
|
{
|
||||||
|
"name": "init1",
|
||||||
|
"image": "test-app-image:latest",
|
||||||
|
"resources": {},
|
||||||
|
"terminationMessagePath": "/dev/termination-log",
|
||||||
|
"terminationMessagePolicy": "File",
|
||||||
|
"imagePullPolicy": "Always"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"containers": [
|
||||||
|
{
|
||||||
|
"name": "app",
|
||||||
|
"image": "test-app-image:latest",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"name": "http",
|
||||||
|
"containerPort": 8080,
|
||||||
|
"protocol": "TCP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"resources": {},
|
||||||
|
"terminationMessagePath": "/dev/termination-log",
|
||||||
|
"terminationMessagePolicy": "File",
|
||||||
|
"imagePullPolicy": "Always"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": {}
|
||||||
|
},
|
||||||
|
"oldObject": null,
|
||||||
|
"dryRun": false,
|
||||||
|
"options": {
|
||||||
|
"kind": "CreateOptions",
|
||||||
|
"apiVersion": "meta.k8s.io/v1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"kind": "AdmissionReview",
|
||||||
|
"apiVersion": "admission.k8s.io/v1",
|
||||||
|
"request": {
|
||||||
|
"uid": "26beb334-739a-48d2-b04d-25f6e5e7c106",
|
||||||
|
"kind": {
|
||||||
|
"group": "apps",
|
||||||
|
"version": "v1",
|
||||||
|
"kind": "Deployment"
|
||||||
|
},
|
||||||
|
"resource": {},
|
||||||
|
"f:type": {}
|
||||||
|
},
|
||||||
|
"f:template": {
|
||||||
|
"f:metadata": {
|
||||||
|
"f:labels": {
|
||||||
|
".": {},
|
||||||
|
"f:app": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f:spec": {
|
||||||
|
"f:containers": {
|
||||||
|
"k:{\"name\":\"nginx\"}": {
|
||||||
|
".": {},
|
||||||
|
"f:image": {},
|
||||||
|
"f:imagePullPolicy": {},
|
||||||
|
"f:name": {},
|
||||||
|
"f:ports": {
|
||||||
|
".": {},
|
||||||
|
"k:{\"containerPort\":80,\"protocol\":\"TCP\"}": {
|
||||||
|
".": {},
|
||||||
|
"f:containerPort": {},
|
||||||
|
"f:protocol": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f:resources": {},
|
||||||
|
"f:terminationMessagePath": {},
|
||||||
|
"f:terminationMessagePolicy": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"f:dnsPolicy": {},
|
||||||
|
"f:restartPolicy": {},
|
||||||
|
"f:schedulerName": {},
|
||||||
|
"f:securityContext": {},
|
||||||
|
"f:terminationGracePeriodSeconds": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3
kubernetes/k8s-admission-controller/src/test/terraform/.gitignore
vendored
Normal file
3
kubernetes/k8s-admission-controller/src/test/terraform/.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.terraform
|
||||||
|
terraform.tfstate
|
||||||
|
terraform.tfstate.backup
|
57
kubernetes/k8s-admission-controller/src/test/terraform/.terraform.lock.hcl
generated
Normal file
57
kubernetes/k8s-admission-controller/src/test/terraform/.terraform.lock.hcl
generated
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
# This file is maintained automatically by "terraform init".
|
||||||
|
# Manual edits may be lost in future updates.
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/kubernetes" {
|
||||||
|
version = "2.3.2"
|
||||||
|
constraints = "2.3.2"
|
||||||
|
hashes = [
|
||||||
|
"h1:D8HWX3vouTPI3Jicq43xOQyoYWtSsVua92cBVrJ3ZMs=",
|
||||||
|
"zh:10f71c170be13538374a4b9553fcb3d98a6036bcd1ca5901877773116c3f828e",
|
||||||
|
"zh:11d2230e531b7480317e988207a73cb67b332f225b0892304983b19b6014ebe0",
|
||||||
|
"zh:3317387a9a6cc27fd7536b8f3cad4b8a9285e9461f125c5a15d192cef3281856",
|
||||||
|
"zh:458a9858362900fbe97e00432ae8a5bef212a4dacf97a57ede7534c164730da4",
|
||||||
|
"zh:50ea297007d9fe53e5411577f87a4b13f3877ce732089b42f938430e6aadff0d",
|
||||||
|
"zh:56705c959e4cbea3b115782d04c62c68ac75128c5c44ee7aa4043df253ffbfe3",
|
||||||
|
"zh:7eb3722f7f036e224824470c3e0d941f1f268fcd5fa2f8203e0eee425d0e1484",
|
||||||
|
"zh:9f408a6df4d74089e6ce18f9206b06b8107ddb57e2bc9b958a6b7dc352c62980",
|
||||||
|
"zh:aadd25ccc3021040808feb2645779962f638766eb583f586806e59f24dde81bb",
|
||||||
|
"zh:b101c3456e4309b09aab129b0118561178c92cb4be5d96dec553189c3084dca1",
|
||||||
|
"zh:ec08478573b4953764099fbfd670fae81dc24b60e467fb3b023e6fab50b70a9e",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/null" {
|
||||||
|
version = "3.1.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:SFT7X3zY18CLWjoH2GfQyapxsRv6GDKsy9cF1aRwncc=",
|
||||||
|
"zh:02a1675fd8de126a00460942aaae242e65ca3380b5bb192e8773ef3da9073fd2",
|
||||||
|
"zh:53e30545ff8926a8e30ad30648991ca8b93b6fa496272cd23b26763c8ee84515",
|
||||||
|
"zh:5f9200bf708913621d0f6514179d89700e9aa3097c77dac730e8ba6e5901d521",
|
||||||
|
"zh:9ebf4d9704faba06b3ec7242c773c0fbfe12d62db7d00356d4f55385fc69bfb2",
|
||||||
|
"zh:a6576c81adc70326e4e1c999c04ad9ca37113a6e925aefab4765e5a5198efa7e",
|
||||||
|
"zh:a8a42d13346347aff6c63a37cda9b2c6aa5cc384a55b2fe6d6adfa390e609c53",
|
||||||
|
"zh:c797744d08a5307d50210e0454f91ca4d1c7621c68740441cf4579390452321d",
|
||||||
|
"zh:cecb6a304046df34c11229f20a80b24b1603960b794d68361a67c5efe58e62b8",
|
||||||
|
"zh:e1371aa1e502000d9974cfaff5be4cfa02f47b17400005a16f14d2ef30dc2a70",
|
||||||
|
"zh:fc39cc1fe71234a0b0369d5c5c7f876c71b956d23d7d6f518289737a001ba69b",
|
||||||
|
"zh:fea4227271ebf7d9e2b61b89ce2328c7262acd9fd190e1fd6d15a591abfa848e",
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
provider "registry.terraform.io/hashicorp/tls" {
|
||||||
|
version = "3.1.0"
|
||||||
|
hashes = [
|
||||||
|
"h1:ekOxs6MjdIElt8h9crEVaOwWbEqtfUUfArtA13Jkk6A=",
|
||||||
|
"zh:3d46616b41fea215566f4a957b6d3a1aa43f1f75c26776d72a98bdba79439db6",
|
||||||
|
"zh:623a203817a6dafa86f1b4141b645159e07ec418c82fe40acd4d2a27543cbaa2",
|
||||||
|
"zh:668217e78b210a6572e7b0ecb4134a6781cc4d738f4f5d09eb756085b082592e",
|
||||||
|
"zh:95354df03710691773c8f50a32e31fca25f124b7f3d6078265fdf3c4e1384dca",
|
||||||
|
"zh:9f97ab190380430d57392303e3f36f4f7835c74ea83276baa98d6b9a997c3698",
|
||||||
|
"zh:a16f0bab665f8d933e95ca055b9c8d5707f1a0dd8c8ecca6c13091f40dc1e99d",
|
||||||
|
"zh:be274d5008c24dc0d6540c19e22dbb31ee6bfdd0b2cddd4d97f3cd8a8d657841",
|
||||||
|
"zh:d5faa9dce0a5fc9d26b2463cea5be35f8586ab75030e7fa4d4920cd73ee26989",
|
||||||
|
"zh:e9b672210b7fb410780e7b429975adcc76dd557738ecc7c890ea18942eb321a5",
|
||||||
|
"zh:eb1f8368573d2370605d6dbf60f9aaa5b64e55741d96b5fb026dbfe91de67c0d",
|
||||||
|
"zh:fc1e12b713837b85daf6c3bb703d7795eaf1c5177aebae1afcf811dd7009f4b0",
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
# Sample variable values.
|
||||||
|
#
|
||||||
|
namespace="default"
|
||||||
|
deployment_name="wait-for-it-admission-controller"
|
||||||
|
replicas=1
|
||||||
|
image="psevestre/wait-for-it-admission-controller"
|
||||||
|
image_prefix=""
|
||||||
|
image_version="latest"
|
||||||
|
k8s_config_context="minikube"
|
277
kubernetes/k8s-admission-controller/src/test/terraform/main.tf
Normal file
277
kubernetes/k8s-admission-controller/src/test/terraform/main.tf
Normal file
@ -0,0 +1,277 @@
|
|||||||
|
|
||||||
|
locals {
|
||||||
|
prefix = var.image_prefix != "" ? "${var.image_prefix}/":""
|
||||||
|
image = "${local.prefix}${var.image}:${var.image_version}"
|
||||||
|
cloud_sdk_image = "${local.prefix}frapsoft/openssl"
|
||||||
|
ns = data.kubernetes_namespace.ns.metadata[0].name
|
||||||
|
|
||||||
|
# Spring SSL Configuration
|
||||||
|
webhook_config_json = jsonencode({
|
||||||
|
server = {
|
||||||
|
port = 443
|
||||||
|
ssl = {
|
||||||
|
"key-store" = "/shared-config/webhook.p12"
|
||||||
|
"key-store-type" = "PKCS12"
|
||||||
|
"key-alias" = "webhook"
|
||||||
|
"key-store-password" = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
admission-controller = {
|
||||||
|
disabled = false
|
||||||
|
image-prefix = "gcr.io/sandboxbv-01"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Resource namespace
|
||||||
|
data "kubernetes_namespace" "ns" {
|
||||||
|
metadata {
|
||||||
|
name = var.namespace
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# TLS Key
|
||||||
|
resource "tls_private_key" "tls" {
|
||||||
|
algorithm = "RSA"
|
||||||
|
}
|
||||||
|
|
||||||
|
# CSR
|
||||||
|
resource "tls_cert_request" "tls" {
|
||||||
|
key_algorithm = "RSA"
|
||||||
|
private_key_pem = tls_private_key.tls.private_key_pem
|
||||||
|
subject {
|
||||||
|
common_name = "${var.deployment_name}.${var.namespace}.svc"
|
||||||
|
}
|
||||||
|
|
||||||
|
dns_names = [
|
||||||
|
var.deployment_name,
|
||||||
|
"${var.deployment_name}.${var.namespace}",
|
||||||
|
"${var.deployment_name}.${var.namespace}.svc",
|
||||||
|
"${var.deployment_name}.${var.namespace}.svc.cluster.local"
|
||||||
|
]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# HTTPS Certificate
|
||||||
|
resource "tls_self_signed_cert" "tls" {
|
||||||
|
key_algorithm = tls_private_key.tls.algorithm
|
||||||
|
private_key_pem = tls_private_key.tls.private_key_pem
|
||||||
|
|
||||||
|
subject {
|
||||||
|
common_name = "${var.deployment_name}.${local.ns}"
|
||||||
|
}
|
||||||
|
|
||||||
|
validity_period_hours = 24*365*20
|
||||||
|
|
||||||
|
dns_names = [
|
||||||
|
var.deployment_name,
|
||||||
|
"${var.deployment_name}.${var.namespace}",
|
||||||
|
"${var.deployment_name}.${var.namespace}.svc",
|
||||||
|
"${var.deployment_name}.${var.namespace}.svc.cluster.local"
|
||||||
|
]
|
||||||
|
|
||||||
|
allowed_uses = [
|
||||||
|
"key_encipherment",
|
||||||
|
"digital_signature",
|
||||||
|
"server_auth"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Certificado
|
||||||
|
# Obs: Desativado pois o certificado fica preso no estado "Issued"
|
||||||
|
resource "kubernetes_certificate_signing_request" "tls" {
|
||||||
|
count = 0
|
||||||
|
metadata {
|
||||||
|
name = "${var.deployment_name}.${var.namespace}"
|
||||||
|
}
|
||||||
|
|
||||||
|
auto_approve = true
|
||||||
|
|
||||||
|
spec {
|
||||||
|
usages = [
|
||||||
|
"key encipherment",
|
||||||
|
"digital signature",
|
||||||
|
"server auth"
|
||||||
|
]
|
||||||
|
|
||||||
|
signer_name = "kubernetes.io/kubelet-serving"
|
||||||
|
|
||||||
|
request = tls_cert_request.tls.cert_request_pem
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Secret to store TLS key/cert
|
||||||
|
resource "kubernetes_secret" "tls" {
|
||||||
|
metadata {
|
||||||
|
namespace = local.ns
|
||||||
|
name = var.deployment_name
|
||||||
|
}
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"webhook-key.pem" = tls_private_key.tls.private_key_pem
|
||||||
|
"webhook-cert.pem" = tls_self_signed_cert.tls.cert_pem
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# Deployment
|
||||||
|
resource "kubernetes_deployment" "main" {
|
||||||
|
metadata {
|
||||||
|
name = var.deployment_name
|
||||||
|
namespace = local.ns
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
replicas = var.replicas
|
||||||
|
selector {
|
||||||
|
match_labels = {
|
||||||
|
app = var.deployment_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
metadata {
|
||||||
|
labels = {
|
||||||
|
app = var.deployment_name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
container {
|
||||||
|
image = local.image
|
||||||
|
name = var.deployment_name
|
||||||
|
volume_mount {
|
||||||
|
mount_path = "/shared-config"
|
||||||
|
name = "shared-config"
|
||||||
|
}
|
||||||
|
|
||||||
|
env {
|
||||||
|
name = "SPRING_APPLICATION_JSON"
|
||||||
|
value = local.webhook_config_json
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
init_container {
|
||||||
|
name = "setup-keystore"
|
||||||
|
image = local.cloud_sdk_image
|
||||||
|
|
||||||
|
args = [
|
||||||
|
"pkcs12", "-export",
|
||||||
|
"-in", "/secret/webhook-cert.pem",
|
||||||
|
"-inkey", "/secret/webhook-key.pem",
|
||||||
|
"-name", "webhook",
|
||||||
|
"-out", "/shared-config/webhook.p12",
|
||||||
|
"-passout", "pass:"
|
||||||
|
]
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
mount_path = "/shared-config"
|
||||||
|
name = "shared-config"
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
mount_path = "/secret/webhook-cert.pem"
|
||||||
|
name = "webhook-secret"
|
||||||
|
sub_path = "webhook-cert.pem"
|
||||||
|
}
|
||||||
|
|
||||||
|
volume_mount {
|
||||||
|
mount_path = "/secret/webhook-key.pem"
|
||||||
|
name = "webhook-secret"
|
||||||
|
sub_path = "webhook-key.pem"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
volume {
|
||||||
|
name = "shared-config"
|
||||||
|
empty_dir {}
|
||||||
|
}
|
||||||
|
|
||||||
|
volume {
|
||||||
|
name = "webhook-secret"
|
||||||
|
secret {
|
||||||
|
secret_name = kubernetes_secret.tls.metadata[0].name
|
||||||
|
items {
|
||||||
|
key = "webhook-cert.pem"
|
||||||
|
path = "webhook-cert.pem"
|
||||||
|
}
|
||||||
|
items {
|
||||||
|
key = "webhook-key.pem"
|
||||||
|
path = "webhook-key.pem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service
|
||||||
|
resource "kubernetes_service" "svc" {
|
||||||
|
metadata {
|
||||||
|
name = var.deployment_name
|
||||||
|
namespace = local.ns
|
||||||
|
}
|
||||||
|
|
||||||
|
spec {
|
||||||
|
selector = {
|
||||||
|
"app" = var.deployment_name
|
||||||
|
}
|
||||||
|
|
||||||
|
port {
|
||||||
|
name = "https"
|
||||||
|
port = 443
|
||||||
|
protocol = "TCP"
|
||||||
|
target_port = 443
|
||||||
|
}
|
||||||
|
|
||||||
|
type = "ClusterIP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Admission Controller
|
||||||
|
resource "kubernetes_mutating_webhook_configuration" "waitforit" {
|
||||||
|
metadata {
|
||||||
|
name = var.deployment_name
|
||||||
|
}
|
||||||
|
|
||||||
|
webhook {
|
||||||
|
name = var.admission_controller_name
|
||||||
|
admission_review_versions = [ "v1", "v1beta1" ]
|
||||||
|
|
||||||
|
#failure_policy = "Ignore" #
|
||||||
|
|
||||||
|
client_config {
|
||||||
|
|
||||||
|
service {
|
||||||
|
name = kubernetes_service.svc.metadata[0].name
|
||||||
|
namespace = local.ns
|
||||||
|
path = "/mutate"
|
||||||
|
port = 443
|
||||||
|
}
|
||||||
|
|
||||||
|
# IMPORTANT: CA_BUNDLE must be Base64-encoded
|
||||||
|
ca_bundle = tls_self_signed_cert.tls.cert_pem
|
||||||
|
}
|
||||||
|
|
||||||
|
rule {
|
||||||
|
api_groups = [ "*" ]
|
||||||
|
api_versions = [ "*" ]
|
||||||
|
operations = [ "CREATE", "UPDATE" ]
|
||||||
|
resources = [ "deployments", "statefulsets" ]
|
||||||
|
}
|
||||||
|
|
||||||
|
side_effects = "None" #
|
||||||
|
}
|
||||||
|
|
||||||
|
depends_on = [
|
||||||
|
kubernetes_deployment.main
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
terraform {
|
||||||
|
required_providers {
|
||||||
|
kubernetes = {
|
||||||
|
source = "hashicorp/kubernetes"
|
||||||
|
version = "2.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Use standard kubectl environment to get connection details
|
||||||
|
provider "kubernetes" {
|
||||||
|
config_context = var.k8s_config_context
|
||||||
|
config_path = var.k8s_config_path
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
variable "namespace" {
|
||||||
|
type = string
|
||||||
|
description = "Namespace where the Admission Controller will be deploymed"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "deployment_name" {
|
||||||
|
type = string
|
||||||
|
description = "Admission Controller Deployment Name"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "replicas" {
|
||||||
|
type = number
|
||||||
|
description = "Number of replicas used in the deployment"
|
||||||
|
default = 3
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "image" {
|
||||||
|
type = string
|
||||||
|
description = "Admission Controller image name"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "image_version" {
|
||||||
|
type = string
|
||||||
|
description = "Admission Controller image version name"
|
||||||
|
default = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "image_prefix" {
|
||||||
|
type = string
|
||||||
|
description = "Image repository prefix"
|
||||||
|
default = "gcr.io/baeldung"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "admission_controller_name" {
|
||||||
|
type = string
|
||||||
|
description = "Admission Controller name"
|
||||||
|
default = "wait-for-it.service.local"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "k8s_config_context" {
|
||||||
|
type = string
|
||||||
|
description = "Name of the K8S config context"
|
||||||
|
}
|
||||||
|
|
||||||
|
variable "k8s_config_path" {
|
||||||
|
type = string
|
||||||
|
description = "Location of the standard K8S configuration"
|
||||||
|
default = "~/.kube/config"
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<artifactId>kubernetes</artifactId>
|
<artifactId>kubernetes</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
@ -13,5 +12,6 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>k8s-intro</module>
|
<module>k8s-intro</module>
|
||||||
</modules>
|
<module>k8s-admission-controller</module>
|
||||||
|
</modules>
|
||||||
</project>
|
</project>
|
Loading…
x
Reference in New Issue
Block a user