Skip to content

Commit

Permalink
feat(cfn): enable nested stacks for cloudformation changesets (#5187)
Browse files Browse the repository at this point in the history
Co-authored-by: Jared Stehler <jared.stehler@gmail.com>
  • Loading branch information
ajordens and jaredstehler committed Jan 13, 2021
1 parent c86a270 commit 3b7486d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,15 @@ class AwsConfigurationProperties {
final AlarmsConfig alarms = new AlarmsConfig()
}

@Canonical
static class CloudFormationConfig {
boolean changeSetsIncludeNestedStacks = false
}

@NestedConfigurationProperty
final ClientConfig client = new ClientConfig()
@NestedConfigurationProperty
final CleanupConfig cleanup = new CleanupConfig()
@NestedConfigurationProperty
final CloudFormationConfig cloudformation = new CloudFormationConfig()
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
import com.amazonaws.services.cloudformation.model.*;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.netflix.spinnaker.clouddriver.aws.AwsConfigurationProperties;
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;
Expand All @@ -39,6 +40,7 @@ public class DeployCloudFormationAtomicOperation implements AtomicOperation<Map>
private static final String NO_CHANGE_STACK_ERROR_MESSAGE = "No updates";

@Autowired AmazonClientProvider amazonClientProvider;
@Autowired AwsConfigurationProperties awsConfigurationProperties;

@Autowired
@Qualifier("amazonObjectMapper")
Expand Down Expand Up @@ -191,7 +193,9 @@ private String createChangeSet(
.withTags(tags)
.withTemplateBody(template)
.withCapabilities(capabilities)
.withChangeSetType(changeSetType);
.withChangeSetType(changeSetType)
.withIncludeNestedStacks(
awsConfigurationProperties.getCloudformation().getChangeSetsIncludeNestedStacks());
if (StringUtils.hasText(roleARN)) {
createChangeSetRequest.setRoleARN(roleARN);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.amazonaws.services.cloudformation.model.Tag
import com.amazonaws.services.cloudformation.model.UpdateStackRequest
import com.amazonaws.services.cloudformation.model.UpdateStackResult
import com.fasterxml.jackson.databind.ObjectMapper
import com.netflix.spinnaker.clouddriver.aws.AwsConfigurationProperties
import com.netflix.spinnaker.clouddriver.aws.TestCredential
import com.netflix.spinnaker.clouddriver.aws.deploy.description.DeployCloudFormationDescription
import com.netflix.spinnaker.clouddriver.aws.security.AmazonClientProvider
Expand Down Expand Up @@ -149,6 +150,75 @@ class DeployCloudFormationAtomicOperationSpec extends Specification {
def amazonClientProvider = Mock(AmazonClientProvider)
def amazonCloudFormation = Mock(AmazonCloudFormation)
def createChangeSetResult = Mock(CreateChangeSetResult)

def awsConfigurationProperties = new AwsConfigurationProperties()

def stackId = "stackId"
def op = new DeployCloudFormationAtomicOperation(
new DeployCloudFormationDescription(
[
stackName: "stackTest",
region: "eu-west-1",
templateBody: 'key: "value"',
roleARN: roleARN,
parameters: [ key: "value" ],
tags: [ key: "value" ],
capabilities: ["cap1", "cap2"],
credentials: TestCredential.named("test"),
isChangeSet: true,
changeSetName: "changeSetTest"
]
)
)
op.amazonClientProvider = amazonClientProvider
op.awsConfigurationProperties = awsConfigurationProperties
op.objectMapper = new ObjectMapper()

when:
op.operate([])

then:
1 * amazonClientProvider.getAmazonCloudFormation(_, _) >> amazonCloudFormation
1 * amazonCloudFormation.describeStacks(_) >> {
if (existingStack) {
new DescribeStacksResult().withStacks([new Stack().withStackId("stackId")] as Collection)
} else {
new DescribeStacksResult().withStacks([] as Collection)
}
}
1* amazonCloudFormation.createChangeSet(_) >> { CreateChangeSetRequest request ->
assert request.getStackName() == "stackTest"
assert request.getTemplateBody() == 'key: "value"'
assert request.getRoleARN() == expectedRoleARN
assert request.getParameters() == [ new Parameter().withParameterKey("key").withParameterValue("value") ]
assert request.getTags() == [ new Tag().withKey("key").withValue("value") ]
assert request.getCapabilities() == ["cap1", "cap2"]
assert request.getChangeSetName() == "changeSetTest"
assert request.getChangeSetType() == changeSetType
assert request.isIncludeNestedStacks() == false
createChangeSetResult
}
1 * createChangeSetResult.getStackId() >> stackId

where:
roleARN | expectedRoleARN | existingStack || changeSetType
"arn:aws:iam:123456789012:role/test" | "arn:aws:iam:123456789012:role/test" | true || ChangeSetType.UPDATE.toString()
"" | null | true || ChangeSetType.UPDATE.toString()
" " | null | true || ChangeSetType.UPDATE.toString()
"arn:aws:iam:123456789012:role/test" | "arn:aws:iam:123456789012:role/test" | true || ChangeSetType.UPDATE.toString()
"arn:aws:iam:123456789012:role/test" | "arn:aws:iam:123456789012:role/test" | false || ChangeSetType.CREATE.toString()
}

@Unroll
void "should build an CreateChangeSetRequest with includeNestedStacks if set"() {
given:
def amazonClientProvider = Mock(AmazonClientProvider)
def amazonCloudFormation = Mock(AmazonCloudFormation)
def createChangeSetResult = Mock(CreateChangeSetResult)

def awsConfigurationProperties = new AwsConfigurationProperties()
awsConfigurationProperties.cloudformation.changeSetsIncludeNestedStacks = true

def stackId = "stackId"
def op = new DeployCloudFormationAtomicOperation(
new DeployCloudFormationDescription(
Expand All @@ -167,6 +237,7 @@ class DeployCloudFormationAtomicOperationSpec extends Specification {
)
)
op.amazonClientProvider = amazonClientProvider
op.awsConfigurationProperties = awsConfigurationProperties
op.objectMapper = new ObjectMapper()

when:
Expand All @@ -190,6 +261,7 @@ class DeployCloudFormationAtomicOperationSpec extends Specification {
assert request.getCapabilities() == ["cap1", "cap2"]
assert request.getChangeSetName() == "changeSetTest"
assert request.getChangeSetType() == changeSetType
assert request.isIncludeNestedStacks() == true
createChangeSetResult
}
1 * createChangeSetResult.getStackId() >> stackId
Expand Down

0 comments on commit 3b7486d

Please sign in to comment.