From cb603f9511d77ea355ae07a54338782013cf4fcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Le=C3=B3n?= Date: Mon, 7 Oct 2019 10:33:50 +0200 Subject: [PATCH 1/5] feat(cfn): delete CFN changeset if empty upon request This patch introduces the DeleteCloudFormationChangeSet async operation, so Orca can request to delete a CFN change set under some conditions (e.g. when it's empty, or after the change set has been executed). This will allow full control of the change set life cycle by Spinnaker. --- ...tionChangeSetAtomicOperationConverter.java | 43 ++++++++++++ ...eteCloudFormationChangeSetDescription.java | 28 ++++++++ ...loudFormationChangeSetAtomicOperation.java | 68 +++++++++++++++++++ ...angeSetAtomicOperationConverterSpec.groovy | 65 ++++++++++++++++++ ...rmationChangeSetAtomicOperationSpec.groovy | 65 ++++++++++++++++++ .../orchestration/AtomicOperations.java | 1 + 6 files changed, 270 insertions(+) create mode 100644 clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverter.java create mode 100644 clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/description/DeleteCloudFormationChangeSetDescription.java create mode 100644 clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java create mode 100644 clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverterSpec.groovy create mode 100644 clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverter.java b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverter.java new file mode 100644 index 00000000000..56759be688a --- /dev/null +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverter.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2019 Adevinta + * + * 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.clouddriver.aws.deploy.converters; + +import com.netflix.spinnaker.clouddriver.aws.AmazonOperation; +import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeleteCloudFormationChangeSetDescription; +import com.netflix.spinnaker.clouddriver.aws.deploy.ops.DeleteCloudFormationChangeSetAtomicOperation; +import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation; +import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperations; +import com.netflix.spinnaker.clouddriver.security.AbstractAtomicOperationsCredentialsSupport; +import java.util.Map; +import org.springframework.stereotype.Component; + +@AmazonOperation(AtomicOperations.DELETE_CLOUDFORMATION_CHANGESET) +@Component("deleteCloudFormationChangeSetDescription") +public class DeleteCloudFormationChangeSetAtomicOperationConverter + extends AbstractAtomicOperationsCredentialsSupport { + @Override + public AtomicOperation convertOperation(Map input) { + return new DeleteCloudFormationChangeSetAtomicOperation(convertDescription(input)); + } + + @Override + public DeleteCloudFormationChangeSetDescription convertDescription(Map input) { + DeleteCloudFormationChangeSetDescription converted = + getObjectMapper().convertValue(input, DeleteCloudFormationChangeSetDescription.class); + converted.setCredentials(getCredentialsObject((String) input.get("credentials"))); + return converted; + } +} diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/description/DeleteCloudFormationChangeSetDescription.java b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/description/DeleteCloudFormationChangeSetDescription.java new file mode 100644 index 00000000000..b28595ce2e5 --- /dev/null +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/description/DeleteCloudFormationChangeSetDescription.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019 Adevinta + * + * 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.clouddriver.aws.deploy.description; + +import lombok.Data; +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = false) +@Data +public class DeleteCloudFormationChangeSetDescription extends AbstractAmazonCredentialsDescription { + + private String stackName; + private String changeSetName; + private String region; +} diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java new file mode 100644 index 00000000000..7d500748a9b --- /dev/null +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019 Adevinta + * + * 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.clouddriver.aws.deploy.ops; + +import com.amazonaws.services.cloudformation.AmazonCloudFormation; +import com.amazonaws.services.cloudformation.model.*; +import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeleteCloudFormationChangeSetDescription; +import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider; +import com.netflix.spinnaker.clouddriver.data.task.Task; +import com.netflix.spinnaker.clouddriver.data.task.TaskRepository; +import com.netflix.spinnaker.clouddriver.orchestration.AtomicOperation; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; + +@Slf4j +public class DeleteCloudFormationChangeSetAtomicOperation implements AtomicOperation { + + private static final String BASE_PHASE = "DELETE_CLOUDFORMATION_CHANGESET"; + + @Autowired AmazonClientProvider amazonClientProvider; + + private DeleteCloudFormationChangeSetDescription description; + + public DeleteCloudFormationChangeSetAtomicOperation( + DeleteCloudFormationChangeSetDescription deployCloudFormationDescription) { + this.description = deployCloudFormationDescription; + } + + @Override + public Map operate(List priorOutputs) { + Task task = TaskRepository.threadLocalTask.get(); + AmazonCloudFormation amazonCloudFormation = + amazonClientProvider.getAmazonCloudFormation( + description.getCredentials(), description.getRegion()); + + DeleteChangeSetRequest deleteChangeSetRequest = + new DeleteChangeSetRequest() + .withStackName(description.getStackName()) + .withChangeSetName(description.getChangeSetName()); + try { + task.updateStatus(BASE_PHASE, "Deleting CloudFormation ChangeSet"); + amazonCloudFormation.deleteChangeSet(deleteChangeSetRequest); + } catch (AmazonCloudFormationException e) { + log.error( + "Error removing change set {} on stack {}", + description.getChangeSetName(), + description.getStackName()); + return Collections.emptyMap(); + } + return Collections.emptyMap(); + } +} diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverterSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverterSpec.groovy new file mode 100644 index 00000000000..c58ba5adc1c --- /dev/null +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/converters/DeleteCloudFormationChangeSetAtomicOperationConverterSpec.groovy @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2019 Adevinta + * + * 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.clouddriver.aws.deploy.converters + +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeleteCloudFormationChangeSetDescription +import com.netflix.spinnaker.clouddriver.aws.deploy.ops.DeleteCloudFormationChangeSetAtomicOperation +import com.netflix.spinnaker.clouddriver.aws.security.NetflixAmazonCredentials +import com.netflix.spinnaker.clouddriver.security.AccountCredentialsProvider +import spock.lang.Shared +import spock.lang.Specification + +class DeleteCloudFormationChangeSetAtomicOperationConverterSpec extends Specification { + + @Shared + ObjectMapper mapper = new ObjectMapper() + + @Shared + DeleteCloudFormationChangeSetAtomicOperationConverter converter + + def setupSpec() { + this.converter = new DeleteCloudFormationChangeSetAtomicOperationConverter(objectMapper: mapper) + def accountCredentialsProvider = Mock(AccountCredentialsProvider) + def mockCredentials = Mock(NetflixAmazonCredentials) + accountCredentialsProvider.getCredentials(_) >> mockCredentials + converter.accountCredentialsProvider = accountCredentialsProvider + } + + void "DeleteCloudFormationChangeSetConverter returns DeleteCloudFormationChangeSetDescription"() { + setup: + def input = [stackName : "stack", + changeSetName : "changeset", + region : "eu-west-1", + credentials : "credentials"] + + when: + DeleteCloudFormationChangeSetDescription description = converter.convertDescription(input) + + then: + description instanceof DeleteCloudFormationChangeSetDescription + description.stackName == "stack" + description.changeSetName == "changeset" + description.region == "eu-west-1" + + when: + def operation = converter.convertOperation(input) + + then: + operation instanceof DeleteCloudFormationChangeSetAtomicOperation + } +} diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy new file mode 100644 index 00000000000..c11afd1e6f0 --- /dev/null +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy @@ -0,0 +1,65 @@ +/* + * Copyright 2019 Adevinta, 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.clouddriver.aws.deploy.ops + +import com.amazonaws.services.cloudformation.AmazonCloudFormation +import com.amazonaws.services.cloudformation.model.* +import com.fasterxml.jackson.databind.ObjectMapper +import com.netflix.spinnaker.clouddriver.aws.TestCredential +import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeleteCloudFormationChangeSetDescription +import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeployCloudFormationDescription +import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider +import com.netflix.spinnaker.clouddriver.data.task.Task +import com.netflix.spinnaker.clouddriver.data.task.TaskRepository +import spock.lang.Specification +import spock.lang.Unroll + +class DeleteCloudFormationChangeSetAtomicOperationSpec extends Specification { + void setupSpec() { + TaskRepository.threadLocalTask.set(Mock(Task)) + } + + void "should build a DeleteChangeSetRequest and submit through aws client"() { + given: + def amazonClientProvider = Mock(AmazonClientProvider) + def amazonCloudFormation = Mock(AmazonCloudFormation) + def deleteChangeSetResult = Mock(DeleteChangeSetResult) + def op = new DeleteCloudFormationChangeSetAtomicOperation( + new DeleteCloudFormationChangeSetDescription( + [ + stackName: "stackTest", + changeSetName: "changeSetName", + region: "eu-west-1", + credentials: TestCredential.named("test") + ] + ) + ) + op.amazonClientProvider = amazonClientProvider + + when: + op.operate([]) + + then: + 1 * amazonClientProvider.getAmazonCloudFormation(_, _) >> amazonCloudFormation + 1 * amazonCloudFormation.deleteChangeSet(_) >> { DeleteChangeSetRequest request -> + assert request.getStackName() == "stackTest" + assert request.getChangeSetName() == "changeSetName" + deleteChangeSetResult + } + } + +} diff --git a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/AtomicOperations.java b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/AtomicOperations.java index 2dd4f4896c6..e9f438b80aa 100644 --- a/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/AtomicOperations.java +++ b/clouddriver-core/src/main/groovy/com/netflix/spinnaker/clouddriver/orchestration/AtomicOperations.java @@ -106,4 +106,5 @@ public final class AtomicOperations { // CloudFormation operations public static final String DEPLOY_CLOUDFORMATION_STACK = "deployCloudFormation"; + public static final String DELETE_CLOUDFORMATION_CHANGESET = "deleteCloudFormationChangeSet"; } From 37de452d3378e70e936d10bdd8aaf4e08a1f975a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Le=C3=B3n?= Date: Mon, 21 Oct 2019 10:44:12 +0200 Subject: [PATCH 2/5] Add some information about the error --- .../ops/DeleteCloudFormationChangeSetAtomicOperation.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java index 7d500748a9b..7c7b2078a98 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java @@ -58,9 +58,11 @@ public Map operate(List priorOutputs) { amazonCloudFormation.deleteChangeSet(deleteChangeSetRequest); } catch (AmazonCloudFormationException e) { log.error( - "Error removing change set {} on stack {}", - description.getChangeSetName(), - description.getStackName()); + "Error removing change set " + + description.getChangeSetName() + + " on stack " + + description.getStackName(), + e.getMessage()); return Collections.emptyMap(); } return Collections.emptyMap(); From 8f4d8511d9f3bbff9eb4731b8e5568cb0db97887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Le=C3=B3n?= Date: Tue, 22 Oct 2019 22:43:29 +0200 Subject: [PATCH 3/5] Update clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java Co-Authored-By: Mark Vulfson --- .../ops/DeleteCloudFormationChangeSetAtomicOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java index 7c7b2078a98..3b8ba8406e6 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java @@ -62,7 +62,7 @@ public Map operate(List priorOutputs) { + description.getChangeSetName() + " on stack " + description.getStackName(), - e.getMessage()); + e; return Collections.emptyMap(); } return Collections.emptyMap(); From c3bf76677c9b59785d67084b7a52fc5ce8e4f278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Le=C3=B3n?= Date: Tue, 22 Oct 2019 22:44:52 +0200 Subject: [PATCH 4/5] Missing parenthesis --- .../ops/DeleteCloudFormationChangeSetAtomicOperation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java index 3b8ba8406e6..41e3c7e9a4a 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java @@ -62,7 +62,7 @@ public Map operate(List priorOutputs) { + description.getChangeSetName() + " on stack " + description.getStackName(), - e; + e); return Collections.emptyMap(); } return Collections.emptyMap(); From c7cd1349d81479884881e3937a0e497146128f0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xavi=20Le=C3=B3n?= Date: Mon, 28 Oct 2019 12:40:00 +0100 Subject: [PATCH 5/5] Rethrow exception so it can be properly handled by orca --- ...loudFormationChangeSetAtomicOperation.java | 5 ++- ...rmationChangeSetAtomicOperationSpec.groovy | 37 +++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java index 41e3c7e9a4a..5e721c5ae31 100644 --- a/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java +++ b/clouddriver-aws/src/main/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperation.java @@ -15,6 +15,7 @@ */ package com.netflix.spinnaker.clouddriver.aws.deploy.ops; +import com.amazonaws.AmazonServiceException; import com.amazonaws.services.cloudformation.AmazonCloudFormation; import com.amazonaws.services.cloudformation.model.*; import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeleteCloudFormationChangeSetDescription; @@ -56,14 +57,14 @@ public Map operate(List priorOutputs) { try { task.updateStatus(BASE_PHASE, "Deleting CloudFormation ChangeSet"); amazonCloudFormation.deleteChangeSet(deleteChangeSetRequest); - } catch (AmazonCloudFormationException e) { + } catch (AmazonServiceException e) { log.error( "Error removing change set " + description.getChangeSetName() + " on stack " + description.getStackName(), e); - return Collections.emptyMap(); + throw e; } return Collections.emptyMap(); } diff --git a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy index c11afd1e6f0..7d94b659482 100644 --- a/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy +++ b/clouddriver-aws/src/test/groovy/com/netflix/spinnaker/clouddriver/aws/deploy/ops/DeleteCloudFormationChangeSetAtomicOperationSpec.groovy @@ -16,17 +16,15 @@ package com.netflix.spinnaker.clouddriver.aws.deploy.ops +import com.amazonaws.AmazonServiceException import com.amazonaws.services.cloudformation.AmazonCloudFormation import com.amazonaws.services.cloudformation.model.* -import com.fasterxml.jackson.databind.ObjectMapper import com.netflix.spinnaker.clouddriver.aws.TestCredential import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeleteCloudFormationChangeSetDescription -import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeployCloudFormationDescription import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider import com.netflix.spinnaker.clouddriver.data.task.Task import com.netflix.spinnaker.clouddriver.data.task.TaskRepository import spock.lang.Specification -import spock.lang.Unroll class DeleteCloudFormationChangeSetAtomicOperationSpec extends Specification { void setupSpec() { @@ -62,4 +60,37 @@ class DeleteCloudFormationChangeSetAtomicOperationSpec extends Specification { } } + void "should propagate exceptions when deleting the change set"() { + given: + def amazonClientProvider = Mock(AmazonClientProvider) + def amazonCloudFormation = Mock(AmazonCloudFormation) + def op = new DeleteCloudFormationChangeSetAtomicOperation( + new DeleteCloudFormationChangeSetDescription( + [ + stackName: "stackTest", + changeSetName: "changeSetName", + region: "eu-west-1", + credentials: TestCredential.named("test") + ] + ) + ) + op.amazonClientProvider = amazonClientProvider + def exception = new AmazonServiceException("error") + + when: + try { + op.operate([]) + } + catch (Exception e) { + e instanceof AmazonServiceException + } + + then: + 1 * amazonClientProvider.getAmazonCloudFormation(_, _) >> amazonCloudFormation + 1 * amazonCloudFormation.deleteChangeSet(_) >> { + throw exception + } + } + + }