Skip to content

Commit fda7ee7

Browse files
committed
Cover generated Helm chart by Dekorate
Fix #111
1 parent 1f13b7e commit fda7ee7

16 files changed

+158
-181
lines changed

.github/workflows/pr.yml

+6
Original file line numberDiff line numberDiff line change
@@ -96,3 +96,9 @@ jobs:
9696
./run_tests_with_dekorate_in_k8s.sh $KIND_REGISTRY k8s
9797
- name: Delete Project using K8s
9898
run: kubectl delete namespace k8s
99+
- name: Build Project using the generated Helm by Dekorate
100+
run: |
101+
kubectl create namespace genhelm
102+
./run_tests_with_generated_helm_in_k8s.sh $KIND_REGISTRY genhelm
103+
- name: Delete Project using the generated Helm by Dekorate
104+
run: kubectl delete namespace genhelm

pom.xml

+9
Original file line numberDiff line numberDiff line change
@@ -261,6 +261,15 @@
261261
</dependency>
262262
</dependencies>
263263
</profile>
264+
<profile>
265+
<id>helm</id>
266+
<dependencies>
267+
<dependency>
268+
<groupId>io.dekorate</groupId>
269+
<artifactId>helm-annotations</artifactId>
270+
</dependency>
271+
</dependencies>
272+
</profile>
264273
<profile>
265274
<id>openshift-it</id>
266275
<build>

run_tests_with_dekorate_in_k8s.sh

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
#!/usr/bin/env bash
22
CONTAINER_REGISTRY=${1:-localhost:5000}
33
K8S_NAMESPACE=${2:-k8s}
4+
MAVEN_OPTS=${3:-}
45

56
source scripts/waitFor.sh
67

78
kubectl config set-context --current --namespace=$K8S_NAMESPACE
89

9-
# Create ConfigMap
10-
kubectl create -f .github/configmap.yaml
11-
1210
# deploy application and run tests
13-
./mvnw -s .github/mvn-settings.xml clean verify -Pkubernetes,kubernetes-it -Ddekorate.docker.registry=$CONTAINER_REGISTRY -Dkubernetes.namespace=$K8S_NAMESPACE -Ddekorate.push=true
11+
./mvnw -s .github/mvn-settings.xml clean verify -Pkubernetes,kubernetes-it -Ddekorate.docker.registry=$CONTAINER_REGISTRY -Dkubernetes.namespace=$K8S_NAMESPACE -Ddekorate.push=true $MAVEN_OPTS
1412

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#!/usr/bin/env bash
2+
CONTAINER_REGISTRY=${1:-localhost:5000}
3+
K8S_NAMESPACE=${2:-genhelm}
4+
5+
source scripts/waitFor.sh
6+
oc project $K8S_NAMESPACE
7+
8+
# Build
9+
./mvnw -s .github/mvn-settings.xml clean package -Pkubernetes,helm -DskipTests -Ddekorate.options.properties-profile=helm
10+
11+
# Create docker image and tag it in registry
12+
IMAGE=configmap:genhelm
13+
docker build . -t $IMAGE
14+
docker tag $IMAGE $CONTAINER_REGISTRY/$IMAGE
15+
docker push $CONTAINER_REGISTRY/$IMAGE
16+
17+
helm install configmap ./target/classes/META-INF/dekorate/helm/configmap --set app.image=$CONTAINER_REGISTRY/$IMAGE -n $K8S_NAMESPACE
18+
if [[ $(waitFor "configmap" "app.kubernetes.io/name") -eq 1 ]] ; then
19+
echo "Application failed to deploy. Aborting"
20+
exit 1
21+
fi
22+
23+
# Run Tests
24+
./mvnw -s .github/mvn-settings.xml clean verify -Pkubernetes-it -Dunmanaged-test=true -Dkubernetes.namespace=$K8S_NAMESPACE

src/main/java/dev/snowdrop/example/service/GreetingController.java

+5
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,9 @@ public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") S
4040
String message = String.format(properties.getMessage(), name);
4141
return new Greeting(message);
4242
}
43+
44+
@RequestMapping("/api/greeting/message")
45+
public String greetingMessage() {
46+
return properties.getMessage();
47+
}
4348
}
+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
management.endpoints.web.exposure.include: health,info
2+
# Dekorate
3+
dekorate:
4+
helm:
5+
name: configmap
6+
kubernetes:
7+
labels:
8+
- key: app
9+
value: configmap
10+
options:
11+
input-path: kubernetes
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
management.endpoints.web.exposure.include: health,info
2+
# Dekorate
3+
dekorate:
4+
kubernetes:
5+
labels:
6+
- key: app
7+
value: configmap
8+
options:
9+
input-path: kubernetes

src/main/resources/application-openshift.yml

+3
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,8 @@ management.endpoints.web.exposure.include: health,info
33
dekorate:
44
openshift:
55
expose: true
6+
labels:
7+
- key: app
8+
value: configmap
69
s2i:
710
builder-image: registry.access.redhat.com/ubi8/openjdk-11:1.14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
apiVersion: v1
2+
kind: List
3+
items:
4+
# ConfigMap to be created to pass application.yml content to Spring Boot
5+
- apiVersion: v1
6+
kind: ConfigMap
7+
metadata:
8+
name: app-config
9+
data:
10+
application.yml: |
11+
# This properties file should be used to initialise a ConfigMap
12+
greeting:
13+
message: "Hello %s from a ConfigMap!"
14+
- kind: Role
15+
apiVersion: rbac.authorization.k8s.io/v1
16+
metadata:
17+
name: app-config-role
18+
rules:
19+
- apiGroups: [ "" ]
20+
resources: [ "configmaps", "pods", "services", "endpoints", "secrets" ]
21+
verbs: [ "get", "list", "watch" ]
22+
- kind: RoleBinding
23+
apiVersion: rbac.authorization.k8s.io/v1
24+
metadata:
25+
name: app-config-role-binding
26+
subjects:
27+
- kind: ServiceAccount
28+
name: default
29+
apiGroup: ""
30+
roleRef:
31+
kind: Role
32+
name: app-config-role
33+
apiGroup: ""

src/test/java/dev/snowdrop/example/AbstractIT.java

+51-42
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,22 @@
1919
import static io.restassured.RestAssured.given;
2020
import static org.awaitility.Awaitility.await;
2121
import static org.hamcrest.core.Is.is;
22-
import static org.junit.jupiter.api.Assertions.assertNotNull;
22+
import static org.junit.jupiter.api.Assertions.assertEquals;
23+
import static org.junit.jupiter.api.Assertions.assertTrue;
2324

25+
import java.io.IOException;
2426
import java.util.concurrent.TimeUnit;
2527

28+
import org.apache.commons.lang3.StringUtils;
2629
import org.junit.jupiter.api.Test;
2730

2831
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
32+
import io.fabric8.kubernetes.api.model.Pod;
33+
import io.fabric8.kubernetes.api.model.PodList;
2934
import io.fabric8.kubernetes.client.KubernetesClient;
30-
import io.fabric8.kubernetes.client.dsl.ScalableResource;
35+
import io.fabric8.kubernetes.client.LocalPortForward;
36+
import io.fabric8.kubernetes.client.dsl.FilterWatchListDeletable;
37+
import io.fabric8.kubernetes.client.internal.readiness.Readiness;
3138

3239
public abstract class AbstractIT {
3340

@@ -36,42 +43,59 @@ public abstract class AbstractIT {
3643
protected static final String GREETING_PATH = "api/greeting";
3744

3845
@Test
39-
public void testConfigMapLifecycle() {
46+
public void testConfigMapLifecycle() throws IOException {
4047
// Endpoint should say Hello at the beginning as ConfigMap must have been loaded before running the test
4148
verifyEndpoint("Hello");
4249

4350
// Verify the name parameter is properly replaced in the greetings sentence.
44-
given().param("name", "John")
45-
.when()
46-
.get(baseURL() + GREETING_PATH)
47-
.then()
48-
.statusCode(200)
49-
.body("content", is("Hello John from a ConfigMap!"));
51+
try (LocalPortForward appPort = kubernetesClient().services().withName(GREETING_NAME).portForward(8080)) {
52+
given().param("name", "John")
53+
.get("http://localhost:" + appPort.getLocalPort() + "/" + GREETING_PATH)
54+
.then()
55+
.statusCode(200)
56+
.body("content", is("Hello John from a ConfigMap!"));
57+
}
5058

5159
// Verify the app is updated when the config map changes
5260
updateConfigMap();
53-
rolloutChanges();
54-
waitForApp();
61+
stopApplication();
62+
startApplication();
5563
verifyEndpoint("Bonjour");
5664

5765
// Verify the app is updated when the config map is deleted
5866
deleteConfigMap();
59-
rolloutChanges();
67+
stopApplication();
68+
startApplication();
6069
await().atMost(5, TimeUnit.MINUTES)
61-
.catchUncaughtExceptions()
62-
.untilAsserted(() -> given().get(baseURL() + GREETING_PATH)
63-
.then().statusCode(500));
70+
.ignoreExceptions()
71+
.untilAsserted(() -> {
72+
try (LocalPortForward appPort = kubernetesClient().services().withName(GREETING_NAME).portForward(8080)) {
73+
String message = given().get("http://localhost:" + appPort.getLocalPort() + "/" + GREETING_PATH + "/message")
74+
.then().extract().asString();
75+
assertTrue(StringUtils.isEmpty(message));
76+
}
77+
}
78+
);
6479
}
6580

66-
protected abstract String baseURL();
6781
protected abstract KubernetesClient kubernetesClient();
68-
protected abstract ScalableResource<?> deployment();
82+
83+
protected void stopApplication() {
84+
pods().delete();
85+
}
6986

7087
private void verifyEndpoint(final String greeting) {
71-
given().get(baseURL() + GREETING_PATH)
72-
.then()
73-
.statusCode(200)
74-
.body("content", is(String.format("%s World from a ConfigMap!", greeting)));
88+
await().atMost(5, TimeUnit.MINUTES)
89+
.ignoreExceptions()
90+
.untilAsserted(() -> {
91+
try (LocalPortForward appPort = kubernetesClient().services().withName(GREETING_NAME).portForward(8080)) {
92+
given().get("http://localhost:" + appPort.getLocalPort() + "/" + GREETING_PATH)
93+
.then()
94+
.statusCode(200)
95+
.body("content", is(String.format("%s World from a ConfigMap!", greeting)));
96+
}
97+
}
98+
);
7599
}
76100

77101
private void updateConfigMap() {
@@ -88,31 +112,16 @@ private void deleteConfigMap() {
88112
.delete();
89113
}
90114

91-
private void rolloutChanges() {
92-
scale(0);
93-
scale(1);
94-
}
95-
96-
private void scale(int replicas) {
97-
ScalableResource<?> deployment = deployment();
98-
assertNotNull(deployment, "Deployment with name '" + GREETING_NAME + "' not found!");
99-
deployment.scale(replicas);
100-
101-
await().atMost(5, TimeUnit.MINUTES)
102-
.until(() -> {
103-
ScalableResource<?> updatedDeployment = deployment();
104-
return updatedDeployment != null && updatedDeployment.scale().getStatus().getReplicas() == replicas;
105-
});
106-
}
107-
108-
private void waitForApp() {
115+
private void startApplication() {
109116
await().atMost(5, TimeUnit.MINUTES)
110117
.ignoreExceptions()
111118
.untilAsserted(
112-
() -> given()
113-
.get(baseURL() + GREETING_PATH)
114-
.then().statusCode(200)
119+
() -> assertEquals(1, pods().list().getItems().stream().filter(Readiness::isPodReady).count())
115120
);
116121
}
117122

123+
private FilterWatchListDeletable<Pod, PodList> pods() {
124+
return kubernetesClient().pods().withLabel("app", "configmap");
125+
}
126+
118127
}

src/test/java/dev/snowdrop/example/AbstractKubernetesIT.java

-29
This file was deleted.

src/test/java/dev/snowdrop/example/AbstractOpenShiftIT.java

-28
This file was deleted.

src/test/java/dev/snowdrop/example/ManagedKubernetesIT.java

+1-26
Original file line numberDiff line numberDiff line change
@@ -16,43 +16,18 @@
1616

1717
package dev.snowdrop.example;
1818

19-
import java.io.IOException;
20-
21-
import org.junit.jupiter.api.AfterEach;
22-
import org.junit.jupiter.api.BeforeEach;
2319
import org.junit.jupiter.api.condition.DisabledIfSystemProperty;
2420

2521
import io.dekorate.testing.annotation.Inject;
2622
import io.dekorate.testing.annotation.KubernetesIntegrationTest;
2723
import io.fabric8.kubernetes.client.KubernetesClient;
28-
import io.fabric8.kubernetes.client.LocalPortForward;
2924

3025
@DisabledIfSystemProperty(named = "unmanaged-test", matches = "true")
3126
@KubernetesIntegrationTest
32-
public class ManagedKubernetesIT extends AbstractKubernetesIT {
27+
public class ManagedKubernetesIT extends AbstractIT {
3328
@Inject
3429
KubernetesClient kubernetesClient;
3530

36-
LocalPortForward appPort;
37-
38-
@BeforeEach
39-
public void setup() {
40-
appPort = kubernetesClient.services().inNamespace(NAMESPACE).withName("configmap")
41-
.portForward(8080);
42-
}
43-
44-
@AfterEach
45-
public void tearDown() throws IOException {
46-
if (appPort != null) {
47-
appPort.close();
48-
}
49-
}
50-
51-
@Override
52-
protected String baseURL() {
53-
return "http://localhost:" + appPort.getLocalPort() + "/";
54-
}
55-
5631
@Override
5732
protected KubernetesClient kubernetesClient() {
5833
return kubernetesClient;

0 commit comments

Comments
 (0)