Skip to content

Commit 181e036

Browse files
Merge pull request #49 from xenit-eu/ACC-2066
ACC-2066: Customizers for K3SContainer
2 parents cfddb50 + 0ebb682 commit 181e036

31 files changed

+1196
-223
lines changed

contentgrid-junit-jupiter-k8s/README.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,29 @@ ls -ltr /tmp/kubeconfig*.yml
3030

3131
```java
3232
import com.contentgrid.junit.jupiter.k8s.KubernetesTestCluster;
33+
import com.contentgrid.junit.jupiter.k8s.providers.K3sTestcontainersClusterProvider;
34+
import com.contentgrid.testcontainers.k3s.customizer.ClusterDomainsK3sContainerCustomizer;
3335
import io.fabric8.kubernetes.client.KubernetesClient;
3436
import org.junit.jupiter.api.Test;
3537
import static org.assertj.core.api.Assertions.assertThat;
3638

37-
@KubernetesTestCluster
39+
@KubernetesTestCluster(providers = CustomClusterProvider.class)
3840
class MyKubernetesTest {
41+
42+
public static class CustomClusterProvider extends K3sTestcontainersClusterProvider {
43+
44+
public CustomClusterProvider() {
45+
customize(container -> {
46+
// modify the K3sContainer directly
47+
});
48+
49+
// Or use pre-defined customizers to set up some aspects
50+
configure(
51+
ClusterDomainsK3sContainerCustomizer.class,
52+
domains -> domains.withDomains("example.test")
53+
);
54+
}
55+
}
3956

4057
static KubernetesClient kubernetesClient; // Injected Kubernetes client
4158

contentgrid-junit-jupiter-k8s/src/main/java/com/contentgrid/junit/jupiter/k8s/K8sTestUtils.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,38 +29,44 @@ public static void waitUntilDeploymentsReady(int timeout, List<String> deploymen
2929

3030
new KubernetesResourceWaiter(kubernetesClient)
3131
.deployments(ResourceMatcher.named(deployments.toArray(String[]::new)).inNamespace(namespace))
32-
.await(createAwait(timeout));
32+
.await(createAwait(timeout))
33+
.close();
3334
}
3435

3536
public static void waitUntilDeploymentsReady(int timeout, List<String> deployments,
3637
KubernetesClient kubernetesClient) {
3738
new KubernetesResourceWaiter(kubernetesClient)
3839
.deployments(ResourceMatcher.named(deployments.toArray(String[]::new)))
39-
.await(createAwait(timeout));
40+
.await(createAwait(timeout))
41+
.close();
4042
}
4143

4244
public static void waitUntilStatefulSetsReady(int timeout, List<String> statefulSets, KubernetesClient kubernetesClient) {
4345
new KubernetesResourceWaiter(kubernetesClient)
4446
.statefulSets(ResourceMatcher.named(statefulSets.toArray(String[]::new)))
45-
.await(createAwait(timeout));
47+
.await(createAwait(timeout))
48+
.close();
4649
}
4750

4851
public static void waitUntilStatefulSetsReady(int timeout, List<String> statefulSets,
4952
KubernetesClient kubernetesClient, String namespace) {
5053
new KubernetesResourceWaiter(kubernetesClient)
5154
.statefulSets(ResourceMatcher.named(statefulSets.toArray(String[]::new)).inNamespace(namespace))
52-
.await(createAwait(timeout));
55+
.await(createAwait(timeout))
56+
.close();
5357
}
5458

5559
public static void waitUntilReplicaSetsReady(int timeout, List<String> replicaSets, KubernetesClient kubernetesClient) {
5660
new KubernetesResourceWaiter(kubernetesClient)
5761
.include(ReplicaSet.class, ResourceMatcher.named(replicaSets.toArray(String[]::new)))
58-
.await(createAwait(timeout));
62+
.await(createAwait(timeout))
63+
.close();
5964
}
6065

6166
public static void waitUntilReplicaSetsReady(int timeout, List<String> replicaSets, KubernetesClient kubernetesClient, String namespace) {
6267
new KubernetesResourceWaiter(kubernetesClient)
6368
.include(ReplicaSet.class, ResourceMatcher.named(replicaSets.toArray(String[]::new)).inNamespace(namespace))
64-
.await(createAwait(timeout));
69+
.await(createAwait(timeout))
70+
.close();
6571
}
6672
}
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,27 @@
11
package com.contentgrid.junit.jupiter.k8s.providers;
22

3-
import com.contentgrid.testcontainers.k3s.K3sCiliumContainer;
3+
import com.contentgrid.testcontainers.k3s.customizer.cilium.DefaultDenyCiliumK3sContainerCustomizer;
4+
import com.contentgrid.testcontainers.k3s.customizer.ingress.TraefikIngressK3sContainerCustomizer;
45

6+
/**
7+
* Use a customized variant of {@link K3sTestcontainersClusterProvider}
8+
* <code>
9+
* class CustomClusterProvider extends K3sTestcontainersClusterProvider {
10+
* public CustomClusterProvider() {
11+
* configure(DefaultDenyCiliumK3sContainerCustomizer.class);
12+
* configure(TraefikIngressK3sContainerCustomizer.class);
13+
* }
14+
* }
15+
* </code>
16+
*
17+
* @deprecated Replaced by the more flexible {@link com.contentgrid.testcontainers.k3s.customizer.K3sContainerCustomizer} system
18+
*/
19+
@Deprecated(since = "0.1.0", forRemoval = true)
520
public class K3sCiliumDefaultDenyCoreDNSClusterProvider extends K3sTestcontainersClusterProvider {
621

722
public K3sCiliumDefaultDenyCoreDNSClusterProvider() {
8-
super(new K3sCiliumContainer(K3sTestcontainersClusterProvider.IMAGE_RANCHER_K3S, true));
23+
configure(TraefikIngressK3sContainerCustomizer.class);
24+
configure(DefaultDenyCiliumK3sContainerCustomizer.class);
925
}
1026

1127
}
Lines changed: 15 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,33 @@
11
package com.contentgrid.junit.jupiter.k8s.providers;
22

3-
import com.fasterxml.jackson.annotation.JsonIgnore;
4-
import com.fasterxml.jackson.annotation.JsonInclude;
5-
import com.fasterxml.jackson.annotation.JsonInclude.Include;
6-
import com.fasterxml.jackson.databind.ObjectMapper;
7-
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
8-
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
9-
import java.io.Closeable;
10-
import java.util.ArrayList;
11-
import java.util.HashMap;
12-
import java.util.List;
13-
import java.util.Map;
14-
import java.util.Optional;
3+
import com.contentgrid.testcontainers.k3s.customizer.K3sContainerCustomizer;
4+
import com.contentgrid.testcontainers.k3s.customizer.K3sContainerCustomizers;
5+
import com.contentgrid.testcontainers.k3s.customizer.K3sContainerCustomizersImpl;
156
import lombok.AccessLevel;
16-
import lombok.Data;
17-
import lombok.Getter;
18-
import lombok.NoArgsConstructor;
197
import lombok.NonNull;
208
import lombok.RequiredArgsConstructor;
21-
import lombok.SneakyThrows;
9+
import lombok.experimental.Delegate;
2210
import lombok.extern.slf4j.Slf4j;
2311
import org.testcontainers.DockerClientFactory;
24-
import org.testcontainers.images.builder.Transferable;
2512
import org.testcontainers.k3s.K3sContainer;
2613
import org.testcontainers.utility.DockerImageName;
2714

15+
/**
16+
* Provides a kubernetes cluster by using a K3S docker container, optionally customized with {@link K3sContainerCustomizer}s
17+
*/
2818
@Slf4j
2919
@RequiredArgsConstructor(access = AccessLevel.PROTECTED)
30-
public class K3sTestcontainersClusterProvider implements KubernetesClusterProvider {
20+
public class K3sTestcontainersClusterProvider implements KubernetesClusterProvider,
21+
K3sContainerCustomizers {
3122

3223
public static final DockerImageName IMAGE_RANCHER_K3S = DockerImageName.parse("rancher/k3s");
3324

3425
@NonNull
3526
protected final K3sContainer container;
3627

37-
private final Map<String, String> mirrors = new HashMap<>();
38-
39-
private final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()
40-
.disable(Feature.WRITE_DOC_START_MARKER)
41-
.enable(Feature.INDENT_ARRAYS_WITH_INDICATOR)
42-
);
28+
@NonNull
29+
@Delegate(types = K3sContainerCustomizers.class)
30+
private final K3sContainerCustomizersImpl customizers = new K3sContainerCustomizersImpl();
4331

4432
public K3sTestcontainersClusterProvider() {
4533
this(new K3sContainer(IMAGE_RANCHER_K3S));
@@ -56,38 +44,14 @@ public ProviderEvaluationResult evaluate() {
5644

5745
@Override
5846
public KubernetesProviderResult start() {
59-
6047
log.info("Starting k3s: {}", String.join(" ", this.container.getCommandParts()));
6148

62-
registriesConfigYaml(this.registriesConfig()).ifPresent(yaml -> {
63-
log.info("Configuring K3s registries.yaml:");
64-
log.info(yaml);
65-
this.container.withCopyToContainer(Transferable.of(yaml), "/etc/rancher/k3s/registries.yaml");
66-
});
67-
49+
this.customizers.customize(this.container);
6850
this.container.start();
6951

7052
return new DelegatedKubernetesProviderResult(this.container.getKubeConfigYaml(), this::stop);
7153
}
7254

73-
K3sRegistriesConfiguration registriesConfig() {
74-
var config = new K3sRegistriesConfiguration();
75-
this.mirrors.forEach((name, endpoint) -> {
76-
config.mirrors.put(name, new RegistryMirrorConfig(endpoint));
77-
});
78-
79-
return config;
80-
}
81-
82-
@SneakyThrows
83-
Optional<String> registriesConfigYaml(K3sRegistriesConfiguration config) {
84-
if (config.isEmpty()) {
85-
return Optional.empty();
86-
}
87-
88-
return Optional.of(this.yamlMapper.writeValueAsString(config));
89-
}
90-
9155
@Override
9256
public void stop() {
9357
log.debug("Stopping {}", this.container);
@@ -103,61 +67,8 @@ boolean isDockerAvailable() {
10367
}
10468
}
10569

106-
@RequiredArgsConstructor
107-
static class K3sProviderResult implements KubernetesProviderResult {
108-
109-
@Getter
110-
@NonNull
111-
final String kubeConfigYaml;
112-
113-
@NonNull
114-
final Closeable closeableDelegate;
115-
116-
@Override
117-
public void close() throws Throwable {
118-
this.closeableDelegate.close();
119-
}
120-
}
121-
12270
@Override
123-
public void addDockerRegistryMirror(@NonNull String name, @NonNull String endpoint) {
124-
if (this.container.isRunning()) {
125-
throw new IllegalStateException("container already running");
126-
}
127-
128-
this.mirrors.put(name, endpoint);
129-
}
130-
131-
/**
132-
* Get the host that this container may be reached on (may not be the local machine).
133-
*
134-
* @return docker host or ip address
135-
*/
136-
protected static String getDockerHost() {
137-
return DockerClientFactory.instance().dockerHostIpAddress();
138-
}
139-
140-
141-
@Data
142-
static class K3sRegistriesConfiguration {
143-
144-
@JsonInclude(Include.NON_EMPTY)
145-
Map<String, RegistryMirrorConfig> mirrors = new HashMap<>();
146-
147-
@JsonIgnore
148-
boolean isEmpty() {
149-
return this.mirrors.isEmpty();
150-
}
151-
}
152-
153-
@Data
154-
@NoArgsConstructor
155-
private static class RegistryMirrorConfig {
156-
157-
List<String> endpoint = new ArrayList<>();
158-
159-
public RegistryMirrorConfig(@NonNull String endpoint) {
160-
this.endpoint.add(endpoint);
161-
}
71+
public void addDockerRegistryMirror(String name, String endpoint) {
72+
configure(RegistryMirrorsK3sContainerCustomizer.class, mirrors -> mirrors.withMirror(name, endpoint));
16273
}
16374
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package com.contentgrid.junit.jupiter.k8s.providers;
2+
3+
import com.contentgrid.testcontainers.k3s.customizer.K3sContainerCustomizer;
4+
import com.fasterxml.jackson.annotation.JsonInclude;
5+
import com.fasterxml.jackson.annotation.JsonInclude.Include;
6+
import com.fasterxml.jackson.core.JsonProcessingException;
7+
import com.fasterxml.jackson.databind.ObjectMapper;
8+
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
9+
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator.Feature;
10+
import java.util.ArrayList;
11+
import java.util.HashMap;
12+
import java.util.List;
13+
import java.util.Map;
14+
import java.util.Optional;
15+
import lombok.AccessLevel;
16+
import lombok.Data;
17+
import lombok.NoArgsConstructor;
18+
import lombok.NonNull;
19+
import lombok.RequiredArgsConstructor;
20+
import lombok.SneakyThrows;
21+
import lombok.extern.slf4j.Slf4j;
22+
import org.testcontainers.images.builder.Transferable;
23+
import org.testcontainers.k3s.K3sContainer;
24+
25+
/**
26+
* Configures registry mirrors
27+
*/
28+
@Slf4j
29+
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
30+
class RegistryMirrorsK3sContainerCustomizer implements K3sContainerCustomizer {
31+
32+
@NonNull
33+
private final Map<String, String> mirrors;
34+
35+
public RegistryMirrorsK3sContainerCustomizer() {
36+
this(Map.of());
37+
}
38+
39+
private static final ObjectMapper yamlMapper = new ObjectMapper(new YAMLFactory()
40+
.disable(Feature.WRITE_DOC_START_MARKER)
41+
.enable(Feature.INDENT_ARRAYS_WITH_INDICATOR)
42+
);
43+
44+
public RegistryMirrorsK3sContainerCustomizer withMirror(String name, String endpoint) {
45+
var mirrorsCopy = new HashMap<>(mirrors);
46+
if(mirrorsCopy.putIfAbsent(name, endpoint) != null) {
47+
throw new IllegalArgumentException("Mirror %s is already registered".formatted(name));
48+
}
49+
return new RegistryMirrorsK3sContainerCustomizer(mirrorsCopy);
50+
}
51+
52+
@Override
53+
public void customize(K3sContainer container) {
54+
createRegistriesYaml().ifPresent(yaml -> {
55+
log.info("Configuring K3S registry mirrors");
56+
container.withCopyToContainer(Transferable.of(yaml), "/etc/rancher/k3s/registries.yaml");
57+
});
58+
}
59+
60+
@SneakyThrows(JsonProcessingException.class)
61+
Optional<String> createRegistriesYaml() {
62+
if(mirrors.isEmpty()) {
63+
return Optional.empty();
64+
}
65+
var config = new K3sRegistriesConfiguration();
66+
this.mirrors.forEach((name, endpoint) -> {
67+
config.mirrors.put(name, new RegistryMirrorConfig(endpoint));
68+
});
69+
70+
return Optional.of(yamlMapper.writeValueAsString(config));
71+
}
72+
73+
@Data
74+
static class K3sRegistriesConfiguration {
75+
76+
@JsonInclude(Include.NON_EMPTY)
77+
Map<String, RegistryMirrorConfig> mirrors = new HashMap<>();
78+
79+
}
80+
81+
@Data
82+
@NoArgsConstructor
83+
static class RegistryMirrorConfig {
84+
85+
List<String> endpoint = new ArrayList<>();
86+
87+
public RegistryMirrorConfig(@NonNull String endpoint) {
88+
this.endpoint.add(endpoint);
89+
}
90+
}
91+
}

contentgrid-junit-jupiter-k8s/src/main/java/com/contentgrid/junit/jupiter/k8s/wait/KubernetesResourceWaiter.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,13 +224,14 @@ public Stream<? extends AwaitableResource> nonReadyResources() {
224224
* Wait for all resources to be ready
225225
* @param configuration Configure {@link Awaitility}
226226
*/
227-
public void await(@NonNull UnaryOperator<ConditionFactory> configuration) {
227+
public KubernetesResourceWaiter await(@NonNull UnaryOperator<ConditionFactory> configuration) {
228228
configuration.apply(
229229
Awaitility.await()
230230
.conditionEvaluationListener(new ConditionEvaluationListenerImpl())
231231
)
232232
.until(() -> this.nonReadyResources().toList(), Matchers.empty());
233233

234+
return this;
234235
}
235236

236237
/**

contentgrid-junit-jupiter-k8s/src/main/java/com/contentgrid/junit/jupiter/k8s/wait/resource/AwaitableResource.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ public interface AwaitableResource {
2020
record LogLine(
2121
@NonNull
2222
AwaitableResource resource,
23-
@NonNull
2423
Instant timestamp,
2524
@NonNull
2625
String container,

0 commit comments

Comments
 (0)