Skip to content

Commit

Permalink
chore(expressions): Allow to override global spel version at pipeline…
Browse files Browse the repository at this point in the history
… level (#1607)

- Refactored to allow version override at pipeline level
  • Loading branch information
jeyrschabu authored Sep 8, 2017
1 parent 4f3eaa1 commit 7682efb
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@
import org.springframework.expression.spel.standard.SpelExpressionParser;;
import org.springframework.expression.spel.support.StandardEvaluationContext;

import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.*;

import static com.netflix.spinnaker.orca.pipeline.expressions.PipelineExpressionEvaluator.ExpressionEvaluationVersion.V2;
import static com.netflix.spinnaker.orca.pipeline.expressions.PipelineExpressionEvaluator.ExpressionEvaluationVersion.V1;
Expand All @@ -38,7 +35,7 @@ public class PipelineExpressionEvaluator extends ExpressionsSupport implements E
public static final String SUMMARY = "expressionEvaluationSummary";
private static final String SPEL_EVALUATOR = "spelEvaluator";
private final ExpressionParser parser = new SpelExpressionParser();
private static String spelEvaluator = V1;
private static String spelEvaluator;
public static final String ERROR = "Failed Expression Evaluation";

public interface ExpressionEvaluationVersion {
Expand All @@ -58,65 +55,71 @@ public Map<String, Object> evaluate(Map<String, Object> source, Object rootObjec
}

public static boolean shouldUseV2Evaluator(Object obj) {
if (V2.equals(spelEvaluator)) {
return true;
}

try {
if (obj instanceof Map) {
Map pipelineConfig = (Map) obj;
//if using v2, add version to stage context.
if (V2.equals(pipelineConfig.get(SPEL_EVALUATOR))) {
updateSpelEvaluatorVersion(pipelineConfig);
return true;
}

List<Map> stages = (List<Map>) Optional.ofNullable(pipelineConfig.get("stages")).orElse(Collections.emptyList());
boolean useV2 = stages
.stream()
.filter(i -> i.containsKey(SPEL_EVALUATOR) && V2.equals(i.get(SPEL_EVALUATOR)))
.findFirst()
.orElse(null) != null;

if (useV2) {
updateSpelEvaluatorVersion(pipelineConfig);
}
} else if (obj instanceof Pipeline) {
Pipeline pipeline = (Pipeline) obj;
List stages = Optional.ofNullable(pipeline.getStages()).orElse(Collections.emptyList());
return stages
.stream()
.filter(s -> hasV2InContext(s))
.findFirst()
.orElse(null) != null;

} else if (obj instanceof Stage) {
if (hasV2InContext(obj)) {
return true;
}

Stage stage = (Stage) obj;
// if any using v2
return stage.getExecution().getStages()
.stream()
.filter(s -> hasV2InContext(s))
.findFirst()
.orElse(null) != null;
String versionInPipeline = getSpelVersion(obj);
if (Arrays.asList(V1, V2).contains(versionInPipeline) && obj instanceof Map) {
updateSpelEvaluatorVersion((Map) obj, versionInPipeline);
}

return !V1.equals(versionInPipeline) && (V2.equals(spelEvaluator) || V2.equals(versionInPipeline));
} catch (Exception e) {
LOGGER.error("Failed to determine whether to use v2 expression evaluator. using V1.", e);
}

return false;
}

private static boolean hasV2InContext(Object obj) {
return obj instanceof Stage && ((Stage) obj).getContext() != null && V2.equals(((Stage) obj).getContext().get(SPEL_EVALUATOR));
private static boolean hasVersionInContext(Object obj) {
return obj instanceof Stage && ((Stage) obj).getContext().containsKey(SPEL_EVALUATOR);
}

private static String getSpelVersion(Object obj) {
if (obj instanceof Map) {
Map pipelineConfig = (Map) obj;
if (pipelineConfig.containsKey(SPEL_EVALUATOR)) {
return (String) pipelineConfig.get(SPEL_EVALUATOR);
}

List<Map> stages = (List<Map>) Optional.ofNullable(pipelineConfig.get("stages")).orElse(Collections.emptyList());
Map stage = stages
.stream()
.filter(i -> i.containsKey(SPEL_EVALUATOR))
.findFirst()
.orElse(null);

return (stage != null) ? (String) stage.get(SPEL_EVALUATOR) : null;
} else if (obj instanceof Pipeline) {
Pipeline pipeline = (Pipeline) obj;
Stage stage = pipeline.getStages()
.stream()
.filter(PipelineExpressionEvaluator::hasVersionInContext)
.findFirst()
.orElse(null);

return (stage != null) ? (String) stage.getContext().get(SPEL_EVALUATOR) : null;

} else if (obj instanceof Stage) {
Stage stage = (Stage) obj;
if (hasVersionInContext(obj)) {
return (String) stage.getContext().get(SPEL_EVALUATOR);
}

// if any using v2
List stages = stage.getExecution().getStages();
Stage withVersion = (Stage) stages.stream()
.filter(PipelineExpressionEvaluator::hasVersionInContext)
.findFirst()
.orElse(null);

return (withVersion != null) ? (String) withVersion.getContext().get(SPEL_EVALUATOR) : null;
}

return null;
}

private static void updateSpelEvaluatorVersion(Map rawPipeline) {
private static void updateSpelEvaluatorVersion(Map rawPipeline, String versionInPipeline) {
Optional.ofNullable((List<Map>) rawPipeline.get("stages")).orElse(Collections.emptyList())
.forEach(i -> i.put(SPEL_EVALUATOR, V2));
.forEach(i -> i.put(SPEL_EVALUATOR, versionInPipeline));
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* Copyright 2017 Netflix, 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.pipeline.expressions

import com.netflix.spinnaker.orca.pipeline.model.Pipeline
import com.netflix.spinnaker.orca.pipeline.model.Stage
import spock.lang.Specification
import spock.lang.Unroll

import static com.netflix.spinnaker.orca.pipeline.expressions.PipelineExpressionEvaluator.ExpressionEvaluationVersion.*

class PipelineExpressionEvaluatorSpec extends Specification {

@Unroll
def "should determine Evaluator version from pipeline map"() {
expect:
PipelineExpressionEvaluator.shouldUseV2Evaluator(pipeline) == useV2

where:
pipeline | useV2
[:] | false
[spelEvaluator: V1] | false
[spelEvaluator: V2] | true
[stages: [ [spelEvaluator: V2]]] | true
}

@Unroll
def "should update Evaluator version if v2 used anywhere"() {
when:
PipelineExpressionEvaluator.shouldUseV2Evaluator(pipeline)

then:
def stages = pipeline.stages
(stages != null && stages[0].containsKey("spelEvaluator")) == hasVersion

where:
pipeline | hasVersion
[:] | false
[spelEvaluator: V2, stages: [[:]]] | true
[spelEvaluator: V2 ] | false
[spelEvaluator: V1, stages: [[:]]] | true
}

@Unroll
def "should be able to get version from a stage"() {
given:
def stage = new Stage<>(new Pipeline("orca"), "type", stageContext)

expect:
PipelineExpressionEvaluator.shouldUseV2Evaluator(stage) == useV2

where:
stageContext | useV2
[:] | false
[spelEvaluator: V2] | true
[spelEvaluator: V2] | true
[spelEvaluator: V1] | false
}

@Unroll
def "should be able to override global version per at pipeline level"() {
given: "global is set to v2"
PipelineExpressionEvaluator.spelEvaluator = V2

expect:
PipelineExpressionEvaluator.shouldUseV2Evaluator(pipeline) == useV2

where:
pipeline | useV2
[:] | true
[spelEvaluator: V1] | false
[spelEvaluator: null] | true
[stages: [ [spelEvaluator: V2]]] | true
[stages: [ [spelEvaluator: V1]]] | false
}
}

0 comments on commit 7682efb

Please sign in to comment.