Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pipeline/configuration): Updated pipeline level configuration that introduces a new property named metadata. #4808

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 == "[email protected]"
}

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: "[email protected]"]
}

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

then:
shouldContinue == false
stage.context.notifications[0].address == "[email protected]"
}

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
Loading