Skip to content

Commit

Permalink
feat(orca) Place produced artifacts in global context
Browse files Browse the repository at this point in the history
  • Loading branch information
brujoand committed Jul 10, 2017
1 parent 5ca4a32 commit 46cc450
Show file tree
Hide file tree
Showing 8 changed files with 95 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright 2017 Schibsted ASA.
*
* 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.bakery.api

import groovy.transform.EqualsAndHashCode
import groovy.transform.ToString

@EqualsAndHashCode
@ToString(includeNames = true)
class Artifact {
String name
String type
String version
String reference
Map<String, String> metadata
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,6 @@ class Bake {
String ami
String amiName
String imageName
Artifact artifact
// TODO(duftler): Add a cloudProviderType property here? Will be straightforward once rosco is backed by redis.
}
Original file line number Diff line number Diff line change
Expand Up @@ -112,19 +112,25 @@ class BakeStage implements BranchingStageDefinitionBuilder, RestartableStage {
def bakeInitializationStages = stage.execution.stages.findAll {
it.parentStageId == stage.parentStageId && it.status == ExecutionStatus.RUNNING
}

def imageProducingStages = stage.execution.stages.findAll {
it.type == PIPELINE_CONFIG_TYPE && bakeInitializationStages*.id.contains(it.parentStageId) && (it.context.ami || it.context.imageId)
}
def globalContext = [
deploymentDetails: stage.execution.stages.findAll {
it.type == PIPELINE_CONFIG_TYPE && bakeInitializationStages*.id.contains(it.parentStageId) && (it.context.ami || it.context.imageId)
}.collect { Stage bakeStage ->
deploymentDetails: imageProducingStages.collect { Stage bakeStage ->
def deploymentDetails = [:]
["ami", "imageId", "amiSuffix", "baseLabel", "baseOs", "refId", "storeType", "vmType", "region", "package", "cloudProviderType", "cloudProvider"].each {
if (bakeStage.context.containsKey(it)) {
deploymentDetails.put(it, bakeStage.context.get(it))
}
}

return deploymentDetails
},
artifacts: imageProducingStages.collect { Stage bakeStage ->
def artifact = [:]
if (bakeStage.context.containsKey("artifact")) {
artifact.put("artifact", bakeStage.context.get("artifact"))
}
return artifact
}
]
new TaskResult(ExecutionStatus.SUCCEEDED, [:], globalContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class CompletedBakeTask implements Task {
def bakeStatus = stage.context.status as BakeStatus
def bake = bakery.lookupBake(region, bakeStatus.resourceId).toBlocking().first()
// This treatment of ami allows both the aws and gce bake results to be propagated.
def results = [ami: bake.ami ?: bake.imageName, imageId: bake.ami ?: bake.imageName]
def results = [ami: bake.ami ?: bake.imageName, imageId: bake.ami ?: bake.imageName, artifact: bake.artifact ?: ""]
/**
* TODO:
* It would be good to standardize on the key here. "imageId" works for all providers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.netflix.spinnaker.orca.bakery.pipeline

import com.netflix.spinnaker.orca.ExecutionStatus
import com.netflix.spinnaker.orca.bakery.api.Artifact
import com.netflix.spinnaker.orca.pipeline.model.Pipeline
import com.netflix.spinnaker.orca.pipeline.model.Stage
import groovy.time.TimeCategory
Expand Down Expand Up @@ -75,8 +76,9 @@ class BakeStageSpec extends Specification {

def "should include per-region stage contexts as global deployment details"() {
given:
Artifact artifact = new Artifact(type: "aws/image", reference: "ami-12345678")
def pipeline = Pipeline.builder()
.withStage(BakeStage.PIPELINE_CONFIG_TYPE, "Bake", ["ami": 1])
.withStage(BakeStage.PIPELINE_CONFIG_TYPE, "Bake", ["ami": 1, "artifact": artifact])
.withStage(BakeStage.PIPELINE_CONFIG_TYPE, "Bake", ["ami": 2])
.withStage(BakeStage.PIPELINE_CONFIG_TYPE, "Bake", ["ami": 3])
.build()
Expand All @@ -95,6 +97,9 @@ class BakeStageSpec extends Specification {
taskResult.globalOutputs == [
deploymentDetails: [
["ami": 1], ["ami": 2], ["ami": 3]
],
artifacts: [
["artifact":artifact], [:], [:]
]
]
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import com.netflix.frigga.Names
import com.netflix.spinnaker.orca.ExecutionStatus
import com.netflix.spinnaker.orca.RetryableTask
import com.netflix.spinnaker.orca.TaskResult
import com.netflix.spinnaker.orca.bakery.api.Artifact
import com.netflix.spinnaker.orca.clouddriver.OortService
import com.netflix.spinnaker.orca.clouddriver.pipeline.servergroup.support.Location
import com.netflix.spinnaker.orca.clouddriver.tasks.AbstractCloudProviderAwareTask
Expand Down Expand Up @@ -227,10 +228,23 @@ class FindImageFromClusterTask extends AbstractCloudProviderAwareTask implements
}
}.flatten()

List<Artifact> artifacts = deploymentDetails.collect {
new Artifact(
name: it.get("imageName"),
type: it.get("cloudProvider"),
version: null,
reference: it.get("imageId"),
metadata: [
"refId": it.get("refId"),
"sourceServerGroup": it.get("sourceServerGroup")
]
)
}
return new TaskResult(ExecutionStatus.SUCCEEDED, [
amiDetails: deploymentDetails
], [
deploymentDetails: deploymentDetails
deploymentDetails: deploymentDetails,
artifacts: artifacts
])
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,18 @@

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

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.RetryableTask;
import com.netflix.spinnaker.orca.TaskResult;
import com.netflix.spinnaker.orca.bakery.api.Artifact;
import com.netflix.spinnaker.orca.clouddriver.tasks.AbstractCloudProviderAwareTask;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import org.springframework.beans.factory.annotation.Autowired;
Expand Down Expand Up @@ -53,13 +56,33 @@ public TaskResult execute(Stage stage) {
throw new IllegalStateException("Could not find tagged image for package: " + stageData.packageName + " and tags: " + stageData.tags);
}

List<Artifact> artifacts = new ArrayList<>();
imageDetails.forEach(imageDetail -> artifacts.add(generateArtifactFrom(imageDetail, cloudProvider)));

Map<String, Object> globalOutputs = new HashMap<>();
globalOutputs.put("deploymentDetails", imageDetails);
globalOutputs.put("artifacts", artifacts);

return new TaskResult(
ExecutionStatus.SUCCEEDED,
Collections.singletonMap("amiDetails", imageDetails),
Collections.singletonMap("deploymentDetails", imageDetails)
globalOutputs
);

}

private Artifact generateArtifactFrom(ImageFinder.ImageDetails imageDetails, String cloudProvider) {
Map<String, String> metadata = new HashMap<>();
ImageFinder.JenkinsDetails jenkinsDetails = imageDetails.getJenkins();
metadata.put("build_info_url", jenkinsDetails.get("host"));
metadata.put("build_number", jenkinsDetails.get("number"));

Artifact artifact = new Artifact();
artifact.setName(imageDetails.getImageName());
artifact.setReference(imageDetails.getImageId());
artifact.setType(cloudProvider);
artifact.setMetadata(metadata);
return artifact;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ import spock.lang.Subject
class FindImageFromTagTaskSpec extends Specification {

def imageFinder = Mock(ImageFinder)
def imageDetails = Mock(ImageFinder.ImageDetails)
Stage stage = new Stage<>(new Pipeline(), "", [packageName: 'myPackage', tags: ['foo':'bar']])

@Subject
def task = new FindImageFromTagsTask(imageFinders: [imageFinder])

def "Not finding an images should throw IllegalStateException"() {
def "Not finding images should throw IllegalStateException"() {
when:
task.execute(stage)

Expand All @@ -25,14 +26,17 @@ class FindImageFromTagTaskSpec extends Specification {
1 * imageFinder.getCloudProvider() >> 'aws'
}

def "Finding an images should set task state to SUCCEEDED"() {
def "Finding images should set task state to SUCCEEDED"() {
when:
def result = task.execute(stage)

then:
result.status == ExecutionStatus.SUCCEEDED

1 * imageFinder.getCloudProvider() >> 'aws'
1 * imageFinder.byTags(stage, stage.context.packageName, stage.context.tags) >> [[:]]
1 * imageFinder.byTags(stage, stage.context.packageName, stage.context.tags) >> [imageDetails]
1 * imageDetails.getImageName() >> "somename"
1 * imageDetails.getImageId() >> "someId"
1 * imageDetails.getJenkins() >> new ImageFinder.JenkinsDetails("somehost", "somename", "42")
}
}

0 comments on commit 46cc450

Please sign in to comment.