Skip to content

Commit

Permalink
fix(SpEL): Execution context injected into expressions dynamically (#…
Browse files Browse the repository at this point in the history
…3142)

* fix(SpEL): Execution context injected into expressions dynamically
  • Loading branch information
srekapalli authored and marchello2000 committed Sep 12, 2019
1 parent b867530 commit bd27652
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@
import com.netflix.spinnaker.kork.expressions.ExpressionsSupport;
import com.netflix.spinnaker.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.pipeline.model.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import lombok.Getter;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
Expand All @@ -38,8 +40,7 @@ public class PipelineExpressionEvaluator {
public static final String SUMMARY = "expressionEvaluationSummary";
public static final String ERROR = "Failed Expression Evaluation";

private static final List<String> EXECUTION_AWARE_FUNCTIONS =
Arrays.asList("judgment", "judgement", "stage", "stageExists", "deployedServerGroups");
// No new items should go in to this list. We should use functions instead of vars going forward.
private static final List<String> EXECUTION_AWARE_ALIASES =
Collections.singletonList("deployedServerGroups");

Expand All @@ -63,8 +64,11 @@ public class PipelineExpressionEvaluator {
private final ParserContext parserContext = new TemplateParserContext("${", "}");
private final ExpressionsSupport support;

@Getter private final Set<String> executionAwareFunctions = new HashSet<String>();

public PipelineExpressionEvaluator(List<ExpressionFunctionProvider> expressionFunctionProviders) {
this.support = new ExpressionsSupport(extraAllowedReturnTypes, expressionFunctionProviders);
initExecutionAwareFunctions(expressionFunctionProviders);
}

public Map<String, Object> evaluate(
Expand All @@ -82,13 +86,15 @@ public Map<String, Object> evaluate(
private final Function<String, String> includeExecutionParameter =
e -> {
String expression = e;
for (String fn : EXECUTION_AWARE_FUNCTIONS) {
for (String fn : this.executionAwareFunctions) {
if (expression.contains("#" + fn)
&& !expression.contains("#" + fn + "( #root.execution, ")) {
expression = expression.replaceAll("#" + fn + "\\(", "#" + fn + "( #root.execution, ");
}
}

// 'deployServerGroups' is a variable instead of a function and this block handles that.
// Migrate the pipelines to use function instead, before removing this block of code.
for (String a : EXECUTION_AWARE_ALIASES) {
if (expression.contains(a) && !expression.contains("#" + a + "( #root.execution, ")) {
expression = expression.replaceAll(a, "#" + a + "( #root.execution)");
Expand All @@ -97,4 +103,21 @@ public Map<String, Object> evaluate(

return expression;
};

private void initExecutionAwareFunctions(
List<ExpressionFunctionProvider> expressionFunctionProviders) {

expressionFunctionProviders.forEach(
p -> {
p.getFunctions()
.getFunctionsDefinitions()
.forEach(
f -> {
if (!f.getParameters().isEmpty()
&& f.getParameters().get(0).getType() == Execution.class) {
this.executionAwareFunctions.add(f.getName());
}
});
});
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2019 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.kork.expressions.ExpressionFunctionProvider
import com.netflix.spinnaker.orca.pipeline.model.Execution
import spock.lang.Specification

class PipelineExpressionEvaluatorSpec extends Specification {

def 'should set execution aware functions for the given function providers'() {

given: 'function providers'
ExpressionFunctionProvider expressionFunctionProvider1 = buildExpressionFunctionProvider('pipeline')
ExpressionFunctionProvider expressionFunctionProvider2 = buildExpressionFunctionProvider('jenkins')

when: 'registered with pipeline evaluator'
PipelineExpressionEvaluator evaluator = new PipelineExpressionEvaluator([expressionFunctionProvider1, expressionFunctionProvider2])

then:
noExceptionThrown()
evaluator.getExecutionAwareFunctions().size() == 2 // only 1 function is execution aware.
evaluator.getExecutionAwareFunctions().findAll { it.contains('functionWithExecutionContext') }.size() == 2
}

private ExpressionFunctionProvider buildExpressionFunctionProvider(String providerName) {
new ExpressionFunctionProvider() {
@Override
String getNamespace() {
return null
}

@Override
ExpressionFunctionProvider.Functions getFunctions() {
return new ExpressionFunctionProvider.Functions(
new ExpressionFunctionProvider.FunctionDefinition(
"functionWithExecutionContext-${providerName}",
new ExpressionFunctionProvider.FunctionParameter(
Execution.class,
"execution",
"The execution containing the currently executing stage"),
new ExpressionFunctionProvider.FunctionParameter(
String.class, "someArg", "A valid stage reference identifier")),
new ExpressionFunctionProvider.FunctionDefinition(
"functionWithNoExecutionContext-${providerName}",
new ExpressionFunctionProvider.FunctionParameter(
String.class, "someArg", "A valid stage reference identifier"))
)
}
}
}

}

0 comments on commit bd27652

Please sign in to comment.