Skip to content

Commit

Permalink
feat(artifacts): allow multiple artifacts in find artifact from execu…
Browse files Browse the repository at this point in the history
…tion stage
  • Loading branch information
maggieneterval committed Jun 21, 2019
1 parent 5b5464e commit 674b0d6
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 61 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2019 Google, 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.orca.clouddriver.tasks.artifacts;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact;
import com.netflix.spinnaker.orca.pipeline.persistence.ExecutionRepository.ExecutionCriteria;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Optional;
import lombok.Data;
import lombok.Getter;

@Getter
public class FindArtifactFromExecutionContext extends HashMap<String, Object> {
private final ExecutionOptions executionOptions;
private final List<ExpectedArtifact> expectedArtifacts;
private final String pipeline;

// There does not seem to be a way to auto-generate a constructor using our current version of
// Lombok (1.16.20) that
// Jackson can use to deserialize.
public FindArtifactFromExecutionContext(
@JsonProperty("executionOptions") ExecutionOptions executionOptions,
@JsonProperty("expectedArtifact") ExpectedArtifact expectedArtifact,
@JsonProperty("expectedArtifacts") List<ExpectedArtifact> expectedArtifacts,
@JsonProperty("pipeline") String pipeline) {
this.executionOptions = executionOptions;
// Previously, this stage accepted only one expected artifact
this.expectedArtifacts =
Optional.ofNullable(expectedArtifacts).orElse(Collections.singletonList(expectedArtifact));
this.pipeline = pipeline;
}

@Data
static class ExecutionOptions {
// Accept either 'succeeded' or 'successful' in the stage config. The front-end sets
// 'successful', but due to a bug
// this class was only looking for 'succeeded'. Fix this by accepting 'successful' but to avoid
// breaking anyone who
// discovered this bug and manually edited their stage to set 'succeeded', continue to accept
// 'succeeded'.
boolean succeeded;
boolean successful;

boolean terminal;
boolean running;

ExecutionCriteria toCriteria() {
List<String> statuses = new ArrayList<>();
if (succeeded || successful) {
statuses.add("SUCCEEDED");
}

if (terminal) {
statuses.add("TERMINAL");
}

if (running) {
statuses.add("RUNNING");
}

return new ExecutionCriteria().setStatuses(statuses);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,95 +17,47 @@

package com.netflix.spinnaker.orca.clouddriver.tasks.artifacts;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact;
import com.netflix.spinnaker.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.Task;
import com.netflix.spinnaker.orca.TaskResult;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.persistence.ExecutionRepository.ExecutionCriteria;
import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nonnull;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class FindArtifactFromExecutionTask implements Task {
public static final String TASK_NAME = "findArtifactFromExecution";

@Autowired ArtifactResolver artifactResolver;

@Autowired ObjectMapper objectMapper;
private final ArtifactResolver artifactResolver;

@Nonnull
@Override
public TaskResult execute(@Nonnull Stage stage) {
Map<String, Object> context = stage.getContext();
FindArtifactFromExecutionContext context = stage.mapTo(FindArtifactFromExecutionContext.class);
Map<String, Object> outputs = new HashMap<>();
String pipeline = (String) context.get("pipeline");
ExpectedArtifact expectedArtifact =
objectMapper.convertValue(context.get("expectedArtifact"), ExpectedArtifact.class);
ExecutionOptions executionOptions =
objectMapper.convertValue(context.get("executionOptions"), ExecutionOptions.class);
String pipeline = context.getPipeline();
List<ExpectedArtifact> expectedArtifacts = context.getExpectedArtifacts();
FindArtifactFromExecutionContext.ExecutionOptions executionOptions =
context.getExecutionOptions();

List<Artifact> priorArtifacts =
artifactResolver.getArtifactsForPipelineId(pipeline, executionOptions.toCriteria());

Artifact match =
artifactResolver.resolveSingleArtifact(expectedArtifact, priorArtifacts, null, false);

if (match == null) {
outputs.put(
"exception",
"No artifact matching " + expectedArtifact + " found among " + priorArtifacts);
return TaskResult.builder(ExecutionStatus.TERMINAL)
.context(new HashMap<>())
.outputs(outputs)
.build();
}
Set<Artifact> matchingArtifacts =
artifactResolver.resolveExpectedArtifacts(expectedArtifacts, priorArtifacts, null, false);

outputs.put("resolvedExpectedArtifacts", Collections.singletonList(expectedArtifact));
outputs.put("artifacts", Collections.singletonList(match));
outputs.put("resolvedExpectedArtifacts", expectedArtifacts);
outputs.put("artifacts", matchingArtifacts);

return TaskResult.builder(ExecutionStatus.SUCCEEDED).context(outputs).outputs(outputs).build();
}

@Data
private static class ExecutionOptions {
// Accept either 'succeeded' or 'successful' in the stage config. The front-end sets
// 'successful', but due to a bug
// this class was only looking for 'succeeded'. Fix this by accepting 'successful' but to avoid
// breaking anyone who
// discovered this bug and manually edited their stage to set 'succeeded', continue to accept
// 'succeeded'.
boolean succeeded;
boolean successful;

boolean terminal;
boolean running;

ExecutionCriteria toCriteria() {
List<String> statuses = new ArrayList<>();
if (succeeded || successful) {
statuses.add("SUCCEEDED");
}

if (terminal) {
statuses.add("TERMINAL");
}

if (running) {
statuses.add("RUNNING");
}

return new ExecutionCriteria().setStatuses(statuses);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2019 Google, 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.orca.tasks.artifacts

import com.netflix.spinnaker.kork.artifacts.model.Artifact
import com.netflix.spinnaker.kork.artifacts.model.ExpectedArtifact
import com.netflix.spinnaker.orca.TaskResult
import com.netflix.spinnaker.orca.clouddriver.tasks.artifacts.FindArtifactFromExecutionTask
import com.netflix.spinnaker.orca.pipeline.model.Execution
import com.netflix.spinnaker.orca.pipeline.model.Stage
import com.netflix.spinnaker.orca.pipeline.persistence.ExecutionRepository
import com.netflix.spinnaker.orca.pipeline.util.ArtifactResolver
import spock.lang.Specification
import spock.lang.Subject

class FindArtifactFromExecutionTaskSpec extends Specification {
def PIPELINE = "my pipeline"
def EXECUTION_CRITERIA = new ExecutionRepository.ExecutionCriteria().setStatuses(Collections.singletonList("SUCCEEDED"))
def ARTIFACT_A = new Artifact(type: "kubernetes/replicaSet")
def ARTIFACT_B = new Artifact(type: "kubernetes/configMap")

ArtifactResolver artifactResolver = Mock(ArtifactResolver)
Execution execution = Mock(Execution)

@Subject
FindArtifactFromExecutionTask task = new FindArtifactFromExecutionTask(artifactResolver)

def "finds a single artifact"() {
given:
def expectedArtifacts = [new ExpectedArtifact(matchArtifact: ARTIFACT_A)]
def pipelineArtifacts = [ARTIFACT_A, ARTIFACT_B]
Set resolvedArtifacts = [ARTIFACT_A]
def stage = new Stage(execution, "findArtifactFromExecution", [
executionOptions: [
succeeded: true
],
expectedArtifacts: expectedArtifacts,
pipeline: PIPELINE
])

when:
TaskResult result = task.execute(stage)

then:
1 * artifactResolver.getArtifactsForPipelineId(PIPELINE, EXECUTION_CRITERIA) >> pipelineArtifacts
1 * artifactResolver.resolveExpectedArtifacts(expectedArtifacts, pipelineArtifacts, null, false) >> resolvedArtifacts
result.context.resolvedExpectedArtifacts == expectedArtifacts
result.context.artifacts == resolvedArtifacts
}

def "finds multiple artifacts"() {
given:
def expectedArtifacts = [new ExpectedArtifact(matchArtifact: ARTIFACT_A), new ExpectedArtifact(matchArtifact: ARTIFACT_B)]
def pipelineArtifacts = [ARTIFACT_A, ARTIFACT_B]
Set resolvedArtifacts = [ARTIFACT_A, ARTIFACT_B]
def stage = new Stage(execution, "findArtifactFromExecution", [
executionOptions: [
succeeded: true
],
expectedArtifacts: expectedArtifacts,
pipeline: PIPELINE
])

when:
TaskResult result = task.execute(stage)

then:
1 * artifactResolver.getArtifactsForPipelineId(PIPELINE, EXECUTION_CRITERIA) >> pipelineArtifacts
1 * artifactResolver.resolveExpectedArtifacts(expectedArtifacts, pipelineArtifacts, null, false) >> resolvedArtifacts
result.context.resolvedExpectedArtifacts == expectedArtifacts
result.context.artifacts == resolvedArtifacts
}
}

0 comments on commit 674b0d6

Please sign in to comment.