Skip to content

Commit 462e355

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 9bcd1fe commit 462e355

File tree

4 files changed

+116
-4
lines changed

4 files changed

+116
-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

+16-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,22 @@
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+
if (expressionProperties != null) {
39+
parser =
40+
new SpelExpressionParser(
41+
expressionProperties.getMaxExpressionLength() > 0
42+
? new SpelParserConfiguration(
43+
null, null, false, false, 0, expressionProperties.getMaxExpressionLength())
44+
: new SpelParserConfiguration());
45+
} else {
46+
parser = new SpelExpressionParser();
47+
}
3448
}
3549

3650
@Override

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import spock.lang.Subject
1212

1313
class ExpectedArtifactExpressionEvaluationPostProcessorSpec extends Specification implements RetrofitStubs {
1414
@Subject
15-
def artifactPostProcessor = new ExpectedArtifactExpressionEvaluationPostProcessor(EchoObjectMapper.getInstance())
15+
def artifactPostProcessor = new ExpectedArtifactExpressionEvaluationPostProcessor(EchoObjectMapper.getInstance(), null)
1616

1717
@Shared
1818
def trigger = Trigger.builder()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
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.test.context.SpringBootTest;
32+
import org.springframework.test.context.ContextConfiguration;
33+
import org.springframework.test.context.TestPropertySource;
34+
35+
@ContextConfiguration(classes = ExpressionProperties.class)
36+
@SpringBootTest
37+
@TestPropertySource(properties = {"expression.max-expression-length=11000"})
38+
class ExpectedArtifactExpressionLengthTest {
39+
@Autowired private ExpressionProperties expressionProperties;
40+
41+
@Test
42+
void customExpressionLength() {
43+
String expression = String.format("%s", repeat("T", 10900));
44+
45+
ExpectedArtifactExpressionEvaluationPostProcessor artifactPostProcessor =
46+
new ExpectedArtifactExpressionEvaluationPostProcessor(
47+
EchoObjectMapper.getInstance(), expressionProperties);
48+
49+
Trigger trigger =
50+
Trigger.builder()
51+
.enabled(true)
52+
.type("jenkins")
53+
.master("master")
54+
.job(expression)
55+
.buildNumber(100)
56+
.build();
57+
58+
ExpectedArtifact artifact =
59+
ExpectedArtifact.builder()
60+
.matchArtifact(
61+
Artifact.builder()
62+
.name(
63+
"group:artifact:${trigger['job'] == '"
64+
+ expression
65+
+ "' ? 'expr-worked' : 'expr-not-worked'}")
66+
.version("${trigger['buildNumber']}")
67+
.type("maven/file")
68+
.build())
69+
.id("testId")
70+
.build();
71+
72+
Pipeline inputPipeline =
73+
Pipeline.builder()
74+
.application("application")
75+
.name("name")
76+
.id(Integer.toString(new AtomicInteger(1).getAndIncrement()))
77+
.trigger(trigger)
78+
.expectedArtifacts(List.of(artifact))
79+
.build()
80+
.withTrigger(trigger);
81+
82+
Pipeline outputPipeline = artifactPostProcessor.processPipeline(inputPipeline);
83+
84+
Artifact evaluatedArtifact = outputPipeline.getExpectedArtifacts().get(0).getMatchArtifact();
85+
86+
assertTrue(evaluatedArtifact.getName().equalsIgnoreCase("group:artifact:expr-worked"));
87+
}
88+
89+
private String repeat(String str, int count) {
90+
String res = "";
91+
for (int i = 0; i < count; i++) {
92+
res += str;
93+
}
94+
return res;
95+
}
96+
}

0 commit comments

Comments
 (0)