Skip to content

Commit 61e42b3

Browse files
committed
feat(SpEL): implement to configure the limit of characters for SpEL expressions
Spring Expression Lanuage (SpEL) has a default limit of 10,000 characters. Springframework provides the feature to configure the limit. This feature allows to configure the limit of characters for SpEL expressions. Approach: In order to use an expression with characters more than the given default limit, require to follow either of the below approaches: 1. For Springframework >=5.3.28 and <6.1.3, by setting `maximumExpressionLength` field while instantiating the custom `SpelParserConfiguration` class. spring-projects/spring-framework#30380 spring-projects/spring-framework#30446 2. For Springframework >=6.1.3, by setting a JVM system property or Spring property named `spring.context.expression.maxLength` to the maximum expression length needed by your application. spring-projects/spring-framework#31952 spring-projects/spring-framework@7855986 Spinnaker supports spring boot 2.7.18, that brings springframework 5.3.31 [https://docs.spring.io/spring-boot/docs/2.7.18/reference/html/dependency-versions.html#appendix.dependency-versions.propertie9]. So first approach need to be implemented along with spinnaker enhancement to expose the `maximumExpressionLength` field.
1 parent cafd094 commit 61e42b3

File tree

4 files changed

+115
-4
lines changed

4 files changed

+115
-4
lines changed

echo-pipelinetriggers/src/main/java/com/netflix/spinnaker/echo/config/PipelineTriggerConfiguration.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.netflix.spinnaker.fiat.shared.FiatPermissionEvaluator;
1414
import com.netflix.spinnaker.fiat.shared.FiatStatus;
1515
import com.netflix.spinnaker.kork.dynamicconfig.DynamicConfigService;
16+
import com.netflix.spinnaker.kork.expressions.config.ExpressionProperties;
1617
import com.netflix.spinnaker.retrofit.Slf4jRetrofitLogger;
1718
import java.util.concurrent.ExecutorService;
1819
import java.util.concurrent.Executors;
@@ -34,7 +35,8 @@
3435
@EnableConfigurationProperties({
3536
FiatClientConfigurationProperties.class,
3637
PipelineCacheConfigurationProperties.class,
37-
QuietPeriodIndicatorConfigurationProperties.class
38+
QuietPeriodIndicatorConfigurationProperties.class,
39+
ExpressionProperties.class
3840
})
3941
public class PipelineTriggerConfiguration {
4042
private OkHttpClientProvider clientProvider;

echo-pipelinetriggers/src/main/java/com/netflix/spinnaker/echo/pipelinetriggers/postprocessors/ExpectedArtifactExpressionEvaluationPostProcessor.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,18 @@
66
import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact;
77
import com.netflix.spinnaker.kork.expressions.ExpressionEvaluationSummary;
88
import com.netflix.spinnaker.kork.expressions.ExpressionTransform;
9+
import com.netflix.spinnaker.kork.expressions.config.ExpressionProperties;
910
import java.util.Collections;
1011
import java.util.List;
1112
import java.util.Map;
1213
import java.util.function.Function;
1314
import java.util.stream.Collectors;
15+
import org.springframework.beans.factory.annotation.Autowired;
1416
import org.springframework.expression.EvaluationContext;
1517
import org.springframework.expression.ExpressionParser;
1618
import org.springframework.expression.ParserContext;
1719
import org.springframework.expression.common.TemplateParserContext;
20+
import org.springframework.expression.spel.SpelParserConfiguration;
1821
import org.springframework.expression.spel.standard.SpelExpressionParser;
1922
import org.springframework.expression.spel.support.StandardEvaluationContext;
2023
import org.springframework.stereotype.Component;
@@ -26,11 +29,18 @@
2629
@Component
2730
public class ExpectedArtifactExpressionEvaluationPostProcessor implements PipelinePostProcessor {
2831
private final ObjectMapper mapper;
29-
private final ExpressionParser parser = new SpelExpressionParser();
32+
private final ExpressionParser parser;
3033
private final ParserContext parserContext = new TemplateParserContext("${", "}");
3134

32-
public ExpectedArtifactExpressionEvaluationPostProcessor(ObjectMapper mapper) {
35+
public ExpectedArtifactExpressionEvaluationPostProcessor(
36+
ObjectMapper mapper, @Autowired ExpressionProperties expressionProperties) {
3337
this.mapper = mapper;
38+
parser =
39+
new SpelExpressionParser(
40+
expressionProperties.getMaxExpressionLength() > 0
41+
? new SpelParserConfiguration(
42+
null, null, false, false, 0, expressionProperties.getMaxExpressionLength())
43+
: new SpelParserConfiguration());
3444
}
3545

3646
@Override

echo-pipelinetriggers/src/test/groovy/com/netflix/spinnaker/echo/pipelinetriggers/postprocessors/ExpectedArtifactExpressionEvaluationPostProcessorSpec.groovy

+2-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import com.netflix.spinnaker.echo.model.Trigger
66
import com.netflix.spinnaker.echo.test.RetrofitStubs
77
import com.netflix.spinnaker.kork.artifacts.model.Artifact
88
import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact
9+
import com.netflix.spinnaker.kork.expressions.config.ExpressionProperties
910
import spock.lang.Shared
1011
import spock.lang.Specification
1112
import spock.lang.Subject
1213

1314
class ExpectedArtifactExpressionEvaluationPostProcessorSpec extends Specification implements RetrofitStubs {
1415
@Subject
15-
def artifactPostProcessor = new ExpectedArtifactExpressionEvaluationPostProcessor(EchoObjectMapper.getInstance())
16+
def artifactPostProcessor = new ExpectedArtifactExpressionEvaluationPostProcessor(EchoObjectMapper.getInstance(), new ExpressionProperties())
1617

1718
@Shared
1819
def trigger = Trigger.builder()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Copyright 2024 OpsMx, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.netflix.spinnaker.echo.pipelinetriggers.postprocessors;
18+
19+
import static org.junit.jupiter.api.Assertions.assertTrue;
20+
21+
import com.netflix.spinnaker.echo.jackson.EchoObjectMapper;
22+
import com.netflix.spinnaker.echo.model.Pipeline;
23+
import com.netflix.spinnaker.echo.model.Trigger;
24+
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
25+
import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact;
26+
import com.netflix.spinnaker.kork.expressions.config.ExpressionProperties;
27+
import java.util.List;
28+
import java.util.concurrent.atomic.AtomicInteger;
29+
import org.junit.jupiter.api.Test;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
32+
import org.springframework.boot.test.context.SpringBootTest;
33+
import org.springframework.test.context.ContextConfiguration;
34+
import org.springframework.test.context.TestPropertySource;
35+
36+
@ContextConfiguration(classes = ExpectedArtifactExpressionLengthTest.class)
37+
@SpringBootTest
38+
@EnableConfigurationProperties(ExpressionProperties.class)
39+
@TestPropertySource(properties = {"expression.max-expression-length=11000"})
40+
class ExpectedArtifactExpressionLengthTest {
41+
@Autowired private ExpressionProperties expressionProperties;
42+
43+
@Test
44+
void customExpressionLength() {
45+
String expression = String.format("%s", repeat("T", 10900));
46+
47+
ExpectedArtifactExpressionEvaluationPostProcessor artifactPostProcessor =
48+
new ExpectedArtifactExpressionEvaluationPostProcessor(
49+
EchoObjectMapper.getInstance(), expressionProperties);
50+
51+
Trigger trigger =
52+
Trigger.builder()
53+
.enabled(true)
54+
.type("jenkins")
55+
.master("master")
56+
.job(expression)
57+
.buildNumber(100)
58+
.build();
59+
60+
ExpectedArtifact artifact =
61+
ExpectedArtifact.builder()
62+
.matchArtifact(
63+
Artifact.builder()
64+
.name(
65+
"group:artifact:${trigger['job'] == '"
66+
+ expression
67+
+ "' ? 'expr-worked' : 'expr-not-worked'}")
68+
.version("${trigger['buildNumber']}")
69+
.type("maven/file")
70+
.build())
71+
.id("testId")
72+
.build();
73+
74+
Pipeline inputPipeline =
75+
Pipeline.builder()
76+
.application("application")
77+
.name("name")
78+
.id(Integer.toString(new AtomicInteger(1).getAndIncrement()))
79+
.trigger(trigger)
80+
.expectedArtifacts(List.of(artifact))
81+
.build()
82+
.withTrigger(trigger);
83+
84+
Pipeline outputPipeline = artifactPostProcessor.processPipeline(inputPipeline);
85+
86+
Artifact evaluatedArtifact = outputPipeline.getExpectedArtifacts().get(0).getMatchArtifact();
87+
88+
assertTrue(evaluatedArtifact.getName().equalsIgnoreCase("group:artifact:expr-worked"));
89+
}
90+
91+
private String repeat(String str, int count) {
92+
String res = "";
93+
for (int i = 0; i < count; i++) {
94+
res += str;
95+
}
96+
return res;
97+
}
98+
}

0 commit comments

Comments
 (0)