Skip to content

Commit b95967d

Browse files
authored
Add support for Image Volumes (strimzi#11467)
Signed-off-by: Jakub Scholz <www@scholzj.com>
1 parent 725ab1a commit b95967d

25 files changed

Lines changed: 936 additions & 134 deletions

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
* Add support for Kafka 3.9.1
66
* Fixed MirrorMaker 2 client rack init container override being ignored.
7+
* Support for Kubernetes Image Volumes to mount custom plugins
78

89
### Major changes, deprecations and removals
910

api/src/main/java/io/strimzi/api/kafka/model/common/template/AdditionalVolume.java

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.fabric8.kubernetes.api.model.CSIVolumeSource;
1010
import io.fabric8.kubernetes.api.model.ConfigMapVolumeSource;
1111
import io.fabric8.kubernetes.api.model.EmptyDirVolumeSource;
12+
import io.fabric8.kubernetes.api.model.ImageVolumeSource;
1213
import io.fabric8.kubernetes.api.model.PersistentVolumeClaimVolumeSource;
1314
import io.fabric8.kubernetes.api.model.SecretVolumeSource;
1415
import io.strimzi.api.kafka.model.common.Constants;
@@ -28,14 +29,15 @@
2829
*/
2930
@Buildable(editableEnabled = false, builderPackage = Constants.FABRIC8_KUBERNETES_API)
3031
@JsonInclude(JsonInclude.Include.NON_NULL)
31-
@JsonPropertyOrder({ "name", "secret", "configMap", "emptyDir", "persistentVolumeClaim", "csi" })
32+
@JsonPropertyOrder({ "name", "secret", "configMap", "emptyDir", "persistentVolumeClaim", "csi", "image" })
3233
@OneOf({
3334
@OneOf.Alternative({
3435
@OneOf.Alternative.Property(value = "secret", required = false),
3536
@OneOf.Alternative.Property(value = "configMap", required = false),
3637
@OneOf.Alternative.Property(value = "emptyDir", required = false),
3738
@OneOf.Alternative.Property(value = "persistentVolumeClaim", required = false),
38-
@OneOf.Alternative.Property(value = "csi", required = false)
39+
@OneOf.Alternative.Property(value = "csi", required = false),
40+
@OneOf.Alternative.Property(value = "image", required = false)
3941
})
4042
})
4143
@EqualsAndHashCode
@@ -47,6 +49,7 @@ public class AdditionalVolume implements UnknownPropertyPreserving {
4749
private EmptyDirVolumeSource emptyDir;
4850
private PersistentVolumeClaimVolumeSource persistentVolumeClaim;
4951
private CSIVolumeSource csi;
52+
private ImageVolumeSource image;
5053
private Map<String, Object> additionalProperties = new HashMap<>(0);
5154

5255
@Description("Name to use for the volume. Required.")
@@ -59,7 +62,7 @@ public void setName(String name) {
5962
this.name = name;
6063
}
6164

62-
@Description("Secret to use populate the volume.")
65+
@Description("`Secret` to use to populate the volume.")
6366
@KubeLink(group = "core", version = "v1", kind = "secretvolumesource")
6467
@JsonInclude(JsonInclude.Include.NON_EMPTY)
6568
public SecretVolumeSource getSecret() {
@@ -70,7 +73,7 @@ public void setSecret(SecretVolumeSource secret) {
7073
this.secret = secret;
7174
}
7275

73-
@Description("ConfigMap to use to populate the volume.")
76+
@Description("`ConfigMap` to use to populate the volume.")
7477
@KubeLink(group = "core", version = "v1", kind = "configmapvolumesource")
7578
@JsonInclude(JsonInclude.Include.NON_EMPTY)
7679
public ConfigMapVolumeSource getConfigMap() {
@@ -81,7 +84,7 @@ public void setConfigMap(ConfigMapVolumeSource configMap) {
8184
this.configMap = configMap;
8285
}
8386

84-
@Description("EmptyDir to use to populate the volume.")
87+
@Description("`EmptyDir` to use to populate the volume.")
8588
@KubeLink(group = "core", version = "v1", kind = "emptydirvolumesource")
8689
@JsonInclude(JsonInclude.Include.NON_EMPTY)
8790
public EmptyDirVolumeSource getEmptyDir() {
@@ -92,7 +95,7 @@ public void setEmptyDir(EmptyDirVolumeSource emptyDir) {
9295
this.emptyDir = emptyDir;
9396
}
9497

95-
@Description("PersistentVolumeClaim object to use to populate the volume.")
98+
@Description("`PersistentVolumeClaim` object to use to populate the volume.")
9699
@KubeLink(group = "core", version = "v1", kind = "persistentvolumeclaimvolumesource")
97100
@JsonInclude(JsonInclude.Include.NON_EMPTY)
98101
public PersistentVolumeClaimVolumeSource getPersistentVolumeClaim() {
@@ -103,7 +106,7 @@ public void setPersistentVolumeClaim(PersistentVolumeClaimVolumeSource persisten
103106
this.persistentVolumeClaim = persistentVolumeClaim;
104107
}
105108

106-
@Description("CSIVolumeSource object to use to populate the volume.")
109+
@Description("`CSIVolumeSource` object to use to populate the volume.")
107110
@KubeLink(group = "core", version = "v1", kind = "csivolumesource")
108111
@JsonInclude(JsonInclude.Include.NON_EMPTY)
109112
public CSIVolumeSource getCsi() {
@@ -114,6 +117,17 @@ public void setCsi(CSIVolumeSource csi) {
114117
this.csi = csi;
115118
}
116119

120+
@Description("`ImageVolumeSource` object to use to populate the volume.")
121+
@KubeLink(group = "core", version = "v1", kind = "imagevolumesource")
122+
@JsonInclude(JsonInclude.Include.NON_EMPTY)
123+
public ImageVolumeSource getImage() {
124+
return image;
125+
}
126+
127+
public void setImage(ImageVolumeSource image) {
128+
this.image = image;
129+
}
130+
117131
@Override
118132
public Map<String, Object> getAdditionalProperties() {
119133
return this.additionalProperties;
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
package io.strimzi.api.kafka.model.connect;
6+
7+
import com.fasterxml.jackson.annotation.JsonInclude;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
10+
import io.strimzi.api.kafka.model.common.Constants;
11+
import io.strimzi.crdgenerator.annotations.Description;
12+
import io.sundr.builder.annotations.Buildable;
13+
import lombok.EqualsAndHashCode;
14+
import lombok.ToString;
15+
16+
/**
17+
* Image Volume artifact represents an artifact which is mounted as a Kubernetes Image Volume
18+
*/
19+
@Buildable(
20+
editableEnabled = false,
21+
builderPackage = Constants.FABRIC8_KUBERNETES_API
22+
)
23+
@JsonInclude(JsonInclude.Include.NON_NULL)
24+
@JsonPropertyOrder({ "type", "reference", "pullPolicy" })
25+
@EqualsAndHashCode(callSuper = true)
26+
@ToString(callSuper = true)
27+
public class ImageArtifact extends MountedArtifact {
28+
private String reference;
29+
private String pullPolicy;
30+
31+
@Description("Must be `" + TYPE_IMAGE + "`")
32+
@Override
33+
@JsonInclude(JsonInclude.Include.NON_NULL)
34+
public String getType() {
35+
return TYPE_IMAGE;
36+
}
37+
38+
@Description("Reference to the container image (OCI artifact) containing the Kafka Connect plugin. " +
39+
"The image is mounted as a volume and provides the plugin binary. " +
40+
"Required.")
41+
@JsonProperty(required = true)
42+
public String getReference() {
43+
return reference;
44+
}
45+
46+
public void setReference(String reference) {
47+
this.reference = reference;
48+
}
49+
50+
@Description("Policy that determines when the container image (OCI artifact) is pulled.\n\n" +
51+
"Possible values are:\n\n" +
52+
"* `Always`: Always pull the image. If the pull fails, container creation fails.\n" +
53+
"* `Never`: Never pull the image. Use only a locally available image. Container creation fails if the image isn’t present.\n" +
54+
"* `IfNotPresent`: Pull the image only if it’s not already available locally. Container creation fails if the image isn’t present and the pull fails.\n\n" +
55+
"Defaults to `Always` if `:latest` tag is specified, or `IfNotPresent` otherwise.")
56+
@JsonInclude(JsonInclude.Include.NON_NULL)
57+
public String getPullPolicy() {
58+
return pullPolicy;
59+
}
60+
61+
public void setPullPolicy(String pullPolicy) {
62+
this.pullPolicy = pullPolicy;
63+
}
64+
}

api/src/main/java/io/strimzi/api/kafka/model/connect/KafkaConnectSpec.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import lombok.ToString;
1919

2020
import java.util.HashMap;
21+
import java.util.List;
2122
import java.util.Map;
2223

2324
@DescriptionFile
@@ -28,7 +29,7 @@
2829
@JsonInclude(JsonInclude.Include.NON_NULL)
2930
@JsonPropertyOrder({ "version", "replicas", "image", "bootstrapServers", "tls", "authentication", "config", "resources",
3031
"livenessProbe", "readinessProbe", "jvmOptions", "jmxOptions", "logging", "clientRackInitImage", "rack",
31-
"metricsConfig", "tracing", "template", "externalConfiguration", "build" })
32+
"metricsConfig", "tracing", "template", "externalConfiguration", "build", "plugins" })
3233
@EqualsAndHashCode(callSuper = true, doNotUseGetters = true)
3334
@ToString(callSuper = true)
3435
public class KafkaConnectSpec extends AbstractKafkaConnectSpec {
@@ -40,6 +41,7 @@ public class KafkaConnectSpec extends AbstractKafkaConnectSpec {
4041
private ClientTls tls;
4142
private KafkaClientAuthentication authentication;
4243
private Build build;
44+
private List<MountedPlugin> plugins;
4345

4446
@JsonInclude(JsonInclude.Include.NON_EMPTY)
4547
@Description("The Kafka Connect configuration. Properties with the following prefixes cannot be set: " + FORBIDDEN_PREFIXES + " (with the exception of: " + FORBIDDEN_PREFIX_EXCEPTIONS + ").")
@@ -91,4 +93,13 @@ public Build getBuild() {
9193
public void setBuild(Build build) {
9294
this.build = build;
9395
}
96+
97+
@Description("List of connector plugins to mount into the `KafkaConnect` pod.")
98+
public List<MountedPlugin> getPlugins() {
99+
return plugins;
100+
}
101+
102+
public void setPlugins(List<MountedPlugin> plugins) {
103+
this.plugins = plugins;
104+
}
94105
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
package io.strimzi.api.kafka.model.connect;
6+
7+
import com.fasterxml.jackson.annotation.JsonInclude;
8+
import com.fasterxml.jackson.annotation.JsonSubTypes;
9+
import com.fasterxml.jackson.annotation.JsonTypeInfo;
10+
import io.strimzi.api.kafka.model.common.UnknownPropertyPreserving;
11+
import io.strimzi.crdgenerator.annotations.Description;
12+
import lombok.EqualsAndHashCode;
13+
import lombok.ToString;
14+
15+
import java.util.HashMap;
16+
import java.util.Map;
17+
18+
/**
19+
* Abstract baseclass for different representations of mounted connector artifacts, discriminated by {@link #getType() type}.
20+
*/
21+
@JsonTypeInfo(
22+
use = JsonTypeInfo.Id.NAME,
23+
include = JsonTypeInfo.As.EXISTING_PROPERTY,
24+
property = "type"
25+
)
26+
@JsonSubTypes(
27+
{
28+
@JsonSubTypes.Type(value = ImageArtifact.class, name = MountedArtifact.TYPE_IMAGE)
29+
}
30+
)
31+
@JsonInclude(JsonInclude.Include.NON_NULL)
32+
@EqualsAndHashCode
33+
@ToString
34+
public abstract class MountedArtifact implements UnknownPropertyPreserving {
35+
public static final String TYPE_IMAGE = "image";
36+
37+
private Map<String, Object> additionalProperties;
38+
39+
@Description("Artifact type. " +
40+
"Currently, the only supported artifact type is `image`.")
41+
public abstract String getType();
42+
43+
@Override
44+
public Map<String, Object> getAdditionalProperties() {
45+
return this.additionalProperties != null ? this.additionalProperties : Map.of();
46+
}
47+
48+
@Override
49+
public void setAdditionalProperty(String name, Object value) {
50+
if (this.additionalProperties == null) {
51+
this.additionalProperties = new HashMap<>(2);
52+
}
53+
this.additionalProperties.put(name, value);
54+
}
55+
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
* Copyright Strimzi authors.
3+
* License: Apache License 2.0 (see the file LICENSE or http://apache.org/licenses/LICENSE-2.0.html).
4+
*/
5+
package io.strimzi.api.kafka.model.connect;
6+
7+
import com.fasterxml.jackson.annotation.JsonInclude;
8+
import com.fasterxml.jackson.annotation.JsonProperty;
9+
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
10+
import io.strimzi.api.kafka.model.common.Constants;
11+
import io.strimzi.api.kafka.model.common.UnknownPropertyPreserving;
12+
import io.strimzi.crdgenerator.annotations.Description;
13+
import io.strimzi.crdgenerator.annotations.DescriptionFile;
14+
import io.strimzi.crdgenerator.annotations.Pattern;
15+
import io.sundr.builder.annotations.Buildable;
16+
import lombok.EqualsAndHashCode;
17+
import lombok.ToString;
18+
19+
import java.util.HashMap;
20+
import java.util.List;
21+
import java.util.Map;
22+
23+
/**
24+
* Represents a connector plugin in Kafka Connect (not in Kafka Connect Build)
25+
*/
26+
@Buildable(
27+
editableEnabled = false,
28+
builderPackage = Constants.FABRIC8_KUBERNETES_API
29+
)
30+
@JsonInclude(JsonInclude.Include.NON_DEFAULT)
31+
@JsonPropertyOrder({ "name", "artifacts" })
32+
@DescriptionFile
33+
@EqualsAndHashCode
34+
@ToString
35+
public class MountedPlugin implements UnknownPropertyPreserving {
36+
private String name;
37+
private List<MountedArtifact> artifacts;
38+
private Map<String, Object> additionalProperties;
39+
40+
@Description("A unique name for the connector plugin. " +
41+
"This name is used to generate the mount path for the connector artifacts. " +
42+
"The name has to be unique within the KafkaConnect resource. " +
43+
"The name must be unique within the `KafkaConnect` resource and match the pattern: `^[a-z][-_a-z0-9]*[a-z]$`. " +
44+
"Required")
45+
@JsonProperty(required = true)
46+
@Pattern("^[a-z0-9][-_a-z0-9]*[a-z0-9]$")
47+
public String getName() {
48+
return name;
49+
}
50+
51+
public void setName(String name) {
52+
this.name = name;
53+
}
54+
55+
@Description("List of artifacts associated with this connector plugin. " +
56+
"Required.")
57+
@JsonProperty(required = true)
58+
public List<MountedArtifact> getArtifacts() {
59+
return artifacts;
60+
}
61+
62+
public void setArtifacts(List<MountedArtifact> artifacts) {
63+
this.artifacts = artifacts;
64+
}
65+
66+
@Override
67+
public Map<String, Object> getAdditionalProperties() {
68+
return this.additionalProperties != null ? this.additionalProperties : Map.of();
69+
}
70+
71+
@Override
72+
public void setAdditionalProperty(String name, Object value) {
73+
if (this.additionalProperties == null) {
74+
this.additionalProperties = new HashMap<>(2);
75+
}
76+
this.additionalProperties.put(name, value);
77+
}
78+
}
79+

0 commit comments

Comments
 (0)