Skip to content

Commit

Permalink
feature(expressions): fetchReference SpEL function (#1177)
Browse files Browse the repository at this point in the history
Introduces a simple SpEL function that returns the reference that was stored
in the artifact store.

Signed-off-by: benjamin-j-powell <[email protected]>
Co-authored-by: benjamin-j-powell <[email protected]>
  • Loading branch information
xibz and benjamin-j-powell committed Apr 6, 2024
1 parent 277447d commit d57eaf4
Show file tree
Hide file tree
Showing 4 changed files with 84 additions and 12 deletions.
1 change: 1 addition & 0 deletions kork-expressions/kork-expressions.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
testImplementation project(":kork-artifacts")
testImplementation "org.assertj:assertj-core"
testImplementation "org.junit.jupiter:junit-jupiter-api"
testImplementation "org.junit.jupiter:junit-jupiter-params"
testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"
testRuntimeOnly "org.slf4j:slf4j-simple"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.netflix.spinnaker.kork.expressions.allowlist.MapPropertyAccessor;
import com.netflix.spinnaker.kork.expressions.allowlist.ReturnTypeRestrictor;
import com.netflix.spinnaker.kork.expressions.config.ExpressionProperties;
import com.netflix.spinnaker.kork.expressions.functions.ArtifactStoreFunctions;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down Expand Up @@ -96,7 +97,9 @@ public ExpressionsSupport(
expressionFunctionProviders =
new ArrayList<>(
Arrays.asList(
new JsonExpressionFunctionProvider(), new StringExpressionFunctionProvider()));
new ArtifactStoreFunctions(),
new JsonExpressionFunctionProvider(),
new StringExpressionFunctionProvider()));

if (extraExpressionFunctionProviders != null) {
expressionFunctionProviders.addAll(extraExpressionFunctionProviders);
Expand Down Expand Up @@ -166,6 +169,7 @@ private StandardEvaluationContext createEvaluationContext(
evaluationContext.setTypeLocator(new AllowListTypeLocator());
evaluationContext.setTypeConverter(
new ArtifactUriToReferenceConverter(ArtifactStore.getInstance()));

evaluationContext.setMethodResolvers(
Collections.singletonList(new FilteredMethodResolver(returnTypeRestrictor)));
evaluationContext.setPropertyAccessors(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2024 Apple 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.kork.expressions.functions;

import com.netflix.spinnaker.kork.api.expressions.ExpressionFunctionProvider;
import org.jetbrains.annotations.Nullable;

/** Houses all SpEL related functions that deal specifically with the artifact store */
public class ArtifactStoreFunctions implements ExpressionFunctionProvider {
@Nullable
@Override
public String getNamespace() {
return null;
}

/**
* Used to return the original based64 reference that was stored by the artifact store. It's
* important to note that utilizing this SpEL function will balloon the context, e.g. increasing
* the size of the execution context.
*
* <p>{@code ${ #fetchReference(#stage('Bake Manifest').context.artifacts[0].reference) }}
*/
public static String fetchReference(String ref) {
// We do not have to do anything here since the artifact URI converter will
// convert any references to the original stored reference
//
// One caveat with this function is that if there is nothing to retrieve,
// the original string will just be returned
return ref;
}

@Override
public Functions getFunctions() {
return new Functions(
new FunctionDefinition(
"fetchReference",
"Retrieve artifact reference",
new FunctionParameter(
String.class,
"ref",
"Will return the associated artifact's base64 reference back.")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package com.netflix.spinnaker.kork.expressions;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;

Expand All @@ -32,7 +31,12 @@
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Stream;
import lombok.SneakyThrows;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.ParserContext;
import org.springframework.expression.common.TemplateParserContext;
Expand Down Expand Up @@ -107,29 +111,36 @@ public void testToJsonWhenComposedExpressionAndEvaluationContext() {
assertThat(evaluated).isEqualTo("{\"json_file\":\"${#toJson(#doNotEval(file_json))}\"}");
}

@Test
public void artifactReferenceInSpEL() {
@ParameterizedTest
@MethodSource("artifactReferenceArgs")
public void artifactReferenceInSpEL(String reference, String expected, String expr) {
MockArtifactStore artifactStore = new MockArtifactStore();
ArtifactStore.setInstance(artifactStore);
ExpressionProperties expressionProperties = new ExpressionProperties();
String expectedValue = "Hello world";
artifactStore.cache.put("ref://app/sha", expectedValue);
String expr = "${#fromBase64(\"ref://app/sha\")}";
artifactStore.cache.put("ref://app/sha", reference);
Map<String, Object> testContext =
Collections.singletonMap(
"artifactReference", Collections.singletonMap("artifactReference", expr));
Collections.singletonMap("artifact", Artifact.builder().reference("ref://app/sha"));

ExpressionsSupport expressionsSupport = new ExpressionsSupport(null, expressionProperties);

StandardEvaluationContext evaluationContext =
expressionsSupport.buildEvaluationContext(
new ExpressionTransformTest.Pipeline(new ExpressionTransformTest.Trigger(123)), true);
expressionsSupport.buildEvaluationContext(testContext, true);

String evaluated =
new ExpressionTransform(parserContext, parser, Function.identity())
.transformString(expr, evaluationContext, new ExpressionEvaluationSummary());

assertThat(evaluated).isEqualTo(expectedValue);
assertThat(evaluated).isEqualTo(expected);
}

@SneakyThrows
public static Stream<Arguments> artifactReferenceArgs() {
return Stream.of(
Arguments.of("Hello world", "Hello world", "${#fromBase64(\"ref://app/sha\")}"),
Arguments.of(
"Hello world",
Base64.getEncoder().encodeToString("Hello world".getBytes()),
"${#fetchReference(artifact.reference)}"));
}

@Test
Expand Down

0 comments on commit d57eaf4

Please sign in to comment.