From 58e02c2eeade5c46a684b41ce49060d6969b7a6e Mon Sep 17 00:00:00 2001 From: maggieneterval Date: Wed, 13 Mar 2019 17:25:02 -0400 Subject: [PATCH] feat(kubernetes): add expression evaluation options to bake and deploy manifest stages --- .../tasks/manifests/BakeManifestContext.java | 55 +++++++++++++++++++ .../manifests/CreateBakeManifestTask.java | 30 +++++++--- .../tasks/manifest/DeployManifestContext.java | 48 ++++++++++++++++ .../tasks/manifest/DeployManifestTask.java | 30 ++++++---- 4 files changed, 142 insertions(+), 21 deletions(-) create mode 100644 orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/BakeManifestContext.java create mode 100644 orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestContext.java diff --git a/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/BakeManifestContext.java b/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/BakeManifestContext.java new file mode 100644 index 00000000000..e36dbe946eb --- /dev/null +++ b/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/BakeManifestContext.java @@ -0,0 +1,55 @@ +/* + * 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.bakery.tasks.manifests; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact; +import lombok.Getter; + +import java.util.List; +import java.util.Map; + +@Getter +public class BakeManifestContext { + private final List inputArtifacts; + private final List expectedArtifacts; + private final Map overrides; + private final Boolean evaluateOverrideExpressions; + private final String templateRenderer; + private final String outputName; + private final String namespace; + + // There does not seem to be a way to auto-generate a constructor using our current version of Lombok (1.16.20) that + // Jackson can use to deserialize. + public BakeManifestContext( + @JsonProperty("inputArtifacts") List inputArtifacts, + @JsonProperty("expectedArtifacts") List expectedArtifacts, + @JsonProperty("overrides") Map overrides, + @JsonProperty("evaluateOverrideExpressions") Boolean evaluateOverrideExpressions, + @JsonProperty("templateRenderer") String templateRenderer, + @JsonProperty("outputName") String outputName, + @JsonProperty("namespace") String namespace + ) { + this.inputArtifacts = inputArtifacts; + this.expectedArtifacts = expectedArtifacts; + this.overrides = overrides; + this.evaluateOverrideExpressions = evaluateOverrideExpressions; + this.templateRenderer = templateRenderer; + this.outputName = outputName; + this.namespace = namespace; + } +} diff --git a/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/CreateBakeManifestTask.java b/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/CreateBakeManifestTask.java index e746854aa70..1317882c2b5 100644 --- a/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/CreateBakeManifestTask.java +++ b/orca-bakery/src/main/groovy/com/netflix/spinnaker/orca/bakery/tasks/manifests/CreateBakeManifestTask.java @@ -17,7 +17,6 @@ package com.netflix.spinnaker.orca.bakery.tasks.manifests; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.netflix.spinnaker.kork.artifacts.model.Artifact; import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact; @@ -28,6 +27,7 @@ import com.netflix.spinnaker.orca.bakery.api.manifests.helm.HelmBakeManifestRequest; import com.netflix.spinnaker.orca.pipeline.model.Stage; import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver; +import com.netflix.spinnaker.orca.pipeline.util.ContextParameterProcessor; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -62,12 +62,15 @@ public long getTimeout() { @Autowired ObjectMapper objectMapper; + @Autowired + ContextParameterProcessor contextParameterProcessor; + @Nonnull @Override public TaskResult execute(@Nonnull Stage stage) { - Map context = stage.getContext(); + BakeManifestContext context = stage.mapTo(BakeManifestContext.class); - List inputArtifactsObj = objectMapper.convertValue(context.get("inputArtifacts"), new TypeReference>() {}); + List inputArtifactsObj = context.getInputArtifacts(); List inputArtifacts; if (inputArtifactsObj == null || inputArtifactsObj.isEmpty()) { @@ -84,7 +87,7 @@ public TaskResult execute(@Nonnull Stage stage) { return a; }).collect(Collectors.toList()); - List expectedArtifacts = objectMapper.convertValue(context.get("expectedArtifacts"), new TypeReference>() {}); + List expectedArtifacts = context.getExpectedArtifacts(); if (expectedArtifacts == null || expectedArtifacts.isEmpty()) { throw new IllegalArgumentException("At least one expected artifact to baked manifest must be supplied"); @@ -96,12 +99,21 @@ public TaskResult execute(@Nonnull Stage stage) { String outputArtifactName = expectedArtifacts.get(0).getMatchArtifact().getName(); + Map overrides = context.getOverrides(); + if (context.getEvaluateOverrideExpressions()) { + overrides = contextParameterProcessor.process( + overrides, + contextParameterProcessor.buildExecutionContext(stage, true), + true + ); + } + HelmBakeManifestRequest request = new HelmBakeManifestRequest(); request.setInputArtifacts(inputArtifacts); - request.setTemplateRenderer((String) context.get("templateRenderer")); - request.setOutputName((String) context.get("outputName")); - request.setOverrides(objectMapper.convertValue(context.get("overrides"), new TypeReference>() { })); - request.setNamespace((String) context.get("namespace")); + request.setTemplateRenderer(context.getTemplateRenderer()); + request.setOutputName(context.getOutputName()); + request.setOverrides(overrides); + request.setNamespace(context.getNamespace()); request.setOutputArtifactName(outputArtifactName); log.info("Requesting {}", request); @@ -114,7 +126,7 @@ public TaskResult execute(@Nonnull Stage stage) { } @Data - private static class InputArtifactPair { + protected static class InputArtifactPair { String id; String account; } 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 new file mode 100644 index 00000000000..ac0a1970983 --- /dev/null +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestContext.java @@ -0,0 +1,48 @@ +/* + * 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.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; + +import java.util.HashMap; +import java.util.List; + +@Getter +public class DeployManifestContext extends HashMap { + private final String source; + private final String manifestArtifactId; + private final String manifestArtifactAccount; + private final Boolean skipExpressionEvaluation; + private final List requiredArtifactIds; + + // There does not seem to be a way to auto-generate a constructor using our current version of Lombok (1.16.20) that + // Jackson can use to deserialize. + public DeployManifestContext( + @JsonProperty("source") String source, + @JsonProperty("manifestArtifactId") String manifestArtifactId, + @JsonProperty("manifestArtifactAccount") String manifestArtifactAccount, + @JsonProperty("skipExpressionEvaluation") Boolean skipExpressionEvaluation, + @JsonProperty("skipExpressionEvaluation") List requiredArtifactIds + ){ + this.source = source; + this.manifestArtifactId = manifestArtifactId; + this.manifestArtifactAccount = manifestArtifactAccount; + this.skipExpressionEvaluation = skipExpressionEvaluation; + this.requiredArtifactIds = requiredArtifactIds; + } +} diff --git a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestTask.java b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestTask.java index 1570f0756d5..e375feab6d6 100644 --- a/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestTask.java +++ b/orca-clouddriver/src/main/groovy/com/netflix/spinnaker/orca/clouddriver/tasks/manifest/DeployManifestTask.java @@ -80,26 +80,29 @@ public TaskResult execute(@Nonnull Stage stage) { String cloudProvider = getCloudProvider(stage); List artifacts = artifactResolver.getArtifacts(stage); - Map task = new HashMap(stage.getContext()); - String artifactSource = (String) task.get("source"); + DeployManifestContext context = stage.mapTo(DeployManifestContext.class); + Map task = new HashMap<>(context); + String artifactSource = context.getSource(); if (StringUtils.isNotEmpty(artifactSource) && artifactSource.equals("artifact")) { - if (task.get("manifestArtifactId") == null) { + String manifestArtifactId = context.getManifestArtifactId(); + if (manifestArtifactId == null) { throw new IllegalArgumentException("No manifest artifact was specified."); } - if (task.get("manifestArtifactAccount") == null) { + String manifestArtifactAccount = context.getManifestArtifactAccount(); + if (manifestArtifactAccount == null) { throw new IllegalArgumentException("No manifest artifact account was specified."); } - Artifact manifestArtifact = artifactResolver.getBoundArtifactForId(stage, task.get("manifestArtifactId").toString()); + Artifact manifestArtifact = artifactResolver.getBoundArtifactForId(stage, manifestArtifactId); if (manifestArtifact == null) { - throw new IllegalArgumentException("No artifact could be bound to '" + task.get("manifestArtifactId") + "'"); + throw new IllegalArgumentException("No artifact could be bound to '" + manifestArtifactId + "'"); } log.info("Using {} as the manifest to be deployed", manifestArtifact); - manifestArtifact.setArtifactAccount((String) task.get("manifestArtifactAccount")); + manifestArtifact.setArtifactAccount(manifestArtifactAccount); Object parsedManifests = retrySupport.retry(() -> { try { Response manifestText = oort.fetchArtifact(manifestArtifact); @@ -119,14 +122,17 @@ public TaskResult execute(@Nonnull Stage stage) { Map manifestWrapper = new HashMap<>(); manifestWrapper.put("manifests", manifests); - manifestWrapper = contextParameterProcessor.process( + Boolean skipExpressionEvaluation = context.getSkipExpressionEvaluation(); + if (!skipExpressionEvaluation) { + manifestWrapper = contextParameterProcessor.process( manifestWrapper, contextParameterProcessor.buildExecutionContext(stage, true), true - ); + ); - if (manifestWrapper.containsKey("expressionEvaluationSummary")) { - throw new IllegalStateException("Failure evaluating manifest expressions: " + manifestWrapper.get("expressionEvaluationSummary")); + if (manifestWrapper.containsKey("expressionEvaluationSummary")) { + throw new IllegalStateException("Failure evaluating manifest expressions: " + manifestWrapper.get("expressionEvaluationSummary")); + } } return manifestWrapper.get("manifests"); @@ -140,7 +146,7 @@ public TaskResult execute(@Nonnull Stage stage) { task.put("source", "text"); } - List requiredArtifactIds = (List) task.get("requiredArtifactIds"); + List requiredArtifactIds = context.getRequiredArtifactIds(); List requiredArtifacts = new ArrayList<>(); requiredArtifactIds = requiredArtifactIds == null ? new ArrayList<>() : requiredArtifactIds; for (String id : requiredArtifactIds) {