Skip to content

Commit

Permalink
feat(pipeline/configuration): Updated pipeline level configuration th…
Browse files Browse the repository at this point in the history
…at introduces a new property named metadata.

SpEL processing pipeline.metadata so that it is resolved/ready when referenced.
  • Loading branch information
GARCIA, JOSE committed Dec 6, 2024
1 parent f327686 commit e2a3499
Show file tree
Hide file tree
Showing 10 changed files with 133 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public interface PipelineExecution {
@Nonnull
ExecutionType getType();

Map<String, Object> getMetadata();

void setMetadata(Map<String, Object> metadata);

String getId();

void setId(String id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.netflix.spinnaker.orca.api.pipeline.graph.TaskNode;
import com.netflix.spinnaker.orca.api.pipeline.models.StageExecution;
import com.netflix.spinnaker.orca.pipeline.expressions.PipelineExpressionEvaluator;
import com.netflix.spinnaker.orca.pipeline.model.PipelineExecutionImpl;
import com.netflix.spinnaker.orca.pipeline.model.StageContext;
import com.netflix.spinnaker.orca.pipeline.tasks.EvaluateVariablesTask;
import com.netflix.spinnaker.orca.pipeline.util.ContextParameterProcessor;
Expand Down Expand Up @@ -76,6 +77,20 @@ public boolean processExpressions(

EvaluateVariablesStageContext context = stage.mapTo(EvaluateVariablesStageContext.class);
StageContext augmentedContext = contextParameterProcessor.buildExecutionContext(stage);

PipelineExecutionImpl pipelineExecution =
(PipelineExecutionImpl) augmentedContext.get("execution");
// Evaluate spel expressions on pipeline.metadata so that they are resolved before evaluating at
// stage level.
// This is needed for in case pipeline.metadata is referenced at stage level.
Map<String, Object> evaluatedPipelineMetadata =
contextParameterProcessor.process(
mapper.convertValue(
pipelineExecution.getMetadata(), new TypeReference<Map<String, Object>>() {}),
augmentedContext,
true);
pipelineExecution.setMetadata(evaluatedPipelineMetadata);

Map<String, Object> varSourceToEval = new HashMap<>();
int lastFailedCount = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ private PipelineExecution parsePipeline(Map<String, Object> config) {
.withSpelEvaluator(getString(config, "spelEvaluator"))
.withTemplateVariables(getMap(config, "templateVariables"))
.withIncludeAllowedAccounts(executionConfigurationProperties.isIncludeAllowedAccounts())
.withMetadata(getMap(config, "metadata"))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ public PipelineBuilder(String application) {
pipeline = PipelineExecutionImpl.newPipeline(application);
}

public PipelineBuilder withMetadata(Map<String, Object> metadata) {
pipeline.setMetadata(metadata);
return this;
}

public PipelineBuilder withIncludeAllowedAccounts(boolean includeAllowedAccounts) {
this.includeAllowedAccounts = includeAllowedAccounts;
return this;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ public PipelineExecutionImpl(
return type;
}

private Map<String, Object> metadata;

public Map<String, Object> getMetadata() {
return this.metadata;
}

public void setMetadata(Map<String, Object> metadata) {
this.metadata = metadata;
}

private String id;

public @Nonnull String getId() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,27 @@ class EvaluateVariablesStageSpec extends Specification {
stage.context.notifications[0].address == "someone@somewhere.com"
}

void "Should eval successful when referencing pipeline metadata"() {
setup:
def summary = new ExpressionEvaluationSummary()

def stage = stage {
refId = "1"
type = "evaluateVariables"
context["notifications"] = [
[address: '${execution.metadata.myaddress}']
]
execution["metadata"] = [myaddress: "someone@somewhere.com"]
}

when:
def shouldContinue = evaluateVariablesStage.processExpressions(stage, contextParameterProcessor, summary)

then:
shouldContinue == false
stage.context.notifications[0].address == "someone@somewhere.com"
}

void "Should correctly clean variables in restart scenario"() {
setup:
def summary = new ExpressionEvaluationSummary()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,4 +81,16 @@ void buildExcludesAllowedAccountsWhenFalse() {
// then
assertThat(execution.getAuthentication().getAllowedAccounts()).isEqualTo(Set.of());
}

@Test
void buildInlcludesMetadata() {

// when
PipelineBuilder pipelineBuilder =
new PipelineBuilder("my-application").withMetadata(new HashMap<String, Object>());
PipelineExecution execution = pipelineBuilder.build();

// then
assertThat(execution.getMetadata()).isNotNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.assertj.core.api.Assertions.assertThat;

import com.netflix.spinnaker.orca.api.pipeline.models.ExecutionType;
import java.util.HashMap;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand Down Expand Up @@ -74,4 +75,13 @@ void getTotalSizeCompleteInfo() {
// then
assertThat(pipelineExecution.getTotalSize().get()).isEqualTo(pipelineSize + stageSize);
}

@Test
void getMetadata() {
// given
pipelineExecution.setMetadata(new HashMap<String, Object>());

// then
assertThat(pipelineExecution.getMetadata()).isNotNull();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ class EchoNotifyingExecutionListener implements ExecutionListener {
}

private void processSpelInNotifications(PipelineExecution execution) {
//Evaluate spel expressions on pipeline.metadata so that they are resolved before evaluating notifications.
//This is needed so that the values are ready for when they are referenced at pipeline notification level.
Map<String, Object> evaluatedPipelineMetadata = contextParameterProcessor.process(objectMapper.convertValue(execution.getMetadata(), Map), contextParameterProcessor.buildExecutionContext(execution), true)
execution.setMetadata(evaluatedPipelineMetadata)

List<Map<String, Object>> spelProcessedNotifications = execution.notifications.collect({
contextParameterProcessor.process(it, contextParameterProcessor.buildExecutionContext(execution), true)
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,56 @@ class EchoNotifyingPipelineExecutionListenerSpec extends Specification {
0 * _
}

void "evaluate SpEL that references pipeline.metadata using beforeExecution"(){
given:
def pipelineMetadata = ['mychainId': '53dd5f3e-fb0d-4c48-92ee-fda7f0eca5e5']
def pipeline = PipelineExecutionImpl.newPipeline("myapp")
pipeline.setMetadata(pipelineMetadata)
def pipelineConfiguredNotification = [
when : ["pipeline.started", "pipeline.completed"],
type : "slack",
address: 'spinnaker',
customData: [chainId: "\${execution.metadata.mychainId}"]
]
pipeline.notifications.add(pipelineConfiguredNotification)

when:
echoListener.beforeExecution(null, pipeline)

then:
pipeline.notifications.size() == 1
pipeline.notifications[0].when.containsAll(["pipeline.started", "pipeline.completed"])
pipeline.notifications[0].customData.chainId == pipelineMetadata.mychainId
1 * front50Service.getApplicationNotifications("myapp") >> new ApplicationNotifications()
1 * echoService.recordEvent(_)
0 * _
}

void "evaluate SpEL that references pipeline.metadata using afterExecution"(){
given:
def pipelineMetadata = ['mychainId': '53dd5f3e-fb0d-4c48-92ee-fda7f0eca5e5']
def pipeline = PipelineExecutionImpl.newPipeline("myapp")
pipeline.setMetadata(pipelineMetadata)
def pipelineConfiguredNotification = [
when : ["pipeline.started", "pipeline.completed"],
type : "slack",
address: 'spinnaker',
customData: [chainId: "\${execution.metadata.mychainId}"]
]
pipeline.notifications.add(pipelineConfiguredNotification)

when:
echoListener.afterExecution(null, pipeline, ExecutionStatus.SUCCEEDED, true)

then:
pipeline.notifications.size() == 1
pipeline.notifications[0].when.containsAll(["pipeline.started", "pipeline.completed"])
pipeline.notifications[0].customData.chainId == pipelineMetadata.mychainId
1 * front50Service.getApplicationNotifications("myapp") >> new ApplicationNotifications()
1 * echoService.recordEvent(_)
0 * _
}

void "handles case where no notifications are present"() {
given:
def pipeline = PipelineExecutionImpl.newPipeline("myapp")
Expand Down

0 comments on commit e2a3499

Please sign in to comment.