From 63df949b991b41543f527d11dd7c922ddf3c63fc Mon Sep 17 00:00:00 2001 From: Vincent Sourin Date: Thu, 17 Sep 2020 23:37:21 +0200 Subject: [PATCH] Kubernetes HostAliases property Allow the generation of hostAliases property from Deployment Signed-off-by: Vincent Sourtin --- .../annotation/KubernetesApplication.java | 5 ++ .../dekorate/AbstractKubernetesHandler.java | 8 +- .../dekorate/kubernetes/annotation/Base.java | 7 ++ .../kubernetes/annotation/HostAlias.java | 31 ++++++++ .../decorator/AddHostAliasesDecorator.java | 68 ++++++++++++++++ .../Dockerfile | 4 + .../pom.xml | 78 +++++++++++++++++++ .../readme.md | 60 ++++++++++++++ .../examples/kubernetes/Controller.java | 28 +++++++ .../io/dekorate/examples/kubernetes/Main.java | 31 ++++++++ .../examples/KubernetesExampleTest.java | 55 +++++++++++++ 11 files changed, 374 insertions(+), 1 deletion(-) create mode 100644 core/src/main/java/io/dekorate/kubernetes/annotation/HostAlias.java create mode 100644 core/src/main/java/io/dekorate/kubernetes/decorator/AddHostAliasesDecorator.java create mode 100644 examples/kubernetes-example-with-hostaliases/Dockerfile create mode 100644 examples/kubernetes-example-with-hostaliases/pom.xml create mode 100644 examples/kubernetes-example-with-hostaliases/readme.md create mode 100644 examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Controller.java create mode 100644 examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Main.java create mode 100644 examples/kubernetes-example-with-hostaliases/src/test/java/io/dekorate/examples/KubernetesExampleTest.java diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/KubernetesApplication.java b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/KubernetesApplication.java index 6873c5df4..645b61f0c 100644 --- a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/KubernetesApplication.java +++ b/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/KubernetesApplication.java @@ -209,6 +209,11 @@ * The image pull secret */ String[] imagePullSecrets() default {}; + + /** + * The hostAliases + */ + HostAlias[] hostAliases() default {}; /** * The liveness probe. diff --git a/core/src/main/java/io/dekorate/AbstractKubernetesHandler.java b/core/src/main/java/io/dekorate/AbstractKubernetesHandler.java index 2162b8e14..2a512ba17 100644 --- a/core/src/main/java/io/dekorate/AbstractKubernetesHandler.java +++ b/core/src/main/java/io/dekorate/AbstractKubernetesHandler.java @@ -28,6 +28,7 @@ import io.dekorate.kubernetes.config.Container; import io.dekorate.kubernetes.config.Env; import io.dekorate.kubernetes.config.BaseConfig; +import io.dekorate.kubernetes.config.HostAlias; import io.dekorate.kubernetes.config.Label; import io.dekorate.kubernetes.config.Mount; import io.dekorate.kubernetes.config.PersistentVolumeClaimVolume; @@ -40,6 +41,7 @@ import io.dekorate.kubernetes.decorator.AddCommitIdAnnotationDecorator; import io.dekorate.kubernetes.decorator.AddConfigMapVolumeDecorator; import io.dekorate.kubernetes.decorator.AddEnvVarDecorator; +import io.dekorate.kubernetes.decorator.AddHostAliasesDecorator; import io.dekorate.kubernetes.decorator.AddImagePullSecretDecorator; import io.dekorate.kubernetes.decorator.AddLabelDecorator; import io.dekorate.kubernetes.decorator.AddLivenessProbeDecorator; @@ -130,6 +132,10 @@ protected void addDecorators(String group, C config) { resources.decorate(group, new AddImagePullSecretDecorator(config.getName(), imagePullSecret)); } + for (HostAlias hostAlias : config.getHostAliases()) { + resources.decorate(new AddHostAliasesDecorator(config.getName(), hostAlias)); + } + for (Container container : config.getSidecars()) { resources.decorate(group, new AddSidecarDecorator(config.getName(), container)); } @@ -201,7 +207,7 @@ protected void addDecorators(String group, C config) { if (Strings.isNotNullOrEmpty(config.getRequestResources() .getMemory())) { resources.decorate(group, new ApplyRequestsMemoryDecorator(config.getName(), config.getName(), config.getRequestResources() .getMemory())); } - + } private static void validateVolume(SecretVolume volume) { diff --git a/core/src/main/java/io/dekorate/kubernetes/annotation/Base.java b/core/src/main/java/io/dekorate/kubernetes/annotation/Base.java index 6b48bb349..2ec0a6d31 100644 --- a/core/src/main/java/io/dekorate/kubernetes/annotation/Base.java +++ b/core/src/main/java/io/dekorate/kubernetes/annotation/Base.java @@ -165,6 +165,13 @@ * The image pull secret */ String[] imagePullSecrets() default {}; + + /** + * Host aliases + * + * @return The host aliases + */ + HostAlias[] hostAliases() default {}; /** * The liveness probe. diff --git a/core/src/main/java/io/dekorate/kubernetes/annotation/HostAlias.java b/core/src/main/java/io/dekorate/kubernetes/annotation/HostAlias.java new file mode 100644 index 000000000..2d5165cee --- /dev/null +++ b/core/src/main/java/io/dekorate/kubernetes/annotation/HostAlias.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dekorate.kubernetes.annotation; + +public @interface HostAlias { + + /** + * Ip address to resolve to. + * @return the ip address. + */ + String ip() default ""; + + /** + * List of hostnames (comma separated) + * @return the hostnames + */ + String hostnames() default ""; +} diff --git a/core/src/main/java/io/dekorate/kubernetes/decorator/AddHostAliasesDecorator.java b/core/src/main/java/io/dekorate/kubernetes/decorator/AddHostAliasesDecorator.java new file mode 100644 index 000000000..75fa8e6cb --- /dev/null +++ b/core/src/main/java/io/dekorate/kubernetes/decorator/AddHostAliasesDecorator.java @@ -0,0 +1,68 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * +**/ + +package io.dekorate.kubernetes.decorator; + +import io.dekorate.deps.kubernetes.api.builder.Predicate; +import io.dekorate.deps.kubernetes.api.model.HostAliasBuilder; +import io.dekorate.deps.kubernetes.api.model.ObjectMeta; +import io.dekorate.deps.kubernetes.api.model.PodSpecFluent; +import io.dekorate.kubernetes.config.HostAlias; +import io.dekorate.utils.Strings; + +import java.util.Arrays; +import java.util.Objects; + +public class AddHostAliasesDecorator extends NamedResourceDecorator> { + + private final HostAlias hostAlias; + + public AddHostAliasesDecorator(String deploymentName, HostAlias hostAlias) { + super(deploymentName); + this.hostAlias = hostAlias; + } + + public void andThenVisit(PodSpecFluent podSpec, ObjectMeta resourceMeta) { + if (Strings.isNotNullOrEmpty(hostAlias.getIp()) && Strings.isNotNullOrEmpty(hostAlias.getHostnames())) { + Predicate matchingHostAlias = host -> { + if (host.getIp() != null) + return host.getIp().equals(hostAlias.getIp()); + return false; + }; + + podSpec.removeMatchingFromHostAliases(matchingHostAlias); + + podSpec.addNewHostAlias() + .withIp(hostAlias.getIp()) + .withHostnames(Arrays.asList(hostAlias.getHostnames().split(","))) + .endHostAlias(); + } + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AddHostAliasesDecorator that = (AddHostAliasesDecorator) o; + return Objects.equals(hostAlias, that.hostAlias); + } + + @Override + public int hashCode() { + return Objects.hash(hostAlias); + } +} diff --git a/examples/kubernetes-example-with-hostaliases/Dockerfile b/examples/kubernetes-example-with-hostaliases/Dockerfile new file mode 100644 index 000000000..4870a0330 --- /dev/null +++ b/examples/kubernetes-example-with-hostaliases/Dockerfile @@ -0,0 +1,4 @@ +FROM openjdk:8u171-alpine3.7 +RUN apk --no-cache add curl +COPY target/*.jar kubernetes-example-with-hostaliases.jar +CMD java ${JAVA_OPTS} -jar kubernetes-example-with-hostaliases.jar diff --git a/examples/kubernetes-example-with-hostaliases/pom.xml b/examples/kubernetes-example-with-hostaliases/pom.xml new file mode 100644 index 000000000..6cd33cbbe --- /dev/null +++ b/examples/kubernetes-example-with-hostaliases/pom.xml @@ -0,0 +1,78 @@ + + + + + examples + io.dekorate + 0.12-SNAPSHOT + + 4.0.0 + + io.dekorate + kubernetes-example-with-hostaliases + Dekorate :: Examples :: Kubernetes with hostAliases + + + UTF-8 + UTF-8 + 1.8 + + 2.1.13.RELEASE + + + + + io.dekorate + kubernetes-annotations + ${project.version} + + + + org.springframework.boot + spring-boot-starter-web + ${version.spring-boot} + + + + + io.dekorate + kubernetes-junit-starter + ${project.version} + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + ${version.spring-boot} + + + + repackage + + + + + + + + + diff --git a/examples/kubernetes-example-with-hostaliases/readme.md b/examples/kubernetes-example-with-hostaliases/readme.md new file mode 100644 index 000000000..11960116a --- /dev/null +++ b/examples/kubernetes-example-with-hostaliases/readme.md @@ -0,0 +1,60 @@ +# Add hostAliases property Example + +An example that demonstrates the use of `@KubernetesApplication` in order to add hostAliases property to a deployment. +To access the `@KubernetesApplication` annotation you just need to have the following dependency in your +class path: + + + io.dekorate + kubernetes-annotations + ${project.version} + + +So as to add the hostAliases section of the Deployment specification you need pass the `hostAliases` parameter containing the ip address and the list of hostnames (comma separated values) to the `@KubernetesApplication` in the Spring Boot annotated class. The code would look as follow: + +``` +@KubernetesApplication(hostAliases = {@HostAlias(ip = "127.0.0.1", hostnames = "foo.org,bar.com")}) +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} +``` +You can pass multiple `@HostAlias` annotation depending of your needs. + +Check, if necessary, the [Main.java](src/main/java/io/dekorate/examples/kubernetes/Main.java). + +Compile the project using: + + mvn clean install + +You can find the generated deployment under: `target/classes/META-INF/dekorate/kubernetes.yml` that should look like: +```--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app.kubernetes.io/name: kubernetes-example-with-hostaliases + app.kubernetes.io/version: 0.12-SNAPSHOT + name: kubernetes-example-with-hostaliases +spec: + replicas: 1 + template: + metadata: + labels: + app.kubernetes.io/name: kubernetes-example-with-hostaliases + app.kubernetes.io/version: 0.12-SNAPSHOT + spec: + hostAliases: + - hostnames: + - foo.org + - bar.com + ip: 127.0.0.1 + - hostnames: + - test.com + ip: 10.0.0.1 +``` + + diff --git a/examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Controller.java b/examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Controller.java new file mode 100644 index 000000000..17fd4b646 --- /dev/null +++ b/examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Controller.java @@ -0,0 +1,28 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dekorate.examples.kubernetes; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class Controller { + + @RequestMapping("/") + public String hello() { + return "Hello world"; + } +} diff --git a/examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Main.java b/examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Main.java new file mode 100644 index 000000000..669d8f591 --- /dev/null +++ b/examples/kubernetes-example-with-hostaliases/src/main/java/io/dekorate/examples/kubernetes/Main.java @@ -0,0 +1,31 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.dekorate.examples.kubernetes; + +import io.dekorate.kubernetes.annotation.HostAlias; +import io.dekorate.kubernetes.annotation.KubernetesApplication; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@KubernetesApplication(hostAliases = {@HostAlias(ip = "127.0.0.1", hostnames = "foo.org,bar.com"), + @HostAlias(ip = "10.0.0.1", hostnames = "test.com")}) +@SpringBootApplication +public class Main { + + public static void main(String[] args) { + SpringApplication.run(Main.class, args); + } +} diff --git a/examples/kubernetes-example-with-hostaliases/src/test/java/io/dekorate/examples/KubernetesExampleTest.java b/examples/kubernetes-example-with-hostaliases/src/test/java/io/dekorate/examples/KubernetesExampleTest.java new file mode 100644 index 000000000..a83f98ede --- /dev/null +++ b/examples/kubernetes-example-with-hostaliases/src/test/java/io/dekorate/examples/KubernetesExampleTest.java @@ -0,0 +1,55 @@ +/** + * Copyright 2018 The original authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.dekorate.examples; + +import io.dekorate.utils.Serialization; +import org.junit.jupiter.api.Test; +import io.fabric8.kubernetes.api.model.KubernetesList; +import io.fabric8.kubernetes.api.model.apps.Deployment; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class KubernetesExampleTest { + + @Test + public void shouldContainHostAliases() { + List expectedHostname1 = Collections.singletonList("test.com"); + List expectedHostname2 = Arrays.asList("foo.org", "bar.com"); + KubernetesList list = Serialization.unmarshalAsList(KubernetesExampleTest.class.getClassLoader().getResourceAsStream("META-INF/dekorate/kubernetes.yml")); + assertNotNull(list); + assertEquals(1, list.getItems().size()); + Deployment deployment = (Deployment) list.getItems().get(0); + + assertEquals(2, deployment.getSpec().getTemplate().getSpec().getHostAliases().size()); + assertEquals("10.0.0.1", deployment.getSpec().getTemplate().getSpec().getHostAliases().get(0).getIp()); + assertEquals("127.0.0.1", deployment.getSpec().getTemplate().getSpec().getHostAliases().get(1).getIp()); + + assertTrue(expectedHostname1.size() == deployment.getSpec().getTemplate().getSpec().getHostAliases().get(0).getHostnames().size() && + expectedHostname1.containsAll(deployment.getSpec().getTemplate().getSpec().getHostAliases().get(0).getHostnames()) && + deployment.getSpec().getTemplate().getSpec().getHostAliases().get(0).getHostnames().containsAll(expectedHostname1)); + + assertTrue(expectedHostname2.size() == deployment.getSpec().getTemplate().getSpec().getHostAliases().get(1).getHostnames().size() && + expectedHostname2.containsAll(deployment.getSpec().getTemplate().getSpec().getHostAliases().get(1).getHostnames()) && + deployment.getSpec().getTemplate().getSpec().getHostAliases().get(1).getHostnames().containsAll(expectedHostname2)); + } +}