From 8422a0a08275e1ef15cbb9863b10b245339fbd4a Mon Sep 17 00:00:00 2001 From: Kaixiang-AWS Date: Mon, 3 Feb 2020 16:06:18 -0800 Subject: [PATCH] =?UTF-8?q?feat(codebuild):=20Add=20support=20for=20trigge?= =?UTF-8?q?ring=20and=20monitoring=20AWS=20CodeBu=E2=80=A6=20(#595)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(codebuild): Add support for triggering and monitoring AWS CodeBuild builds * Use STS AssumeRole instead of static credentials --- igor-web/igor-web.gradle | 1 + .../igor/codebuild/AwsCodeBuildAccount.java | 76 +++++++++++++++++++ .../AwsCodeBuildAccountRepository.java | 44 +++++++++++ .../codebuild/AwsCodeBuildController.java | 53 +++++++++++++ .../igor/codebuild/AwsCodeBuildMapper.java | 29 +++++++ .../codebuild/AwsCodeBuildRequestHandler.java | 58 ++++++++++++++ .../igor/config/AwsCodeBuildConfig.java | 55 ++++++++++++++ .../igor/config/AwsCodeBuildProperties.java | 35 +++++++++ .../AwsCodeBuildAccountRepositorySpec.groovy | 67 ++++++++++++++++ .../codebuild/AwsCodeBuildAccountSpec.groovy | 72 ++++++++++++++++++ .../codebuild/AwsCodeBuildMapperSpec.groovy | 33 ++++++++ .../AwsCodeBuildRequestHandlerSpec.groovy | 52 +++++++++++++ 12 files changed, 575 insertions(+) create mode 100644 igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccount.java create mode 100644 igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepository.java create mode 100644 igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildController.java create mode 100644 igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapper.java create mode 100644 igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandler.java create mode 100644 igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildConfig.java create mode 100644 igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildProperties.java create mode 100644 igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepositorySpec.groovy create mode 100644 igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountSpec.groovy create mode 100644 igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapperSpec.groovy create mode 100644 igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandlerSpec.groovy diff --git a/igor-web/igor-web.gradle b/igor-web/igor-web.gradle index d257a9246..25e0371b3 100644 --- a/igor-web/igor-web.gradle +++ b/igor-web/igor-web.gradle @@ -65,6 +65,7 @@ dependencies { implementation "org.springframework.security:spring-security-config" implementation "org.springframework.security:spring-security-web" + implementation "com.amazonaws:aws-java-sdk" implementation "com.google.apis:google-api-services-cloudbuild" implementation "com.google.apis:google-api-services-storage" implementation 'com.google.auth:google-auth-library-oauth2-http' diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccount.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccount.java new file mode 100644 index 000000000..5de4f7c76 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccount.java @@ -0,0 +1,76 @@ +/* + * 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.igor.codebuild; + +import com.amazonaws.auth.STSAssumeRoleSessionCredentialsProvider; +import com.amazonaws.services.codebuild.AWSCodeBuildClient; +import com.amazonaws.services.codebuild.AWSCodeBuildClientBuilder; +import com.amazonaws.services.codebuild.model.BatchGetBuildsRequest; +import com.amazonaws.services.codebuild.model.Build; +import com.amazonaws.services.codebuild.model.StartBuildRequest; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; +import java.util.Objects; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; + +/** Generates authenticated requests to AWS CodeBuild API for a single configured account */ +@RequiredArgsConstructor +public class AwsCodeBuildAccount { + private final AWSCodeBuildClient client; + + @Autowired AWSSecurityTokenServiceClient stsClient; + + public AwsCodeBuildAccount(String accountId, String assumeRole, String region) { + STSAssumeRoleSessionCredentialsProvider credentialsProvider = + new STSAssumeRoleSessionCredentialsProvider.Builder( + getRoleArn(accountId, assumeRole), "spinnaker-session") + .withStsClient(stsClient) + .build(); + + // TODO: Add client-side rate limiting to avoid getting throttled if necessary + this.client = + (AWSCodeBuildClient) + AWSCodeBuildClientBuilder.standard() + .withCredentials(credentialsProvider) + .withRequestHandlers(new AwsCodeBuildRequestHandler()) + .withRegion(region) + .build(); + } + + public Build startBuild(StartBuildRequest request) { + return client.startBuild(request).getBuild(); + } + + public Build getBuild(String buildId) { + return client.batchGetBuilds(new BatchGetBuildsRequest().withIds(buildId)).getBuilds().get(0); + } + + private String getRoleArn(String accountId, String assumeRole) { + String assumeRoleValue = Objects.requireNonNull(assumeRole, "assumeRole"); + if (!assumeRoleValue.startsWith("arn:")) { + /** + * GovCloud and China regions need to have the full arn passed because of differing formats + * Govcloud: arn:aws-us-gov:iam China: arn:aws-cn:iam + */ + assumeRoleValue = + String.format( + "arn:aws:iam::%s:%s", + Objects.requireNonNull(accountId, "accountId"), assumeRoleValue); + } + return assumeRoleValue; + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepository.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepository.java new file mode 100644 index 000000000..e41134616 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepository.java @@ -0,0 +1,44 @@ +/* + * 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.igor.codebuild; + +import com.netflix.spinnaker.kork.web.exceptions.NotFoundException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class AwsCodeBuildAccountRepository { + private final Map accounts = new HashMap<>(); + + public void addAccount(String name, AwsCodeBuildAccount service) { + accounts.put(name, service); + } + + public AwsCodeBuildAccount getAccount(String name) { + AwsCodeBuildAccount account = accounts.get(name); + if (account == null) { + throw new NotFoundException( + String.format("No AWS CodeBuild account with name %s is configured", name)); + } + return account; + } + + public List getAccountNames() { + return accounts.keySet().stream().sorted().collect(Collectors.toList()); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildController.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildController.java new file mode 100644 index 000000000..54f2e4685 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildController.java @@ -0,0 +1,53 @@ +/* + * 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.igor.codebuild; + +import com.amazonaws.services.codebuild.model.Build; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.*; + +@ConditionalOnProperty("codebuild.enabled") +@RestController +@RequiredArgsConstructor +@RequestMapping(value = "/codebuild") +public class AwsCodeBuildController { + private final AwsCodeBuildAccountRepository awsCodeBuildAccountRepository; + + @RequestMapping(value = "/accounts", method = RequestMethod.GET) + List getAccounts() { + return awsCodeBuildAccountRepository.getAccountNames(); + } + + @RequestMapping( + value = "/builds/start/{account}", + method = RequestMethod.POST, + consumes = MediaType.APPLICATION_JSON_VALUE) + Build startBuild(@PathVariable String account, @RequestBody Map requestBody) { + return awsCodeBuildAccountRepository + .getAccount(account) + .startBuild(AwsCodeBuildMapper.toStartBuildRequest(requestBody)); + } + + @RequestMapping(value = "/builds/{account}/{buildId}", method = RequestMethod.GET) + Build getBuild(@PathVariable String account, @PathVariable String buildId) { + return awsCodeBuildAccountRepository.getAccount(account).getBuild(buildId); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapper.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapper.java new file mode 100644 index 000000000..7abf24f77 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapper.java @@ -0,0 +1,29 @@ +/* + * 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.igor.codebuild; + +import com.amazonaws.services.codebuild.model.StartBuildRequest; +import java.util.Map; + +public class AwsCodeBuildMapper { + private static final String PROJECT_NAME = "projectName"; + + public static StartBuildRequest toStartBuildRequest(Map requestBody) { + String projectName = (String) requestBody.get(PROJECT_NAME); + return new StartBuildRequest().withProjectName(projectName); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandler.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandler.java new file mode 100644 index 000000000..a71700649 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandler.java @@ -0,0 +1,58 @@ +/* + * 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.igor.codebuild; + +import com.amazonaws.AmazonServiceException; +import com.amazonaws.AmazonWebServiceRequest; +import com.amazonaws.Request; +import com.amazonaws.Response; +import com.amazonaws.handlers.RequestHandler2; +import com.netflix.spinnaker.igor.exceptions.BuildJobError; +import com.netflix.spinnaker.security.AuthenticatedRequest; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class AwsCodeBuildRequestHandler extends RequestHandler2 { + @Override + public AmazonWebServiceRequest beforeMarshalling(AmazonWebServiceRequest request) { + final String userAgent = + String.format( + "spinnaker-user/%s spinnaker-executionId/%s", + AuthenticatedRequest.getSpinnakerUser().orElse("unknown"), + AuthenticatedRequest.getSpinnakerExecutionId().orElse("unknown")); + + final AmazonWebServiceRequest cloned = request.clone(); + + cloned.getRequestClientOptions().appendUserAgent(userAgent); + return super.beforeMarshalling(cloned); + } + + @Override + public void afterError(Request request, Response response, Exception e) { + if (e instanceof AmazonServiceException + && ((AmazonServiceException) e) + .getErrorType() + .equals(AmazonServiceException.ErrorType.Client)) { + log.warn(e.getMessage()); + throw new BuildJobError(e.getMessage()); + } else { + log.error(e.getMessage()); + throw new RuntimeException(e); + } + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildConfig.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildConfig.java new file mode 100644 index 000000000..e83a11207 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildConfig.java @@ -0,0 +1,55 @@ +/* + * 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.igor.config; + +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient; +import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClientBuilder; +import com.netflix.spinnaker.igor.codebuild.AwsCodeBuildAccount; +import com.netflix.spinnaker.igor.codebuild.AwsCodeBuildAccountRepository; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConditionalOnProperty("codebuild.enabled") +@EnableConfigurationProperties({AwsCodeBuildProperties.class}) +public class AwsCodeBuildConfig { + @Bean + AwsCodeBuildAccountRepository awsCodeBuildAccountRepository( + AwsCodeBuildProperties awsCodeBuildProperties) { + AwsCodeBuildAccountRepository accounts = new AwsCodeBuildAccountRepository(); + awsCodeBuildProperties + .getAccounts() + .forEach( + a -> { + AwsCodeBuildAccount account = + new AwsCodeBuildAccount(a.getAccountId(), a.getAssumeRole(), a.getRegion()); + accounts.addAccount(a.getName(), account); + }); + return accounts; + } + + @Bean + AWSSecurityTokenServiceClient awsSecurityTokenServiceClient() { + return (AWSSecurityTokenServiceClient) + AWSSecurityTokenServiceClientBuilder.standard() + .withCredentials(DefaultAWSCredentialsProviderChain.getInstance()) + .build(); + } +} diff --git a/igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildProperties.java b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildProperties.java new file mode 100644 index 000000000..108c0aab7 --- /dev/null +++ b/igor-web/src/main/java/com/netflix/spinnaker/igor/config/AwsCodeBuildProperties.java @@ -0,0 +1,35 @@ +/* + * 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.igor.config; + +import java.util.List; +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Data +@ConfigurationProperties(prefix = "codebuild") +public class AwsCodeBuildProperties { + private List accounts; + + @Data + public static class Account { + private String name; + private String region; + private String accountId; + private String assumeRole; + } +} diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepositorySpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepositorySpec.groovy new file mode 100644 index 000000000..375bf5162 --- /dev/null +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountRepositorySpec.groovy @@ -0,0 +1,67 @@ +/* + * 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.igor.codebuild + +import com.netflix.spinnaker.kork.web.exceptions.NotFoundException +import spock.lang.Specification + +class AwsCodeBuildAccountRepositorySpec extends Specification{ + AwsCodeBuildAccountRepository repository = new AwsCodeBuildAccountRepository() + AwsCodeBuildAccount account1 = Mock(AwsCodeBuildAccount) + AwsCodeBuildAccount account2 = Mock(AwsCodeBuildAccount) + + def "getAccount should return account if it exists in the repository"() { + given: + repository.addAccount("account1", account1) + repository.addAccount("account2", account2) + + when: + def outputAccount1 = repository.getAccount("account1") + def outputAccount2 = repository.getAccount("account2") + + then: + outputAccount1 == account1 + outputAccount2 == account2 + } + + def "getAccount should throw NotFoundException if account doesn't exist in the repository"() { + given: + repository.addAccount("account1", account1) + + when: + repository.getAccount("account2") + + then: + NotFoundException exception = thrown() + exception.getMessage() == "No AWS CodeBuild account with name account2 is configured" + } + + def "getAccountNames should return all accounts in the repository"() { + given: + repository.addAccount("account1", account1) + repository.addAccount("account2", account2) + + when: + String[] accounts = repository.getAccountNames() + + then: + accounts.length == 2 + accounts.contains("account1") + accounts.contains("account2") + } +} diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountSpec.groovy new file mode 100644 index 000000000..6b556b61b --- /dev/null +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildAccountSpec.groovy @@ -0,0 +1,72 @@ +/* + * 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.igor.codebuild + +import com.amazonaws.services.codebuild.AWSCodeBuildClient +import com.amazonaws.services.codebuild.model.BatchGetBuildsRequest +import com.amazonaws.services.codebuild.model.BatchGetBuildsResult +import com.amazonaws.services.codebuild.model.Build +import com.amazonaws.services.codebuild.model.StartBuildRequest +import com.amazonaws.services.codebuild.model.StartBuildResult +import spock.lang.Specification + +class AwsCodeBuildAccountSpec extends Specification { + AWSCodeBuildClient client = Mock(AWSCodeBuildClient) + AwsCodeBuildAccount awsCodeBuildAccount = new AwsCodeBuildAccount(client) + + def "startBuild starts a build and returns the result"() { + given: + def inputRequest = getStartBuildInput("test") + def mockOutputBuild = getOutputBuild("test") + + when: + def result = awsCodeBuildAccount.startBuild(inputRequest) + + then: + 1 * client.startBuild(inputRequest) >> new StartBuildResult().withBuild(mockOutputBuild) + result == mockOutputBuild + } + + def "getBuild returns the build details"() { + given: + String buildId = "test:c7715bbf-5c12-44d6-87ef-8149473e02f7" + def inputRequest = getBatchGetBuildsInput(buildId) + def mockOutputBuild = getOutputBuild("test") + + when: + def result = awsCodeBuildAccount.getBuild(buildId) + + then: + 1 * client.batchGetBuilds(inputRequest) >> new BatchGetBuildsResult().withBuilds(mockOutputBuild) + result == mockOutputBuild + } + + private static StartBuildRequest getStartBuildInput(String projectName) { + return new StartBuildRequest() + .withProjectName(projectName); + } + + private static BatchGetBuildsRequest getBatchGetBuildsInput(String... ids) { + return new BatchGetBuildsRequest() + .withIds(ids); + } + + private static Build getOutputBuild(String projectName) { + return new Build() + .withProjectName(projectName); + } +} diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapperSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapperSpec.groovy new file mode 100644 index 000000000..b449c1d4c --- /dev/null +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildMapperSpec.groovy @@ -0,0 +1,33 @@ +/* + * 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.igor.codebuild + +import spock.lang.Specification + +class AwsCodeBuildMapperSpec extends Specification { + def "toStartBuildRequest should convert a map to a StartBuildRequest"() { + given: + def request = new HashMap() + request.put("projectName", "test") + + when: + def startBuildRequest = AwsCodeBuildMapper.toStartBuildRequest(request) + + then: + startBuildRequest.getProjectName() == "test" + } +} diff --git a/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandlerSpec.groovy b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandlerSpec.groovy new file mode 100644 index 000000000..b4d34708b --- /dev/null +++ b/igor-web/src/test/groovy/com/netflix/spinnaker/igor/codebuild/AwsCodeBuildRequestHandlerSpec.groovy @@ -0,0 +1,52 @@ +/* + * 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.igor.codebuild + +import com.amazonaws.AmazonServiceException +import com.amazonaws.DefaultRequest +import com.amazonaws.Response +import com.amazonaws.services.codebuild.model.InvalidInputException +import com.amazonaws.services.codebuild.model.StartBuildRequest +import com.amazonaws.services.codebuild.model.StartBuildResult +import com.netflix.spinnaker.igor.exceptions.BuildJobError +import spock.lang.Specification + +class AwsCodeBuildRequestHandlerSpec extends Specification { + def handler = new AwsCodeBuildRequestHandler() + def request = new DefaultRequest(new StartBuildRequest(), "codebuild") + def response = new Response(new StartBuildResult(), null) + + def "should throw BuildJobError in case of a client exception"() { + when: + def exception = new InvalidInputException("err msg") + exception.setErrorType(AmazonServiceException.ErrorType.Client) + handler.afterError(request, response, exception) + then: + BuildJobError err = thrown() + err.getMessage().contains("err msg") + } + + def "should throw RuntimeException in case of other exceptions"() { + when: + def exception = new IllegalArgumentException("err msg") + handler.afterError(request, response, exception) + + then: + RuntimeException err = thrown() + err.getMessage().contains("err msg") + } +}