feat(kubernetes): validate manifests for spinnaker-managed rollout strategies
MODIFY
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesDeployManifestDescription.java
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesDeployManifestDescription.java
@@ -40,9 +40,16 @@ public class KubernetesDeployManifestDescription extends KubernetesAtomicOperati
private boolean enableTraffic = true;
private List<String> services;
+ private Strategy strategy;
public enum Source {
artifact,
text
}
+
+ public enum Strategy {
+ RED_BLACK,
+ HIGHLANDER,
+ NONE
+ }
}

MODIFY
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesManifestAnnotater.java
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/description/manifest/KubernetesManifestAnnotater.java
@@ -229,6 +229,17 @@ public class KubernetesManifestAnnotater {
storeAnnotation(annotations, LOAD_BALANCERS, loadBalancers);
}
+ public static void validateAnnotationsForRolloutStrategies(KubernetesManifest manifest, KubernetesDeployManifestDescription.Strategy strategy) {
+ Map<String, String> annotations = manifest.getAnnotations();
+ Integer maxVersionHistory = getAnnotation(annotations, MAX_VERSION_HISTORY, new TypeReference<Integer>() {});
+ if (strategy == KubernetesDeployManifestDescription.Strategy.RED_BLACK && maxVersionHistory != null && maxVersionHistory < 2) {
+ throw new RuntimeException(String.format(
+ "The max version history specified in your manifest conflicts with the behavior of the Red/Black rollout strategy. Please update your %s annotation to a value greater than or equal to 2.",
+ MAX_VERSION_HISTORY
+ ));
+ }
+ }
+
public static KubernetesCachingProperties getCachingProperties(KubernetesManifest manifest) {
Map<String, String> annotations = manifest.getAnnotations();

MODIFY
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/manifest/KubernetesDeployManifestOperation.java
clouddriver-kubernetes/src/main/groovy/com/netflix/spinnaker/clouddriver/kubernetes/v2/op/manifest/KubernetesDeployManifestOperation.java
@@ -98,6 +98,8 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
Set<Artifact> boundArtifacts = new HashSet<>();
+ validateManifestsForRolloutStrategies(inputManifests);
+
for (KubernetesManifest manifest : inputManifests) {
if (StringUtils.isEmpty(manifest.getNamespace()) && manifest.getKind().isNamespaced()) {
manifest.setNamespace(credentials.getDefaultNamespace());
@@ -112,6 +114,8 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
throw new IllegalArgumentException("No deployer available for Kubernetes object kind '" + manifest.getKind().toString() + "', unable to continue.");
}
+ KubernetesManifestAnnotater.validateAnnotationsForRolloutStrategies(manifest, description.getStrategy());
+
getTask().updateStatus(OP_NAME, "Swapping out artifacts in " + manifest.getFullResourceName() + " from context...");
ReplaceResult replaceResult = deployer.replaceArtifacts(manifest, artifacts, description.getAccount());
deployManifests.add(replaceResult.getManifest());
@@ -207,6 +211,14 @@ public class KubernetesDeployManifestOperation implements AtomicOperation<Operat
traffic.getLoadBalancers().forEach(l -> attachLoadBalancer(l, target));
}
+ private void validateManifestsForRolloutStrategies(List<KubernetesManifest> manifests) {
+ if (description.getStrategy() != null && (manifests.size() != 1 || manifests.get(0).getKind() != KubernetesKind.REPLICA_SET)) {
+ throw new RuntimeException(
+ "Spinnaker can manage traffic for ReplicaSets only. Please deploy exactly one ReplicaSet manifest or disable rollout strategies."
+ );
+ }
+ }
+
private void attachLoadBalancer(String loadBalancerName, KubernetesManifest target) {
Pair<KubernetesKind, String> name;
try {

feat(kubernetes): validate manifests for spinnaker-managed rollout strategies (#3589)