Skip to content

Commit

Permalink
feat(codebuild): Add AWS CodeBuild stage (spinnaker#3386)
Browse files Browse the repository at this point in the history
* feat(codebuild): Add AWS CodeBuild stage

* Fix broken test

Co-authored-by: Clare Liguori <[email protected]>
  • Loading branch information
Kaixiang-AWS and clareliguori authored Feb 4, 2020
1 parent 7bde8eb commit 5b0731b
Show file tree
Hide file tree
Showing 9 changed files with 471 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.netflix.spinnaker.orca.igor;

import com.netflix.spinnaker.kork.artifacts.model.Artifact;
import com.netflix.spinnaker.orca.igor.model.AwsCodeBuildExecution;
import com.netflix.spinnaker.orca.igor.model.GoogleCloudBuild;
import com.netflix.spinnaker.orca.igor.model.GoogleCloudBuildRepoSource;
import java.util.List;
Expand Down Expand Up @@ -87,6 +88,14 @@ GoogleCloudBuild runGoogleCloudBuildTrigger(
@Path("triggerId") String triggerId,
@Body GoogleCloudBuildRepoSource repoSource);

@POST("/codebuild/builds/start/{account}")
AwsCodeBuildExecution startAwsCodeBuild(
@Path("account") String account, @Body Map<String, Object> requestInput);

@GET("/codebuild/builds/{account}/{buildId}")
AwsCodeBuildExecution getAwsCodeBuildExecution(
@Path("account") String account, @Path("buildId") String buildId);

@GET("/delivery-config/manifest")
Map<String, Object> getDeliveryConfigManifest(
@Query("scmType") String repoType,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* 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.model;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.netflix.spinnaker.orca.ExecutionStatus;
import lombok.Data;
import lombok.Getter;

@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class AwsCodeBuildExecution {
private final String arn;
private final Status status;
private final AwsCodeBuildLogs logs;
private final String buildUrl;

@JsonCreator
public AwsCodeBuildExecution(
@JsonProperty("arn") String arn,
@JsonProperty("buildStatus") String buildStatus,
@JsonProperty("logs") AwsCodeBuildLogs logs) {
this.arn = arn;
this.status = Status.fromString(buildStatus);
this.buildUrl = getBuildUrl(arn);
this.logs = logs;
}

private String getBuildUrl(String arn) {
final String[] arnSplit = arn.split("/");
final String region = arnSplit[0].split(":")[3];
final String buildId = arnSplit[1];
final String project = buildId.split(":")[0];
return String.format(
"https://%s.console.aws.amazon.com/codesuite/codebuild/projects/%s/build/%s/log?region=%s",
region, project, buildId, region);
}

@Data
private static class AwsCodeBuildLogs {
private String deepLink;
private String s3DeepLink;
}

public enum Status {
IN_PROGRESS(ExecutionStatus.RUNNING),
SUCCEEDED(ExecutionStatus.SUCCEEDED),
FAILED(ExecutionStatus.TERMINAL),
FAULT(ExecutionStatus.TERMINAL),
TIMED_OUT(ExecutionStatus.TERMINAL),
STOPPED(ExecutionStatus.CANCELED),
UNKNOWN(ExecutionStatus.TERMINAL);

@Getter private ExecutionStatus executionStatus;

Status(ExecutionStatus executionStatus) {
this.executionStatus = executionStatus;
}

public static Status fromString(String status) {
try {
return valueOf(status);
} catch (NullPointerException | IllegalArgumentException e) {
return UNKNOWN;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* 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.model;

import lombok.Data;

@Data
public class AwsCodeBuildStageDefinition implements RetryableStageDefinition {
private String account;
private String projectName;
private AwsCodeBuildExecution buildInfo;
private int consecutiveErrors;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.pipeline;

import com.netflix.spinnaker.orca.igor.tasks.MonitorAwsCodeBuildTask;
import com.netflix.spinnaker.orca.igor.tasks.StartAwsCodeBuildTask;
import com.netflix.spinnaker.orca.pipeline.StageDefinitionBuilder;
import com.netflix.spinnaker.orca.pipeline.TaskNode;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class AwsCodeBuildStage implements StageDefinitionBuilder {
@Override
public void taskGraph(@Nonnull Stage stage, @Nonnull TaskNode.Builder builder) {
builder
.withTask("startAwsCodeBuildTask", StartAwsCodeBuildTask.class)
.withTask("monitorAwsCodeBuildTask", MonitorAwsCodeBuildTask.class);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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.orca.OverridableTimeoutRetryableTask;
import com.netflix.spinnaker.orca.TaskResult;
import com.netflix.spinnaker.orca.igor.IgorService;
import com.netflix.spinnaker.orca.igor.model.AwsCodeBuildExecution;
import com.netflix.spinnaker.orca.igor.model.AwsCodeBuildStageDefinition;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nonnull;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
@Slf4j
public class MonitorAwsCodeBuildTask extends RetryableIgorTask<AwsCodeBuildStageDefinition>
implements OverridableTimeoutRetryableTask {
@Getter protected long backoffPeriod = TimeUnit.SECONDS.toMillis(10);
@Getter protected long timeout = TimeUnit.HOURS.toMillis(8); // maximum build timeout

private final IgorService igorService;

@Override
@Nonnull
public TaskResult tryExecute(@Nonnull AwsCodeBuildStageDefinition stageDefinition) {
AwsCodeBuildExecution execution =
igorService.getAwsCodeBuildExecution(
stageDefinition.getAccount(), getBuildId(stageDefinition.getBuildInfo().getArn()));
Map<String, Object> context = new HashMap<>();
context.put("buildInfo", execution);
return TaskResult.builder(execution.getStatus().getExecutionStatus()).context(context).build();
}

@Override
@Nonnull
protected 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,54 @@
/*
* 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.orca.ExecutionStatus;
import com.netflix.spinnaker.orca.Task;
import com.netflix.spinnaker.orca.TaskResult;
import com.netflix.spinnaker.orca.igor.IgorService;
import com.netflix.spinnaker.orca.igor.model.AwsCodeBuildExecution;
import com.netflix.spinnaker.orca.igor.model.AwsCodeBuildStageDefinition;
import com.netflix.spinnaker.orca.pipeline.model.Stage;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nonnull;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;

@Component
@RequiredArgsConstructor
public class StartAwsCodeBuildTask implements Task {
private static final String PROJECT_NAME = "projectName";

private final IgorService igorService;

@Override
@Nonnull
public TaskResult execute(@Nonnull Stage stage) {
AwsCodeBuildStageDefinition stageDefinition = stage.mapTo(AwsCodeBuildStageDefinition.class);

Map<String, Object> requestInput = new HashMap<>();
requestInput.put(PROJECT_NAME, stageDefinition.getProjectName());

AwsCodeBuildExecution execution =
igorService.startAwsCodeBuild(stageDefinition.getAccount(), requestInput);

Map<String, Object> context = stage.getContext();
context.put("buildInfo", execution);
return TaskResult.builder(ExecutionStatus.SUCCEEDED).context(context).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* 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.pipeline

import com.netflix.spinnaker.orca.igor.tasks.StartAwsCodeBuildTask
import spock.lang.Specification

import static com.netflix.spinnaker.orca.test.model.ExecutionBuilder.stage

class AwsCodeBuildStageSpec extends Specification {
def ACCOUNT = "codebuild-account"
def PROJECT_NAME = "test"

def "should start a build"() {
given:
def awsCodeBuildStage = new AwsCodeBuildStage()

def stage = stage {
type = "awsCodeBuild"
context = [
account: ACCOUNT,
projectName: PROJECT_NAME,
]
}

when:
def tasks = awsCodeBuildStage.buildTaskGraph(stage)

then:
tasks.findAll {
it.implementingClass == StartAwsCodeBuildTask
}.size() == 1
}
}
Loading

0 comments on commit 5b0731b

Please sign in to comment.