From 7350f811db193eaa3386e40b7a45e420cf2cdd80 Mon Sep 17 00:00:00 2001 From: Jose Date: Fri, 23 Jun 2023 06:51:57 +0200 Subject: [PATCH] feat: support deployment strategy rolling update in DeploymentConfig Related to https://github.com/quarkusio/quarkus/pull/34179 --- .../annotation/KubernetesApplication.java | 2 +- .../annotation/OpenshiftApplication.java | 14 ++++ ...pplyDeploymentConfigStrategyDecorator.java | 67 +++++++++++++++++++ .../DeploymentConfigResourceFactory.java | 2 +- .../manifest/OpenshiftManifestGenerator.java | 16 ++++- .../dekorate/kubernetes/annotation/Base.java | 13 ++++ .../kubernetes/annotation/RollingUpdate.java | 0 .../kubernetes/config/DeploymentStrategy.java | 2 +- .../ApplyDeploymentStrategyDecorator.java | 11 +-- .../pom.xml | 6 ++ .../src/main/resources/application.properties | 3 + .../dekorate/annotationless/Feat590Test.java | 15 ++++- 12 files changed, 140 insertions(+), 11 deletions(-) create mode 100644 annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/ApplyDeploymentConfigStrategyDecorator.java rename {annotations/kubernetes-annotations => core}/src/main/java/io/dekorate/kubernetes/annotation/RollingUpdate.java (100%) rename {annotations/kubernetes-annotations => core}/src/main/java/io/dekorate/kubernetes/config/DeploymentStrategy.java (56%) rename {annotations/kubernetes-annotations => core}/src/main/java/io/dekorate/kubernetes/decorator/ApplyDeploymentStrategyDecorator.java (85%) 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 45c89faa8..5ba12f680 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 @@ -136,7 +136,7 @@ /** * Specifies rolling update configuration. - * The configuration is applied when DeploymentStrategy == Rolling update, or + * The configuration is applied when DeploymentStrategy == RollingUpdate, or * when explicit configuration has been provided. In the later case RollingUpdate is assumed. */ RollingUpdate rollingUpdate() default @RollingUpdate; diff --git a/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/annotation/OpenshiftApplication.java b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/annotation/OpenshiftApplication.java index a77c630ba..66e951c69 100644 --- a/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/annotation/OpenshiftApplication.java +++ b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/annotation/OpenshiftApplication.java @@ -38,9 +38,11 @@ import io.dekorate.kubernetes.annotation.Port; import io.dekorate.kubernetes.annotation.Probe; import io.dekorate.kubernetes.annotation.ResourceRequirements; +import io.dekorate.kubernetes.annotation.RollingUpdate; import io.dekorate.kubernetes.annotation.SecretVolume; import io.dekorate.kubernetes.annotation.ServiceType; import io.dekorate.kubernetes.config.BaseConfig; +import io.dekorate.kubernetes.config.DeploymentStrategy; import io.sundr.builder.annotations.Adapter; import io.sundr.builder.annotations.Buildable; import io.sundr.builder.annotations.Pojo; @@ -151,6 +153,18 @@ */ int replicas() default 1; + /** + * Specifies the deployment strategy. + */ + DeploymentStrategy deploymentStrategy() default DeploymentStrategy.None; + + /** + * Specifies rolling update configuration. + * The configuration is applied when DeploymentStrategy == Rolling update, or + * when explicit configuration has been provided. In the later case RollingUpdate is assumed. + */ + RollingUpdate rollingUpdate() default @RollingUpdate; + /** * The service account. * diff --git a/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/ApplyDeploymentConfigStrategyDecorator.java b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/ApplyDeploymentConfigStrategyDecorator.java new file mode 100644 index 000000000..a8c44b017 --- /dev/null +++ b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/ApplyDeploymentConfigStrategyDecorator.java @@ -0,0 +1,67 @@ +/** + * 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.openshift.decorator; + +import io.dekorate.kubernetes.config.DeploymentStrategy; +import io.dekorate.kubernetes.config.RollingUpdate; +import io.dekorate.kubernetes.decorator.NamedResourceDecorator; +import io.dekorate.utils.Strings; +import io.fabric8.kubernetes.api.model.ObjectMeta; +import io.fabric8.openshift.api.model.DeploymentConfigSpecFluent; + +public class ApplyDeploymentConfigStrategyDecorator extends NamedResourceDecorator> { + + private final DeploymentStrategy strategy; + private final RollingUpdate rollingUpdate; + + public ApplyDeploymentConfigStrategyDecorator(String name, DeploymentStrategy strategy) { + this(name, strategy, null); + } + + public ApplyDeploymentConfigStrategyDecorator(String name, DeploymentStrategy strategy, RollingUpdate rollingUpdate) { + super(name); + this.strategy = strategy; + this.rollingUpdate = rollingUpdate; + } + + @Override + public void andThenVisit(final DeploymentConfigSpecFluent spec, final ObjectMeta resourceMeta) { + boolean hasCustomRollingUpdate = hasCustomRollingUpdateConfig(rollingUpdate); + if (strategy == DeploymentStrategy.Recreate) { + if (hasCustomRollingUpdate) { + throw new IllegalStateException( + "Detected both Recreate strategy and custom Rolling Update config. Please use one or the other!"); + } + spec.withNewStrategy() + .withType("Recreate") + .endStrategy(); + } else if (strategy == DeploymentStrategy.RollingUpdate || hasCustomRollingUpdate) { + spec.withNewStrategy() + .withType("RollingUpdate") + .withNewRollingParams() + .withNewMaxSurge().withValue(rollingUpdate.getMaxSurge()).endMaxSurge() + .withNewMaxUnavailable().withValue(rollingUpdate.getMaxUnavailable()).endMaxUnavailable() + .endRollingParams() + .endStrategy(); + } + } + + private boolean hasCustomRollingUpdateConfig(RollingUpdate rollingUpdate) { + return rollingUpdate != null + && (!Strings.equals(rollingUpdate.getMaxUnavailable(), "25%") + || !Strings.equals(rollingUpdate.getMaxSurge(), "25%")); + } +} diff --git a/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/DeploymentConfigResourceFactory.java b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/DeploymentConfigResourceFactory.java index 63683054c..b38630dea 100644 --- a/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/DeploymentConfigResourceFactory.java +++ b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/decorator/DeploymentConfigResourceFactory.java @@ -32,7 +32,7 @@ public class DeploymentConfigResourceFactory implements ResourceFactory { - private static final String KIND = "DeploymentConfig"; + public static final String KIND = "DeploymentConfig"; private static final String IF_NOT_PRESENT = "IfNotPresent"; private static final String KUBERNETES_NAMESPACE = "KUBERNETES_NAMESPACE"; diff --git a/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/manifest/OpenshiftManifestGenerator.java b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/manifest/OpenshiftManifestGenerator.java index e725473b6..501b8fdec 100644 --- a/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/manifest/OpenshiftManifestGenerator.java +++ b/annotations/openshift-annotations/src/main/java/io/dekorate/openshift/manifest/OpenshiftManifestGenerator.java @@ -34,6 +34,7 @@ import io.dekorate.kubernetes.decorator.AddLabelDecorator; import io.dekorate.kubernetes.decorator.AddServiceResourceDecorator; import io.dekorate.kubernetes.decorator.AddVcsUrlAnnotationDecorator; +import io.dekorate.kubernetes.decorator.ApplyDeploymentStrategyDecorator; import io.dekorate.kubernetes.decorator.ApplyHeadlessDecorator; import io.dekorate.kubernetes.decorator.ApplyReplicasToDeploymentDecorator; import io.dekorate.kubernetes.decorator.ApplyReplicasToStatefulSetDecorator; @@ -50,8 +51,10 @@ import io.dekorate.openshift.decorator.AddRouteDecorator; import io.dekorate.openshift.decorator.AddServiceToRouteDecorator; import io.dekorate.openshift.decorator.AddTlsConfigToRouteDecorator; +import io.dekorate.openshift.decorator.ApplyDeploymentConfigStrategyDecorator; import io.dekorate.openshift.decorator.ApplyDeploymentTriggerDecorator; import io.dekorate.openshift.decorator.ApplyReplicasToDeploymentConfigDecorator; +import io.dekorate.openshift.decorator.DeploymentConfigResourceFactory; import io.dekorate.option.config.VcsConfig; import io.dekorate.project.ApplyProjectInfo; import io.dekorate.project.Project; @@ -140,14 +143,23 @@ protected void addDecorators(String group, OpenshiftConfig config) { Project project = getProject(); Optional vcsConfig = configurationRegistry.get(VcsConfig.class); String remote = vcsConfig.map(VcsConfig::getRemote).orElse(Git.ORIGIN); - boolean httpsPrefered = vcsConfig.map(VcsConfig::isHttpsPreferred).orElse(false); + boolean httpsPreferred = vcsConfig.map(VcsConfig::isHttpsPreferred).orElse(false); String vcsUrl = project.getScmInfo() != null && Strings.isNotNullOrEmpty(project.getScmInfo().getRemote().get(Git.ORIGIN)) - ? Git.getRemoteUrl(project.getRoot(), remote, httpsPrefered).orElse(Labels.UNKNOWN) + ? Git.getRemoteUrl(project.getRoot(), remote, httpsPreferred).orElse(Labels.UNKNOWN) : Labels.UNKNOWN; resourceRegistry.decorate(group, new AddVcsUrlAnnotationDecorator(config.getName(), OpenshiftAnnotations.VCS_URL, vcsUrl)); resourceRegistry.decorate(group, new AddCommitIdAnnotationDecorator()); + if (DeploymentResourceFactory.KIND.equalsIgnoreCase(config.getDeploymentKind())) { + resourceRegistry.decorate(group, + new ApplyDeploymentStrategyDecorator(config.getName(), config.getDeploymentStrategy(), + config.getRollingUpdate())); + } else if (DeploymentConfigResourceFactory.KIND.equalsIgnoreCase(config.getDeploymentKind())) { + resourceRegistry.decorate(group, + new ApplyDeploymentConfigStrategyDecorator(config.getName(), config.getDeploymentStrategy(), + config.getRollingUpdate())); + } } public boolean accepts(Class type) { 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 01a1bf4f3..13d3a08d6 100644 --- a/core/src/main/java/io/dekorate/kubernetes/annotation/Base.java +++ b/core/src/main/java/io/dekorate/kubernetes/annotation/Base.java @@ -21,6 +21,7 @@ import java.lang.annotation.Target; import io.dekorate.kubernetes.config.ApplicationConfiguration; +import io.dekorate.kubernetes.config.DeploymentStrategy; import io.sundr.builder.annotations.Buildable; import io.sundr.builder.annotations.Pojo; @@ -169,6 +170,18 @@ */ String[] imagePullSecrets() default {}; + /** + * Specifies the deployment strategy. + */ + DeploymentStrategy deploymentStrategy() default DeploymentStrategy.None; + + /** + * Specifies rolling update configuration. + * The configuration is applied when DeploymentStrategy == Rolling update, or + * when explicit configuration has been provided. In the later case RollingUpdate is assumed. + */ + RollingUpdate rollingUpdate() default @RollingUpdate; + /** * Host aliases * diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/RollingUpdate.java b/core/src/main/java/io/dekorate/kubernetes/annotation/RollingUpdate.java similarity index 100% rename from annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/annotation/RollingUpdate.java rename to core/src/main/java/io/dekorate/kubernetes/annotation/RollingUpdate.java diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/config/DeploymentStrategy.java b/core/src/main/java/io/dekorate/kubernetes/config/DeploymentStrategy.java similarity index 56% rename from annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/config/DeploymentStrategy.java rename to core/src/main/java/io/dekorate/kubernetes/config/DeploymentStrategy.java index 23b06ad55..44c99603d 100644 --- a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/config/DeploymentStrategy.java +++ b/core/src/main/java/io/dekorate/kubernetes/config/DeploymentStrategy.java @@ -2,6 +2,6 @@ public enum DeploymentStrategy { - None, //This is added to imply that no explict setting has been provided + None, // This is added to imply that no explicit setting has been provided Recreate, RollingUpdate } diff --git a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/decorator/ApplyDeploymentStrategyDecorator.java b/core/src/main/java/io/dekorate/kubernetes/decorator/ApplyDeploymentStrategyDecorator.java similarity index 85% rename from annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/decorator/ApplyDeploymentStrategyDecorator.java rename to core/src/main/java/io/dekorate/kubernetes/decorator/ApplyDeploymentStrategyDecorator.java index d644bbee0..4121e2383 100644 --- a/annotations/kubernetes-annotations/src/main/java/io/dekorate/kubernetes/decorator/ApplyDeploymentStrategyDecorator.java +++ b/core/src/main/java/io/dekorate/kubernetes/decorator/ApplyDeploymentStrategyDecorator.java @@ -17,6 +17,7 @@ import io.dekorate.kubernetes.config.DeploymentStrategy; import io.dekorate.kubernetes.config.RollingUpdate; +import io.dekorate.utils.Strings; import io.fabric8.kubernetes.api.model.ObjectMeta; import io.fabric8.kubernetes.api.model.apps.DeploymentSpecFluent; @@ -37,7 +38,7 @@ public ApplyDeploymentStrategyDecorator(String name, DeploymentStrategy strategy @Override public void andThenVisit(final DeploymentSpecFluent spec, final ObjectMeta resourceMeta) { - boolean hasCustomRollingUpdate = hasCusomRollingUpdateConfig(rollingUpdate); + boolean hasCustomRollingUpdate = hasCustomRollingUpdateConfig(rollingUpdate); if (strategy == DeploymentStrategy.Recreate) { if (hasCustomRollingUpdate) { throw new IllegalStateException( @@ -57,9 +58,9 @@ public void andThenVisit(final DeploymentSpecFluent spec, final ObjectMeta re } } - private boolean hasCusomRollingUpdateConfig(RollingUpdate rollingUpdate) { - return rollingUpdate != null && - ((rollingUpdate.getMaxUnavailable() != null && !rollingUpdate.getMaxUnavailable().equals("25%")) || - (rollingUpdate.getMaxSurge() != null && !rollingUpdate.getMaxSurge().equals("25%"))); + private boolean hasCustomRollingUpdateConfig(RollingUpdate rollingUpdate) { + return rollingUpdate != null + && (!Strings.equals(rollingUpdate.getMaxUnavailable(), "25%") + || !Strings.equals(rollingUpdate.getMaxSurge(), "25%")); } } diff --git a/tests/feat-590-custom-rolling-update-strategy/pom.xml b/tests/feat-590-custom-rolling-update-strategy/pom.xml index 22cf16e18..bee215079 100644 --- a/tests/feat-590-custom-rolling-update-strategy/pom.xml +++ b/tests/feat-590-custom-rolling-update-strategy/pom.xml @@ -22,6 +22,12 @@ ${project.version} + + io.dekorate + openshift-spring-starter + ${project.version} + + io.sundr builder-annotations diff --git a/tests/feat-590-custom-rolling-update-strategy/src/main/resources/application.properties b/tests/feat-590-custom-rolling-update-strategy/src/main/resources/application.properties index 09f4fe568..6b095e1a9 100644 --- a/tests/feat-590-custom-rolling-update-strategy/src/main/resources/application.properties +++ b/tests/feat-590-custom-rolling-update-strategy/src/main/resources/application.properties @@ -1,2 +1,5 @@ dekorate.kubernetes.rolling-update.max-surge=40% dekorate.kubernetes.rolling-update.max-unavailable=30% + +dekorate.openshift.rolling-update.max-surge=41% +dekorate.openshift.rolling-update.max-unavailable=32% diff --git a/tests/feat-590-custom-rolling-update-strategy/src/test/java/io/dekorate/annotationless/Feat590Test.java b/tests/feat-590-custom-rolling-update-strategy/src/test/java/io/dekorate/annotationless/Feat590Test.java index 7955860fb..552f009c8 100644 --- a/tests/feat-590-custom-rolling-update-strategy/src/test/java/io/dekorate/annotationless/Feat590Test.java +++ b/tests/feat-590-custom-rolling-update-strategy/src/test/java/io/dekorate/annotationless/Feat590Test.java @@ -27,11 +27,12 @@ import io.fabric8.kubernetes.api.model.HasMetadata; import io.fabric8.kubernetes.api.model.KubernetesList; import io.fabric8.kubernetes.api.model.apps.Deployment; +import io.fabric8.openshift.api.model.DeploymentConfig; public class Feat590Test { @Test - public void shouldHaveRollingUpdateStrategy() { + public void shouldHaveRollingUpdateStrategyInKubernetes() { KubernetesList list = Serialization .unmarshalAsList(getClass().getClassLoader().getResourceAsStream("META-INF/dekorate/kubernetes.yml")); assertNotNull(list); @@ -42,6 +43,18 @@ public void shouldHaveRollingUpdateStrategy() { assertEquals("30%", d.getSpec().getStrategy().getRollingUpdate().getMaxUnavailable().getStrVal()); } + @Test + public void shouldHaveRollingUpdateStrategyInOpenShift() { + KubernetesList list = Serialization + .unmarshalAsList(getClass().getClassLoader().getResourceAsStream("META-INF/dekorate/openshift.yml")); + assertNotNull(list); + DeploymentConfig d = findFirst(list, DeploymentConfig.class).orElseThrow(() -> new IllegalStateException()); + assertNotNull(d); + assertEquals("RollingUpdate", d.getSpec().getStrategy().getType()); + assertEquals("41%", d.getSpec().getStrategy().getRollingParams().getMaxSurge().getStrVal()); + assertEquals("32%", d.getSpec().getStrategy().getRollingParams().getMaxUnavailable().getStrVal()); + } + Optional findFirst(KubernetesList list, Class t) { return (Optional) list.getItems().stream() .filter(i -> t.isInstance(i))