Skip to content

Commit 293a986

Browse files
author
Corneil du Plessis
authored
Add support for multiple initContainers to KubernetesDeployerProperties. (#466)
* Add support for initContainers to KubernetesDeployerProperties. Fixes #465 * Remove initContainer that has been merged in initContainers.
1 parent 65dbb7d commit 293a986

File tree

4 files changed

+155
-34
lines changed

4 files changed

+155
-34
lines changed

spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/AbstractKubernetesDeployer.java

+11-8
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
package org.springframework.cloud.deployer.spi.kubernetes;
1818

19+
20+
import java.util.Collection;
1921
import java.util.ArrayList;
2022
import java.util.HashMap;
2123
import java.util.List;
@@ -64,6 +66,7 @@
6466
* @author Enrique Medina Montenegro
6567
* @author Ilayaperumal Gopinathan
6668
* @author Chris Bono
69+
* @author Corneil du Plessis
6770
*/
6871
public class AbstractKubernetesDeployer {
6972

@@ -283,12 +286,14 @@ PodSpec createPodSpec(AppDeploymentRequest appDeploymentRequest) {
283286
podSpec.withAffinity(affinity);
284287
}
285288

286-
Container initContainer = this.deploymentPropertiesResolver.getInitContainer(deploymentProperties);
287-
if (initContainer != null) {
288-
if (initContainer.getSecurityContext() == null && containerSecurityContext != null) {
289-
initContainer.setSecurityContext(containerSecurityContext);
289+
Collection<Container> initContainers = this.deploymentPropertiesResolver.getInitContainers(deploymentProperties);
290+
if (initContainers != null && !initContainers.isEmpty()) {
291+
for (Container initContainer : initContainers) {
292+
if (initContainer.getSecurityContext() == null && containerSecurityContext != null) {
293+
initContainer.setSecurityContext(containerSecurityContext);
294+
}
295+
podSpec.addToInitContainers(initContainer);
290296
}
291-
podSpec.addToInitContainers(initContainer);
292297
}
293298

294299
Boolean shareProcessNamespace = this.deploymentPropertiesResolver.getShareProcessNamespace(deploymentProperties);
@@ -310,9 +315,7 @@ PodSpec createPodSpec(AppDeploymentRequest appDeploymentRequest) {
310315

311316
List<Container> allContainers = new ArrayList<>();
312317
allContainers.add(container);
313-
if (initContainer != null) {
314-
allContainers.add(initContainer);
315-
}
318+
316319
allContainers.addAll(additionalContainers);
317320
// only add volumes with corresponding volume mounts in any container.
318321
podSpec.withVolumes(this.deploymentPropertiesResolver.getVolumes(deploymentProperties).stream()

spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/DeploymentPropertiesResolver.java

+65-25
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import java.util.ArrayList;
1919
import java.util.Arrays;
20+
import java.util.Collection;
2021
import java.util.Collections;
2122
import java.util.HashMap;
2223
import java.util.List;
@@ -65,6 +66,7 @@
6566
import org.springframework.cloud.deployer.spi.util.ByteSizeUtils;
6667
import org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;
6768
import org.springframework.core.io.ByteArrayResource;
69+
import org.springframework.lang.Nullable;
6870
import org.springframework.util.Assert;
6971
import org.springframework.util.CollectionUtils;
7072
import org.springframework.util.StringUtils;
@@ -77,6 +79,7 @@
7779
* @author Chris Schaefer
7880
* @author Ilayaperumal Gopinathan
7981
* @author Chris Bono
82+
* @author Corneil du Plessis
8083
*/
8184

8285
class DeploymentPropertiesResolver {
@@ -586,44 +589,79 @@ Affinity getAffinityRules(Map<String, String> kubernetesDeployerProperties) {
586589
return affinity;
587590
}
588591

589-
Container getInitContainer(Map<String, String> kubernetesDeployerProperties) {
592+
Collection<Container> getInitContainers(Map<String, String> kubernetesDeployerProperties) {
593+
Collection<Container> initContainers = new ArrayList<>();
590594
KubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,
591595
this.propertyPrefix + ".initContainer", "initContainer");
592596

593597
// Deployment prop passed in for entire '.initContainer'
594598
InitContainer initContainerProps = deployerProperties.getInitContainer();
595599
if (initContainerProps != null) {
596-
return containerFromProps(initContainerProps);
600+
initContainers.add(containerFromProps(initContainerProps));
601+
} else {
602+
String propertyKey = this.propertyPrefix + ".initContainer";
603+
Container container = initContainerFromProperties(kubernetesDeployerProperties, propertyKey);
604+
if (container != null) {
605+
initContainers.add(container);
606+
} else {
607+
initContainerProps = this.properties.getInitContainer();
608+
if (initContainerProps != null) {
609+
initContainers.add(containerFromProps(initContainerProps));
610+
}
611+
}
597612
}
613+
KubernetesDeployerProperties initContainerDeployerProperties = bindProperties(kubernetesDeployerProperties,
614+
this.propertyPrefix + ".initContainers", "initContainers");
615+
for (InitContainer initContainer : initContainerDeployerProperties.getInitContainers()) {
616+
initContainers.add(containerFromProps(initContainer));
617+
}
618+
if(initContainerDeployerProperties.getInitContainers().isEmpty()) {
619+
for (int i = 0; ; i++) {
620+
String propertyKey = this.propertyPrefix + ".initContainers[" + i + "]";
621+
// Get properties using binding
622+
KubernetesDeployerProperties kubeProps = bindProperties(kubernetesDeployerProperties, propertyKey, "initContainer");
623+
if (kubeProps.getInitContainer() != null) {
624+
initContainers.add(containerFromProps(kubeProps.getInitContainer()));
625+
} else {
626+
// Get properties using FQN
627+
Container initContainer = initContainerFromProperties(kubernetesDeployerProperties, propertyKey);
628+
if (initContainer != null) {
629+
initContainers.add(initContainer);
630+
} else {
631+
// Use default is configured
632+
if (properties.getInitContainers().size() > i) {
633+
initContainers.add(containerFromProps(properties.getInitContainers().get(i)));
634+
}
635+
break;
636+
}
637+
}
638+
}
639+
}
640+
if (!properties.getInitContainers().isEmpty()) {
641+
// Add remaining defaults.
642+
for (int i = initContainers.size(); i < properties.getInitContainers().size(); i++) {
643+
initContainers.add(containerFromProps(properties.getInitContainers().get(i)));
644+
}
645+
}
646+
return initContainers;
647+
}
598648

599-
// Deployment props passed in for specific '.initContainer.<property>'
600-
String containerName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
601-
this.propertyPrefix + ".initContainer.containerName");
602-
String imageName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
603-
this.propertyPrefix + ".initContainer.imageName");
604-
649+
private @Nullable Container initContainerFromProperties(Map<String, String> kubeProps, String propertyKey) {
650+
String containerName = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + ".containerName");
651+
String imageName = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + ".imageName");
605652
if (StringUtils.hasText(containerName) && StringUtils.hasText(imageName)) {
606-
String commandsStr = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
607-
this.propertyPrefix + ".initContainer.commands");
608-
List<String> commands = StringUtils.hasText(commandsStr) ? Arrays.stream(commandsStr.split(",")).collect(Collectors.toList()) : Collections.emptyList();
609-
String envString = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
610-
this.propertyPrefix + ".initContainer.environmentVariables");
611-
List<VolumeMount> vms = this.getInitContainerVolumeMounts(kubernetesDeployerProperties);
653+
String commandsStr = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + ".commands");
654+
List<String> commands = StringUtils.hasText(commandsStr) ? Arrays.asList(commandsStr.split(",")) : Collections.emptyList();
655+
String envString = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + "environmentVariables");
656+
List<VolumeMount> vms = this.getInitContainerVolumeMounts(kubeProps, propertyKey);
612657
return new ContainerBuilder()
613658
.withName(containerName)
614659
.withImage(imageName)
615660
.withCommand(commands)
616-
.withEnv(toEnvironmentVariables((envString != null)? envString.split(","): new String[0]))
661+
.withEnv(toEnvironmentVariables((envString != null) ? envString.split(",") : new String[0]))
617662
.addAllToVolumeMounts(vms)
618663
.build();
619664
}
620-
621-
// Default is global initContainer
622-
initContainerProps = this.properties.getInitContainer();
623-
if (initContainerProps != null) {
624-
return containerFromProps(initContainerProps);
625-
}
626-
627665
return null;
628666
}
629667

@@ -836,9 +874,11 @@ List<VolumeMount> getVolumeMounts(Map<String, String> deploymentProperties) {
836874
* @param deploymentProperties the deployment properties from {@link AppDeploymentRequest}
837875
* @return the configured volume mounts
838876
*/
839-
private List<VolumeMount> getInitContainerVolumeMounts(Map<String, String> deploymentProperties) {
840-
return this.getVolumeMounts(PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,
841-
this.propertyPrefix + ".initContainer.volumeMounts"));
877+
private List<VolumeMount> getInitContainerVolumeMounts(Map<String, String> deploymentProperties, String propertyKey) {
878+
return this.getVolumeMounts(PropertyParserUtils.getDeploymentPropertyValue(
879+
deploymentProperties,
880+
propertyKey + ".volumeMounts")
881+
);
842882
}
843883

844884
private List<VolumeMount> getVolumeMounts(String propertyValue) {

spring-cloud-deployer-kubernetes/src/main/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesDeployerProperties.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -1439,7 +1439,11 @@ public void setTaskServiceAccountName(String taskServiceAccountName) {
14391439
* A custom init container to apply.
14401440
*/
14411441
private InitContainer initContainer;
1442-
1442+
/**
1443+
* Apply multiple custom init containers.
1444+
* Will add initContainer first if it exists and add to list.
1445+
*/
1446+
private List<InitContainer> initContainers = new ArrayList<>();
14431447
/**
14441448
* Lifecycle spec to apply.
14451449
*/
@@ -2494,6 +2498,14 @@ public void setInitContainer(InitContainer initContainer) {
24942498
this.initContainer = initContainer;
24952499
}
24962500

2501+
public List<InitContainer> getInitContainers() {
2502+
return initContainers;
2503+
}
2504+
2505+
public void setInitContainers(List<InitContainer> initContainers) {
2506+
this.initContainers = initContainers;
2507+
}
2508+
24972509
public List<Container> getAdditionalContainers() {
24982510
return this.additionalContainers;
24992511
}

spring-cloud-deployer-kubernetes/src/test/java/org/springframework/cloud/deployer/spi/kubernetes/KubernetesAppDeployerTests.java

+66
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
* @author Chris Schaefer
8787
* @author Enrique Medina Montenegro
8888
* @author Chris Bono
89+
* @author Corneil du Plessis
8990
*/
9091
@DisplayName("KubernetesAppDeployer")
9192
public class KubernetesAppDeployerTests {
@@ -891,6 +892,71 @@ public void testConfigMapKeyRefGlobalFromYaml() throws Exception {
891892
assertThat(configMapKeySelector.getName()).as("Unexpected config map name").isEqualTo("myConfigMap");
892893
assertThat(configMapKeySelector.getKey()).as("Unexpected config map data key").isEqualTo("envName");
893894
}
895+
@Test
896+
public void testInitContainerProperties() {
897+
Map<String, String> props = new HashMap<>();
898+
props.put("spring.cloud.deployer.kubernetes.initContainer", "{ \"imageName\": \"busybox:1\", \"containerName\": \"bb_s1\", \"commands\": [\"sh\", \"-c\", \"script1.sh\"] }");
899+
900+
AppDefinition definition = new AppDefinition("app-test", null);
901+
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);
902+
903+
deployer = k8sAppDeployer(new KubernetesDeployerProperties());
904+
PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);
905+
assertThat(podSpec.getInitContainers()).isNotEmpty();
906+
Container container = podSpec.getInitContainers().get(0);
907+
assertThat(container.getImage()).isEqualTo("busybox:1");
908+
assertThat(container.getName()).isEqualTo("bb_s1");
909+
assertThat(container.getCommand()).containsExactly("sh", "-c", "script1.sh");
910+
}
911+
@Test
912+
public void testInitContainerJsonArrayProperties() {
913+
Map<String, String> props = new HashMap<>();
914+
props.put("spring.cloud.deployer.kubernetes.init-containers", "[{ \"imageName\": \"busybox:1\", \"containerName\": \"bb_s1\", \"commands\": [\"sh\", \"-c\", \"script1.sh\"] }]");
915+
916+
AppDefinition definition = new AppDefinition("app-test", null);
917+
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);
918+
919+
deployer = k8sAppDeployer(new KubernetesDeployerProperties());
920+
PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);
921+
assertThat(podSpec.getInitContainers()).isNotEmpty();
922+
Container container = podSpec.getInitContainers().get(0);
923+
assertThat(container.getImage()).isEqualTo("busybox:1");
924+
assertThat(container.getName()).isEqualTo("bb_s1");
925+
assertThat(container.getCommand()).containsExactly("sh", "-c", "script1.sh");
926+
}
927+
@Test
928+
public void testMultipleInitContainerProperties() {
929+
Map<String, String> props = new HashMap<>();
930+
props.put("spring.cloud.deployer.kubernetes.initContainers[0]", "{ \"imageName\": \"busybox:1\", \"containerName\": \"bb_s1\", \"commands\": [\"sh\", \"-c\", \"script1.sh\"] }");
931+
props.put("spring.cloud.deployer.kubernetes.initContainers[1].imageName", "busybox:2");
932+
props.put("spring.cloud.deployer.kubernetes.initContainers[1].containerName", "bb_s2");
933+
props.put("spring.cloud.deployer.kubernetes.initContainers[1].commands", "sh,-c,script2.sh");
934+
props.put("spring.cloud.deployer.kubernetes.initContainers[2]", "{ \"imageName\": \"busybox:3\", \"containerName\": \"bb_s3\", \"commands\": [\"sh\", \"-c\", \"script3.sh\"], \"volumeMounts\": [{\"mountPath\": \"/data\", \"name\": \"s3vol\", \"readOnly\": true}] }");
935+
936+
AppDefinition definition = new AppDefinition("app-test", null);
937+
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);
938+
939+
deployer = k8sAppDeployer(new KubernetesDeployerProperties());
940+
PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);
941+
assertThat(podSpec.getInitContainers()).isNotEmpty();
942+
assertThat(podSpec.getInitContainers().size()).isEqualTo(3);
943+
Container container0 = podSpec.getInitContainers().get(0);
944+
assertThat(container0.getImage()).isEqualTo("busybox:1");
945+
assertThat(container0.getName()).isEqualTo("bb_s1");
946+
assertThat(container0.getCommand()).containsExactly("sh", "-c", "script1.sh");
947+
Container container1 = podSpec.getInitContainers().get(1);
948+
assertThat(container1.getImage()).isEqualTo("busybox:2");
949+
assertThat(container1.getName()).isEqualTo("bb_s2");
950+
assertThat(container1.getCommand()).containsExactly("sh", "-c", "script2.sh");
951+
Container container2 = podSpec.getInitContainers().get(2);
952+
assertThat(container2.getImage()).isEqualTo("busybox:3");
953+
assertThat(container2.getName()).isEqualTo("bb_s3");
954+
assertThat(container2.getCommand()).containsExactly("sh", "-c", "script3.sh");
955+
assertThat(container2.getVolumeMounts()).isNotEmpty();
956+
assertThat(container2.getVolumeMounts().get(0).getName()).isEqualTo("s3vol");
957+
assertThat(container2.getVolumeMounts().get(0).getMountPath()).isEqualTo("/data");
958+
assertThat(container2.getVolumeMounts().get(0).getReadOnly()).isTrue();
959+
}
894960

895961
@Test
896962
public void testNodeAffinityProperty() {

0 commit comments

Comments
 (0)