Skip to content

Commit

Permalink
feat(kubernetes): support redblack and highlander strategies (#2844)
Browse files Browse the repository at this point in the history
  • Loading branch information
maggieneterval authored Apr 18, 2019
1 parent 5afddcc commit 07384ab
Show file tree
Hide file tree
Showing 9 changed files with 334 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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;

Expand All @@ -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<String> services;
private final ManifestStrategyType strategy;

public Options(
@JsonProperty("enableTraffic") Boolean enableTraffic,
@JsonProperty("services") List<String> services
@JsonProperty("services") List<String> 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);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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) {}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
@@ -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<Map<String, ?>> manifests = (List<Map<String, ?>>) parentContext.get("outputs.manifests");
return manifests.get(0);
}

private List<String> 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<Map> serverGroups = Optional.ofNullable((List<Map>) 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<String> previousManifestNames = getOldManifestNames(application, account, clusterName, namespace, manifestName);
previousManifestNames.forEach(name -> {
graph.append((stage) -> {
stage.setType(stageType);
Map<String, Object> context = stage.getContext();
context.put("account", account);
context.put("app", application);
context.put("cloudProvider", cloudProvider);
context.put("manifestName", name);
context.put("location", namespace);
});
});
}
}
Original file line number Diff line number Diff line change
@@ -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);
}
}
}
Loading

0 comments on commit 07384ab

Please sign in to comment.