Skip to content

Commit d9b3607

Browse files
author
Corneil du Plessis
committed
Add support for initContainers to KubernetesDeployerProperties.
Fixes spring-attic#465
1 parent 71b83a5 commit d9b3607

File tree

4 files changed

+153
-31
lines changed

4 files changed

+153
-31
lines changed

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

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

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

19+
import java.util.Collection;
1920
import java.util.HashMap;
2021
import java.util.List;
2122
import java.util.Map;
@@ -63,6 +64,7 @@
6364
* @author Enrique Medina Montenegro
6465
* @author Ilayaperumal Gopinathan
6566
* @author Chris Bono
67+
* @author Corneil du Plessis
6668
*/
6769
public class AbstractKubernetesDeployer {
6870

@@ -287,12 +289,14 @@ PodSpec createPodSpec(AppDeploymentRequest appDeploymentRequest) {
287289
podSpec.withAffinity(affinity);
288290
}
289291

290-
Container initContainer = this.deploymentPropertiesResolver.getInitContainer(deploymentProperties);
291-
if (initContainer != null) {
292-
if (initContainer.getSecurityContext() == null && containerSecurityContext != null) {
293-
initContainer.setSecurityContext(containerSecurityContext);
292+
Collection<Container> initContainers = this.deploymentPropertiesResolver.getInitContainers(deploymentProperties);
293+
if (initContainers != null && !initContainers.isEmpty()) {
294+
for (Container initContainer : initContainers) {
295+
if (initContainer.getSecurityContext() == null && containerSecurityContext != null) {
296+
initContainer.setSecurityContext(containerSecurityContext);
297+
}
298+
podSpec.addToInitContainers(initContainer);
294299
}
295-
podSpec.addToInitContainers(initContainer);
296300
}
297301

298302
Boolean shareProcessNamespace = this.deploymentPropertiesResolver.getShareProcessNamespace(deploymentProperties);

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

Lines changed: 65 additions & 25 deletions
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;
@@ -64,6 +65,7 @@
6465
import org.springframework.cloud.deployer.spi.util.ByteSizeUtils;
6566
import org.springframework.cloud.deployer.spi.util.CommandLineTokenizer;
6667
import org.springframework.core.io.ByteArrayResource;
68+
import org.springframework.lang.Nullable;
6769
import org.springframework.util.Assert;
6870
import org.springframework.util.CollectionUtils;
6971
import org.springframework.util.StringUtils;
@@ -76,6 +78,7 @@
7678
* @author Chris Schaefer
7779
* @author Ilayaperumal Gopinathan
7880
* @author Chris Bono
81+
* @author Corneil du Plessis
7982
*/
8083

8184
class DeploymentPropertiesResolver {
@@ -583,44 +586,79 @@ Affinity getAffinityRules(Map<String, String> kubernetesDeployerProperties) {
583586
return affinity;
584587
}
585588

586-
Container getInitContainer(Map<String, String> kubernetesDeployerProperties) {
589+
Collection<Container> getInitContainers(Map<String, String> kubernetesDeployerProperties) {
590+
Collection<Container> initContainers = new ArrayList<>();
587591
KubernetesDeployerProperties deployerProperties = bindProperties(kubernetesDeployerProperties,
588592
this.propertyPrefix + ".initContainer", "initContainer");
589593

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

596-
// Deployment props passed in for specific '.initContainer.<property>'
597-
String containerName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
598-
this.propertyPrefix + ".initContainer.containerName");
599-
String imageName = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
600-
this.propertyPrefix + ".initContainer.imageName");
601-
646+
private @Nullable Container initContainerFromProperties(Map<String, String> kubeProps, String propertyKey) {
647+
String containerName = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + ".containerName");
648+
String imageName = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + ".imageName");
602649
if (StringUtils.hasText(containerName) && StringUtils.hasText(imageName)) {
603-
String commandsStr = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
604-
this.propertyPrefix + ".initContainer.commands");
605-
List<String> commands = StringUtils.hasText(commandsStr) ? Arrays.stream(commandsStr.split(",")).collect(Collectors.toList()) : Collections.emptyList();
606-
String envString = PropertyParserUtils.getDeploymentPropertyValue(kubernetesDeployerProperties,
607-
this.propertyPrefix + ".initContainer.environmentVariables");
608-
List<VolumeMount> vms = this.getInitContainerVolumeMounts(kubernetesDeployerProperties);
650+
String commandsStr = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + ".commands");
651+
List<String> commands = StringUtils.hasText(commandsStr) ? Arrays.asList(commandsStr.split(",")) : Collections.emptyList();
652+
String envString = PropertyParserUtils.getDeploymentPropertyValue(kubeProps, propertyKey + "environmentVariables");
653+
List<VolumeMount> vms = this.getInitContainerVolumeMounts(kubeProps, propertyKey);
609654
return new ContainerBuilder()
610655
.withName(containerName)
611656
.withImage(imageName)
612657
.withCommand(commands)
613-
.withEnv(toEnvironmentVariables((envString != null)? envString.split(","): new String[0]))
658+
.withEnv(toEnvironmentVariables((envString != null) ? envString.split(",") : new String[0]))
614659
.addAllToVolumeMounts(vms)
615660
.build();
616661
}
617-
618-
// Default is global initContainer
619-
initContainerProps = this.properties.getInitContainer();
620-
if (initContainerProps != null) {
621-
return containerFromProps(initContainerProps);
622-
}
623-
624662
return null;
625663
}
626664

@@ -833,9 +871,11 @@ List<VolumeMount> getVolumeMounts(Map<String, String> deploymentProperties) {
833871
* @param deploymentProperties the deployment properties from {@link AppDeploymentRequest}
834872
* @return the configured volume mounts
835873
*/
836-
private List<VolumeMount> getInitContainerVolumeMounts(Map<String, String> deploymentProperties) {
837-
return this.getVolumeMounts(PropertyParserUtils.getDeploymentPropertyValue(deploymentProperties,
838-
this.propertyPrefix + ".initContainer.volumeMounts"));
874+
private List<VolumeMount> getInitContainerVolumeMounts(Map<String, String> deploymentProperties, String propertyKey) {
875+
return this.getVolumeMounts(PropertyParserUtils.getDeploymentPropertyValue(
876+
deploymentProperties,
877+
propertyKey + ".volumeMounts")
878+
);
839879
}
840880

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

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

Lines changed: 13 additions & 1 deletion
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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
* @author Chris Schaefer
8181
* @author Enrique Medina Montenegro
8282
* @author Chris Bono
83+
* @author Corneil du Plessis
8384
*/
8485
@DisplayName("KubernetesAppDeployer")
8586
public class KubernetesAppDeployerTests {
@@ -833,6 +834,71 @@ public void testConfigMapKeyRefGlobalFromYaml() throws Exception {
833834
assertThat(configMapKeySelector.getName()).as("Unexpected config map name").isEqualTo("myConfigMap");
834835
assertThat(configMapKeySelector.getKey()).as("Unexpected config map data key").isEqualTo("envName");
835836
}
837+
@Test
838+
public void testInitContainerProperties() {
839+
Map<String, String> props = new HashMap<>();
840+
props.put("spring.cloud.deployer.kubernetes.initContainer", "{ \"imageName\": \"busybox:1\", \"containerName\": \"bb_s1\", \"commands\": [\"sh\", \"-c\", \"script1.sh\"] }");
841+
842+
AppDefinition definition = new AppDefinition("app-test", null);
843+
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);
844+
845+
deployer = k8sAppDeployer(new KubernetesDeployerProperties());
846+
PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);
847+
assertThat(podSpec.getInitContainers()).isNotEmpty();
848+
Container container = podSpec.getInitContainers().get(0);
849+
assertThat(container.getImage()).isEqualTo("busybox:1");
850+
assertThat(container.getName()).isEqualTo("bb_s1");
851+
assertThat(container.getCommand()).containsExactly("sh", "-c", "script1.sh");
852+
}
853+
@Test
854+
public void testInitContainerJsonArrayProperties() {
855+
Map<String, String> props = new HashMap<>();
856+
props.put("spring.cloud.deployer.kubernetes.init-containers", "[{ \"imageName\": \"busybox:1\", \"containerName\": \"bb_s1\", \"commands\": [\"sh\", \"-c\", \"script1.sh\"] }]");
857+
858+
AppDefinition definition = new AppDefinition("app-test", null);
859+
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);
860+
861+
deployer = k8sAppDeployer(new KubernetesDeployerProperties());
862+
PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);
863+
assertThat(podSpec.getInitContainers()).isNotEmpty();
864+
Container container = podSpec.getInitContainers().get(0);
865+
assertThat(container.getImage()).isEqualTo("busybox:1");
866+
assertThat(container.getName()).isEqualTo("bb_s1");
867+
assertThat(container.getCommand()).containsExactly("sh", "-c", "script1.sh");
868+
}
869+
@Test
870+
public void testMultipleInitContainerProperties() {
871+
Map<String, String> props = new HashMap<>();
872+
props.put("spring.cloud.deployer.kubernetes.initContainers[0]", "{ \"imageName\": \"busybox:1\", \"containerName\": \"bb_s1\", \"commands\": [\"sh\", \"-c\", \"script1.sh\"] }");
873+
props.put("spring.cloud.deployer.kubernetes.initContainers[1].imageName", "busybox:2");
874+
props.put("spring.cloud.deployer.kubernetes.initContainers[1].containerName", "bb_s2");
875+
props.put("spring.cloud.deployer.kubernetes.initContainers[1].commands", "sh,-c,script2.sh");
876+
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}] }");
877+
878+
AppDefinition definition = new AppDefinition("app-test", null);
879+
AppDeploymentRequest appDeploymentRequest = new AppDeploymentRequest(definition, getResource(), props);
880+
881+
deployer = k8sAppDeployer(new KubernetesDeployerProperties());
882+
PodSpec podSpec = deployer.createPodSpec(appDeploymentRequest);
883+
assertThat(podSpec.getInitContainers()).isNotEmpty();
884+
assertThat(podSpec.getInitContainers().size()).isEqualTo(3);
885+
Container container0 = podSpec.getInitContainers().get(0);
886+
assertThat(container0.getImage()).isEqualTo("busybox:1");
887+
assertThat(container0.getName()).isEqualTo("bb_s1");
888+
assertThat(container0.getCommand()).containsExactly("sh", "-c", "script1.sh");
889+
Container container1 = podSpec.getInitContainers().get(1);
890+
assertThat(container1.getImage()).isEqualTo("busybox:2");
891+
assertThat(container1.getName()).isEqualTo("bb_s2");
892+
assertThat(container1.getCommand()).containsExactly("sh", "-c", "script2.sh");
893+
Container container2 = podSpec.getInitContainers().get(2);
894+
assertThat(container2.getImage()).isEqualTo("busybox:3");
895+
assertThat(container2.getName()).isEqualTo("bb_s3");
896+
assertThat(container2.getCommand()).containsExactly("sh", "-c", "script3.sh");
897+
assertThat(container2.getVolumeMounts()).isNotEmpty();
898+
assertThat(container2.getVolumeMounts().get(0).getName()).isEqualTo("s3vol");
899+
assertThat(container2.getVolumeMounts().get(0).getMountPath()).isEqualTo("/data");
900+
assertThat(container2.getVolumeMounts().get(0).getReadOnly()).isTrue();
901+
}
836902

837903
@Test
838904
public void testNodeAffinityProperty() {

0 commit comments

Comments
 (0)