From b34b43caa4b61d4464b459a28de9322166fcfb05 Mon Sep 17 00:00:00 2001 From: maggieneterval Date: Wed, 17 Apr 2019 13:30:11 -0400 Subject: [PATCH] feat(kubernetes): support redblack and highlander strategies --- .../manifest/DeployManifestStage.java | 22 ++++- .../tasks/manifest/DeployManifestContext.java | 13 ++- .../manifest/ManifestHighlanderStrategy.java | 37 ++++++++ .../tasks/manifest/ManifestNoneStrategy.java | 26 +++++ .../manifest/ManifestRedBlackStrategy.java | 36 +++++++ .../tasks/manifest/ManifestStrategy.java | 24 +++++ .../manifest/ManifestStrategyHandler.java | 95 +++++++++++++++++++ .../manifest/ManifestStrategyStagesAdder.java | 45 +++++++++ .../tasks/manifest/ManifestStrategyType.java | 42 ++++++++ 9 files changed, 334 insertions(+), 6 deletions(-) create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestHighlanderStrategy.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestNoneStrategy.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestRedBlackStrategy.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategy.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyHandler.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyStagesAdder.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyType.java diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/manifest/DeployManifestStage.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/manifest/DeployManifestStage.java index 7230898b033..b2dfcfa2e47 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/manifest/DeployManifestStage.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/pipeline/manifest/DeployManifestStage.java @@ -19,22 +19,34 @@ import com.netflix.spinnaker.orca.clouddriver.tasks.MonitorKatoTask; import com.netflix.spinnaker.orca.clouddriver.tasks.artifacts.CleanupArtifactsTask; +import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.DeployManifestContext; +import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.DeployManifestContext.TrafficManagement; import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.DeployManifestTask; import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestForceCacheRefreshTask; +import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestStrategyStagesAdder; +import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.ManifestStrategyType; import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.PromoteManifestKatoOutputsTask; import com.netflix.spinnaker.orca.clouddriver.tasks.manifest.WaitForManifestStableTask; import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder; import com.netflix.spinnaker.orca.pipeline.TaskNode; +import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; import com.netflix.spinnaker.orca.pipeline.model.Stage; import com.netflix.spinnaker.orca.pipeline.tasks.artifacts.BindProducedArtifactsTask; +import javax.annotation.Nonnull; +import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; +import java.util.Optional; + @Component +@RequiredArgsConstructor public class DeployManifestStage implements StageDefinitionBuilder { public static final String PIPELINE_CONFIG_TYPE = "deployManifest"; + private final ManifestStrategyStagesAdder manifestStrategyStagesAdder; + @Override - public void taskGraph(Stage stage, TaskNode.Builder builder) { + public void taskGraph(@Nonnull Stage stage, @Nonnull TaskNode.Builder builder) { builder.withTask(DeployManifestTask.TASK_NAME, DeployManifestTask.class) .withTask("monitorDeploy", MonitorKatoTask.class) .withTask(PromoteManifestKatoOutputsTask.TASK_NAME, PromoteManifestKatoOutputsTask.class) @@ -43,4 +55,12 @@ public void taskGraph(Stage stage, TaskNode.Builder builder) { .withTask(CleanupArtifactsTask.TASK_NAME, CleanupArtifactsTask.class) .withTask(BindProducedArtifactsTask.TASK_NAME, BindProducedArtifactsTask.class); } + + public void afterStages(@Nonnull Stage stage, @Nonnull StageGraphBuilder graph) { + DeployManifestContext context = stage.mapTo(DeployManifestContext.class); + TrafficManagement trafficManagement = context.getTrafficManagement(); + if (trafficManagement.isEnabled()) { + manifestStrategyStagesAdder.addAfterStages(trafficManagement.getOptions().getStrategy(), graph, context); + } + } } diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestContext.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestContext.java index d72560fedae..edcccee04fd 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestContext.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestContext.java @@ -54,7 +54,7 @@ public DeployManifestContext( this.manifestArtifact = manifestArtifact; this.manifestArtifactAccount = manifestArtifactAccount; this.skipExpressionEvaluation = skipExpressionEvaluation; - this.trafficManagement = trafficManagement; + this.trafficManagement = Optional.ofNullable(trafficManagement).orElse(new TrafficManagement(false, null)); this.requiredArtifactIds = requiredArtifactIds; this.requiredArtifacts = requiredArtifacts; } @@ -76,7 +76,7 @@ public BindArtifact(@JsonProperty("expectedArtifactId") @Nullable String expecte } @Getter - static class TrafficManagement { + public static class TrafficManagement { private final boolean enabled; private final Options options; @@ -85,20 +85,23 @@ public TrafficManagement ( @JsonProperty("options") Options options ) { this.enabled = Optional.ofNullable(enabled).orElse(false); - this.options = options; + this.options = Optional.ofNullable(options).orElse(new Options(false, Collections.emptyList(), null)); } @Getter - static class Options { + public static class Options { private final boolean enableTraffic; private final List services; + private final ManifestStrategyType strategy; public Options( @JsonProperty("enableTraffic") Boolean enableTraffic, - @JsonProperty("services") List services + @JsonProperty("services") List services, + @JsonProperty("strategy") String strategy ) { this.enableTraffic = Optional.ofNullable(enableTraffic).orElse(false); this.services = Optional.ofNullable(services).orElse(Collections.emptyList()); + this.strategy = ManifestStrategyType.fromKey(strategy); } } } diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestHighlanderStrategy.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestHighlanderStrategy.java new file mode 100644 index 00000000000..ad1448985b2 --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestHighlanderStrategy.java @@ -0,0 +1,37 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.manifest; + +import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ManifestHighlanderStrategy implements ManifestStrategy { + private final ManifestStrategyHandler strategyHandler; + + @Autowired + public ManifestHighlanderStrategy(ManifestStrategyHandler strategyHandler) { + this.strategyHandler = strategyHandler; + } + + public void composeFlow(DeployManifestContext parentContext, StageGraphBuilder graph) { + strategyHandler.disableOldManifests(parentContext, graph); + strategyHandler.deleteOldManifests(parentContext, graph); + } +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestNoneStrategy.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestNoneStrategy.java new file mode 100644 index 00000000000..eaf45d571a3 --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestNoneStrategy.java @@ -0,0 +1,26 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.manifest; + +import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; +import org.springframework.stereotype.Component; + +@Component +public class ManifestNoneStrategy implements ManifestStrategy { + public void composeFlow(DeployManifestContext parentContext, StageGraphBuilder graph) {} +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestRedBlackStrategy.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestRedBlackStrategy.java new file mode 100644 index 00000000000..2d53702333c --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestRedBlackStrategy.java @@ -0,0 +1,36 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.manifest; + +import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +@Component +public class ManifestRedBlackStrategy implements ManifestStrategy { + private final ManifestStrategyHandler strategyHandler; + + @Autowired + public ManifestRedBlackStrategy(ManifestStrategyHandler strategyHandler) { + this.strategyHandler = strategyHandler; + } + + public void composeFlow(DeployManifestContext parentContext, StageGraphBuilder graph) { + strategyHandler.disableOldManifests(parentContext, graph); + } +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategy.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategy.java new file mode 100644 index 00000000000..647be3bc72a --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategy.java @@ -0,0 +1,24 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.manifest; + +import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; + +public interface ManifestStrategy { + void composeFlow(DeployManifestContext parentContext, StageGraphBuilder graph); +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyHandler.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyHandler.java new file mode 100644 index 00000000000..f03bf542d51 --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyHandler.java @@ -0,0 +1,95 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.manifest; + +import com.netflix.spinnaker.orca.clouddriver.pipeline.manifest.DeleteManifestStage; +import com.netflix.spinnaker.orca.clouddriver.pipeline.manifest.DisableManifestStage; +import com.netflix.spinnaker.orca.clouddriver.utils.OortHelper; +import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; + +@Component +@RequiredArgsConstructor +public class ManifestStrategyHandler { + private final OortHelper oortHelper; + + void disableOldManifests(DeployManifestContext parentContext, StageGraphBuilder graph) { + addStagesForOldManifests(parentContext, graph, DisableManifestStage.PIPELINE_CONFIG_TYPE); + } + + void deleteOldManifests(DeployManifestContext parentContext, StageGraphBuilder graph) { + addStagesForOldManifests(parentContext, graph, DeleteManifestStage.PIPELINE_CONFIG_TYPE); + } + + private Map getNewManifest(DeployManifestContext parentContext) { + List> manifests = (List>) parentContext.get("outputs.manifests"); + return manifests.get(0); + } + + private List getOldManifestNames(String application, String account, String clusterName, String namespace, String newManifestName) { + Map cluster = oortHelper.getCluster(application, account, clusterName, "kubernetes") + .orElseThrow(() -> new IllegalArgumentException(String.format("Error fetching cluster %s in account %s and namespace %s", clusterName, account, namespace))); + + List serverGroups = Optional.ofNullable((List) cluster.get("serverGroups")) + .orElse(null); + + if (serverGroups == null) { + return new ArrayList<>(); + } + + return serverGroups.stream() + .filter(s -> s.get("region").equals(namespace)) + .filter(s -> !s.get("name").equals(newManifestName)) + .map(s -> (String) s.get("name")) + .collect(Collectors.toList()); + } + + private void addStagesForOldManifests(DeployManifestContext parentContext, StageGraphBuilder graph, String stageType) { + Map deployedManifest = getNewManifest(parentContext); + String account = (String) parentContext.get("account"); + Map manifestMoniker = (Map) parentContext.get("moniker"); + String application = (String) manifestMoniker.get("app"); + + Map manifestMetadata = (Map) deployedManifest.get("metadata"); + String manifestName = String.format("replicaSet %s", (String) manifestMetadata.get("name")); + String namespace = (String) manifestMetadata.get("namespace"); + Map annotations = (Map) manifestMetadata.get("annotations"); + String clusterName = (String) annotations.get("moniker.spinnaker.io/cluster"); + String cloudProvider = "kubernetes"; + + List previousManifestNames = getOldManifestNames(application, account, clusterName, namespace, manifestName); + previousManifestNames.forEach(name -> { + graph.append((stage) -> { + stage.setType(stageType); + Map context = stage.getContext(); + context.put("account", account); + context.put("app", application); + context.put("cloudProvider", cloudProvider); + context.put("manifestName", name); + context.put("location", namespace); + }); + }); + } +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyStagesAdder.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyStagesAdder.java new file mode 100644 index 00000000000..ec663375e52 --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyStagesAdder.java @@ -0,0 +1,45 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.manifest; + +import com.netflix.spinnaker.orca.pipeline.graph.StageGraphBuilder; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ManifestStrategyStagesAdder { + private final ManifestHighlanderStrategy highlanderStrategy; + + private final ManifestRedBlackStrategy redBlackStrategy; + + private final ManifestNoneStrategy noneStrategy; + + public void addAfterStages(ManifestStrategyType strategy, StageGraphBuilder graph, DeployManifestContext parentContext) { + switch (strategy) { + case RED_BLACK: + redBlackStrategy.composeFlow(parentContext, graph); + break; + case HIGHLANDER: + highlanderStrategy.composeFlow(parentContext, graph); + break; + default: + noneStrategy.composeFlow(parentContext, graph); + } + } +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyType.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyType.java new file mode 100644 index 00000000000..b42c0cb7f1b --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/ManifestStrategyType.java @@ -0,0 +1,42 @@ +/* + * Copyright 2019 Google, Inc. + * + * 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 com.netflix.spinnaker.orca.clouddriver.tasks.manifest; + +public enum ManifestStrategyType { + RED_BLACK("redblack"), + HIGHLANDER("highlander"), + NONE("none"); + + String key; + + ManifestStrategyType(String key) { + this.key = key; + } + + public static ManifestStrategyType fromKey(String key) { + if (key == null) { + return NONE; + } + for (ManifestStrategyType strategy : values()) { + if (key.equals(strategy.key)) { + return strategy; + } + } + return NONE; + } +}