Skip to content

Commit

Permalink
feat(codebuild): Bind artifacts produced by CodeBuild (#3431)
Browse files Browse the repository at this point in the history
AWS CodeBuild is able to produce multiple artifacts to S3 location
as defined by users in their project configuration. However, these
artifacts are not visible to downstream stages, since they are not
Spinnaker artifacts. The two tasks added in this PR will fetch S3
location from CodeBuild and then bind it to the stage output, which
allows downstream stages to consume it as needed.
  • Loading branch information
Kaixiang-AWS authored Feb 12, 2020
1 parent a9448d1 commit c7d2a62
Show file tree
Hide file tree
Showing 4 changed files with 151 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ AwsCodeBuildExecution startAwsCodeBuild(
AwsCodeBuildExecution getAwsCodeBuildExecution(
@Path("account") String account, @Path("buildId") String buildId);

@GET("/codebuild/builds/artifacts/{account}/{buildId}")
List<Artifact> getAwsCodeBuildArtifacts(
@Path("account") String account, @Path("buildId") String buildId);

@POST("/codebuild/builds/stop/{account}/{buildId}")
AwsCodeBuildExecution stopAwsCodeBuild(
@Path("account") String account, @Path("buildId") String buildId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@

import com.netflix.spinnaker.orca.CancellableStage;
import com.netflix.spinnaker.orca.TaskResult;
import com.netflix.spinnaker.orca.igor.tasks.GetAwsCodeBuildArtifactsTask;
import com.netflix.spinnaker.orca.igor.tasks.MonitorAwsCodeBuildTask;
import com.netflix.spinnaker.orca.igor.tasks.StartAwsCodeBuildTask;
import com.netflix.spinnaker.orca.igor.tasks.StopAwsCodeBuildTask;
import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder;
import com.netflix.spinnaker.orca.pipeline.TaskNode;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import com.netflix.spinnaker.orca.pipeline.tasks.artifacts.BindProducedArtifactsTask;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
Expand All @@ -40,7 +42,9 @@ public class AwsCodeBuildStage implements StageDefinitionBuilder, CancellableSta
public void taskGraph(@Nonnull Stage stage, @Nonnull TaskNode.Builder builder) {
builder
.withTask("startAwsCodeBuildTask", StartAwsCodeBuildTask.class)
.withTask("monitorAwsCodeBuildTask", MonitorAwsCodeBuildTask.class);
.withTask("monitorAwsCodeBuildTask", MonitorAwsCodeBuildTask.class)
.withTask("getAwsCodeBuildArtifactsTask", GetAwsCodeBuildArtifactsTask.class)
.withTask("bindProducedArtifacts", BindProducedArtifactsTask.class);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright 2020 Amazon.com, 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.igor.tasks;

import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.TaskResult;
import com.netflix.spinnaker.orca.igor.IgorService;
import com.netflix.spinnaker.orca.igor.model.AwsCodeBuildStageDefinition;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class GetAwsCodeBuildArtifactsTask extends RetryableIgorTask<AwsCodeBuildStageDefinition> {
private final IgorService igorService;

@Override
public @Nonnull TaskResult tryExecute(@Nonnull AwsCodeBuildStageDefinition stageDefinition) {
List<Artifact> artifacts =
igorService.getAwsCodeBuildArtifacts(
stageDefinition.getAccount(), getBuildId(stageDefinition.getBuildInfo().getArn()));
Map<String, List<Artifact>> outputs = Collections.singletonMap("artifacts", artifacts);
return TaskResult.builder(ExecutionStatus.SUCCEEDED).outputs(outputs).build();
}

@Override
public @Nonnull AwsCodeBuildStageDefinition mapStage(@Nonnull Stage stage) {
return stage.mapTo(AwsCodeBuildStageDefinition.class);
}

private String getBuildId(String arn) {
return arn.split("/")[1];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* Copyright 2020 Amazon.com, 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.igor.tasks

import com.netflix.spinnaker.kork.artifacts.model.Artifact
import com.netflix.spinnaker.orca.ExecutionStatus
import com.netflix.spinnaker.orca.TaskResult
import com.netflix.spinnaker.orca.igor.IgorService
import com.netflix.spinnaker.orca.pipeline.model.Execution
import com.netflix.spinnaker.orca.pipeline.model.Stage
import retrofit.RetrofitError
import spock.lang.Specification
import spock.lang.Subject

class GetAwsCodeBuildArtifactsTaskSpec extends Specification {
String ACCOUNT = "my-account"
String BUILD_ID = "test:c7715bbf-5c12-44d6-87ef-8149473e02f7"
String ARN = "arn:aws:codebuild:us-west-2:123456789012:build/$BUILD_ID"

Execution execution = Mock(Execution)
IgorService igorService = Mock(IgorService)

@Subject
GetAwsCodeBuildArtifactsTask task = new GetAwsCodeBuildArtifactsTask(igorService)

def "fetches artifacts from igor and returns success"() {
given:
def artifacts = [
Artifact.builder().reference("abc").name("abc").build(),
Artifact.builder().reference("def").name("def").build()
]
def stage = new Stage(execution, "awsCodeBuild", [
account: ACCOUNT,
buildInfo: [
arn: ARN
],
])

when:
TaskResult result = task.execute(stage)

then:
1 * igorService.getAwsCodeBuildArtifacts(ACCOUNT, BUILD_ID) >> artifacts
0 * igorService._
result.getStatus() == ExecutionStatus.SUCCEEDED
result.getOutputs().get("artifacts") == artifacts
}

def "task returns RUNNING when communcation with igor fails"() {
given:
def stage = new Stage(execution, "awsCodeBuild", [
account: ACCOUNT,
buildInfo: [
arn: ARN
],
])

when:
TaskResult result = task.execute(stage)

then:
1 * igorService.getAwsCodeBuildArtifacts(ACCOUNT, BUILD_ID) >> { throw stubRetrofitError() }
0 * igorService._
result.getStatus() == ExecutionStatus.RUNNING
}

def stubRetrofitError() {
return Stub(RetrofitError) {
getKind() >> RetrofitError.Kind.NETWORK
}
}
}

0 comments on commit c7d2a62

Please sign in to comment.