8000 Feature/aws sam support (#146) · classmethod/gradle-aws-plugin@1ed4b07 · GitHub
[go: up one dir, main page]

Skip to content

Commit 1ed4b07

Browse files
authored
Feature/aws sam support (#146)
Feature/aws sam support
2 parents 04b37fc + 8880bb7 commit 1ed4b07

12 files changed

+695
-132
lines changed

src/main/java/jp/classmethod/aws/gradle/cloudformation/AmazonCloudFormationCreateChangeSetTask.java

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@
3333
import org.gradle.api.tasks.TaskAction;
3434

3535
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
36+
import com.amazonaws.services.cloudformation.model.AmazonCloudFormationException;
3637
import com.amazonaws.services.cloudformation.model.Capability;
38+
import com.amazonaws.services.cloudformation.model.ChangeSetType;
3739
import com.amazonaws.services.cloudformation.model.CreateChangeSetRequest;
3840
import com.amazonaws.services.cloudformation.model.CreateChangeSetResult;
3941
import com.amazonaws.services.cloudformation.model.DescribeStacksRequest;
@@ -76,7 +78,8 @@ public class AmazonCloudFormationCreateChangeSetTask extends ConventionTask {
7678
@Getter
7779
@Setter
7880
private List<String> stableStatuses = Arrays.asList(
79-
"CREATE_COMPLETE", "ROLLBACK_COMPLETE", "UPDATE_COMPLETE", "UPDATE_ROLLBACK_COMPLETE");
81+
"CREATE_COMPLETE", "ROLLBACK_COMPLETE", "UPDATE_COMPLETE", "UPDATE_ROLLBACK_COMPLETE",
82+
"REVIEW_IN_PROGRESS");
8083

8184

8285
public AmazonCloudFormationCreateChangeSetTask() {
@@ -98,17 +101,26 @@ public void createChangeSet() throws InterruptedException, IOException {
98101
getProject().getExtensions().getByType(AmazonCloudFormationPluginExtension.class);
99102
AmazonCloudFormation cfn = ext.getClient();
100103

101-
DescribeStacksResult describeStackResult =
102-
cfn.describeStacks(new DescribeStacksRequest().withStackName(stackName));
103-
Stack stack = describeStackResult.getStacks().get(0);
104-
if (stableStatuses.contains(stack.getStackStatus())) {
105-
createChangeSet(cfn);
106-
} else {
107-
throw new GradleException("invalid status for create change set: " + stack.getStackStatus());
104+
try {
105+
DescribeStacksResult describeStackResult =
106+< 6D40 div class="diff-text-inner"> cfn.describeStacks(new DescribeStacksRequest().withStackName(stackName));
107+
Stack stack = describeStackResult.getStacks().get(0);
108+
if (stableStatuses.contains(stack.getStackStatus())) {
109+
createChangeSet(cfn, ChangeSetType.UPDATE);
110+
} else {
111+
throw new GradleException("invalid status for create change set: " + stack.getStackStatus());
112+
}
113+
} catch (AmazonCloudFormationException e) {
114+
if (e.getMessage().contains("does not exist")) {
115+
// stack does not exist; create change set in CREATE mode
116+
createChangeSet(cfn, ChangeSetType.CREATE);
117+
} else {
118+
throw new GradleException("Failed to describe stack " + stackName, e);
119+
}
108120
}
109121
}
110122

111-
private void createChangeSet(AmazonCloudFormation cfn) throws IOException {
123+
private void createChangeSet(AmazonCloudFormation cfn, ChangeSetType changeSetType) throws IOException {
112124
// to enable conventionMappings feature
113125
String stackName = getStackName();
114126
String cfnTemplateUrl = getCfnTemplateUrl();
@@ -122,7 +134,8 @@ private void createChangeSet(AmazonCloudFormation cfn) throws IOException {
122134
.withChangeSetName(changeSetName)
123135
.withStackName(stackName)
124136
.withParameters(cfnStackParams)
125-
.withTags(cfnStackTags);
137+
.withTags(cfnStackTags)
138+
.withChangeSetType(changeSetType);
126139

127140
// If template URL is specified, then use it
128141
if (Strings.isNullOrEmpty(cfnTemplateUrl) == false) {

src/main/java/jp/classmethod/aws/gradle/cloudformation/AmazonCloudFormationExecuteChangeSetTask.java

Lines changed: 19 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import java.io.IOException;
1919
import java.util.Arrays;
20-
import java.util.Comparator;
2120
import java.util.List;
2221
import java.util.Optional;
2322

@@ -31,11 +30,11 @@
3130
import com.amazonaws.AmazonServiceException;
3231
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
3332
import com.amazonaws.services.cloudformation.model.ChangeSetSummary;
33+
import com.amazonaws.services.cloudformation.model.DescribeChangeSetRequest;
34+
import com.amazonaws.services.cloudformation.model.DescribeChangeSetResult;
3435
import com.amazonaws.services.cloudformation.model.DescribeStacksRequest;
3536
import com.amazonaws.services.cloudformation.model.DescribeStacksResult;
3637
import com.amazonaws.services.cloudformation.model.ExecuteChangeSetRequest;
37-
import com.amazonaws.services.cloudformation.model.ListChangeSetsRequest;
38-
import com.amazonaws.services.cloudformation.model.ListChangeSetsResult;
3938
import com.amazonaws.services.cloudformation.model.Stack;
4039

4140
public class AmazonCloudFormationExecuteChangeSetTask extends ConventionTask {
@@ -45,7 +44,8 @@ public class AmazonCloudFormationExecuteChangeSetTask extends ConventionTask {
4544
String stackName;
4645

4746
private List<String> stableStatuses = Arrays.asList(
48-
"CREATE_COMPLETE", "ROLLBACK_COMPLETE", "UPDATE_COMPLETE", "UPDATE_ROLLBACK_COMPLETE");
47+
"CREATE_COMPLETE", "REVIEW_IN_PROGRESS", "ROLLBACK_COMPLETE", "UPDATE_COMPLETE",
48+
"UPDATE_ROLLBACK_COMPLETE");
4949

5050

5151
public AmazonCloudFormationExecuteChangeSetTask() {
@@ -72,10 +72,16 @@ public void executeChangeSet() throws InterruptedException, IOException {
7272
Stack stack = describeStackResult.getStacks().get(0);
7373

7474
if (stableStatuses.contains(stack.getStackStatus())) {
75-
Optional<ChangeSetSummary> summary = getLatestChangeSetSummary(cfn);
75+
Optional<ChangeSetSummary> summary = new ChangeSetFetcher(cfn).getLatestChangeSetSummary(stackName);
76+
7677
String changeSetName = summary
7778
.orElseThrow(() -> new GradleException("ChangeSet for stack " + stackName + " was not found."))
7879
.getChangeSetName();
80+
81+
if (isNoUpdateRequired(cfn, stackName, changeSetName)) {
82+
return;
83+
}
84+
7985
ExecuteChangeSetRequest req = new ExecuteChangeSetRequest()
8086
.withStackName(stackName)
8187
.withChangeSetName(changeSetName);
@@ -87,32 +93,21 @@ public void executeChangeSet() throws InterruptedException, IOException {
8793
} catch (AmazonServiceException e) {
8894
if (e.getMessage().contains("does not exist")) {
8995
getLogger().warn("stack {} not found", stackName);
90-
} else if (e.getMessage().contains("No updates are to be performed.")) {
91-
getLogger().trace(e.getMessage());
9296
} else {
9397
throw e;
9498
}
9599
}
96-
97100
}
98101

99-
/**
100-
*
101-
* Return the latest ChangeSet Summary for the specified CloudFormation stack.
102-
* @param cfn AmazonCloudFormation
103-
* @return Optional
104-
*/
105-
private Optional<ChangeSetSummary> getLatestChangeSetSummary(AmazonCloudFormation cfn) {
102+
private boolean isNoUpdateRequired(AmazonCloudFormation cfn, String stackName, String changeSetName) {
103+
DescribeChangeSetRequest describeChangeSetRequest =
104+
new DescribeChangeSetRequest().withChangeSetName(changeSetName).withStackName(stackName);
105+
DescribeChangeSetResult describeChangeSetResult = cfn.describeChangeSet(describeChangeSetRequest);
106106

107-
ListChangeSetsResult changeSetsResult =
108-
cfn.listChangeSets(new ListChangeSetsRequest().withStackName(getStackName()));
109-
List<ChangeSetSummary> changeSetSummaries = changeSetsResult.getSummaries();
110-
if (changeSetSummaries.isEmpty()) {
111-
return Optional.empty();
107+
if ("FAILED".equals(describeChangeSetResult.getStatus()) && describeChangeSetResult.getChanges().isEmpty()) {
108+
getLogger().info("No updates to be performed.");
109+
return true;
112110
}
113-
114-
changeSetSummaries.sort(Comparator.comparing(ChangeSetSummary::getCreationTime).reversed());
115-
return Optional.of(changeSetSummaries.get(0));
111+
return false;
116112
}
117-
118113
}

src/main/java/jp/classmethod/aws/gradle/cloudformation/AmazonCloudFormationPlugin.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public void apply(Project project) {
4545
applyTasks(project);
4646
}
4747

48+
//CHECKSTYLE:OFF
4849
private void applyTasks(Project project) { // NOPMD
4950
AmazonCloudFormationPluginExtension cfnExt =
5051
project.getExtensions().findByType(AmazonCloudFormationPluginExtension.class);
@@ -110,7 +111,7 @@ private void applyTasks(Project project) { // NOPMD
110111
task.dependsOn(awsCfnUploadTemplate);
111112
});
112113

113-
project.getTasks()
114+
AmazonCloudFormationCreateChangeSetTask awsCfnCreateChangeSet = project.getTasks()
114115
.create("awsCfnCreateChangeSet", AmazonCloudFormationCreateChangeSetTask.class, task -> {
115116
task.setDescription("Create cfn change set.");
116117
task.mustRunAfter(awsCfnUploadTemplate);
@@ -131,8 +132,28 @@ private void applyTasks(Project project) { // NOPMD
131132
task.conventionMapping("cfnTemplateFile", () -> cfnExt.getTemplateFile());
132133
});
133134

134-
project.getTasks()
135+
AmazonCloudFormationWaitStackStatusTask awsCfnWaitStackCompleteAfterCreateChangeSet = project.getTasks().create(
136+
"awsCfnWaitStackCompleteAfterCreateChangeSet", AmazonCloudFormationWaitStackStatusTask.class,
137+
task -> {
138+
task.setDescription(
139+
"Wait cfn stack for CREATE_COMPLETE, UPDATE_COMPLETE or REVIEW_IN_PROGRESS status.");
140+
task.mustRunAfter(awsCfnCreateChangeSet);
141+
task.setSuccessStatuses(Arrays.asList("CREATE_COMPLETE", "UPDATE_COMPLETE", "REVIEW_IN_PROGRESS"));
142+
task.conventionMapping("stackName", () -> cfnExt.getStackName());
143+
});
144+
145+
AmazonCloudFormationWaitChangeSetStatusTask awsCfnWaitCreateChangeSetComplete = project.getTasks().create(
146+
"awsWaitCreateChangeSetComplete", AmazonCloudFormationWaitChangeSetStatusTask.class,
147+
task -> {
148+
task.setDescription("Wait cfn change set for CREATE_COMPLETE status");
149+
task.mustRunAfter(awsCfnWaitStackCompleteAfterCreateChangeSet);
150+
task.setSuccessStatuses(Arrays.asList("CREATE_COMPLETE"));
151+
task.conventionMapping("stackName", () -> cfnExt.getStackName());
152+
});
153+
154+
AmazonCloudFormationExecuteChangeSetTask awsCfnExecuteChangeSet = project.getTasks()
135155
.create("awsCfnExecuteChangeSet", AmazonCloudFormationExecuteChangeSetTask.class, task -> {
156+
task.mustRunAfter(awsCfnWaitCreateChangeSetComplete);
136157
task.setDescription("execute latest cfn change set.");
137158
task.conventionMapping("stackName", () -> cfnExt.getStackName());
138159
});
@@ -148,6 +169,7 @@ private void applyTasks(Project project) { // NOPMD
148169
task -> {
149170
task.setDescription("Wait cfn stack for CREATE_COMPLETE or UPDATE_COMPLETE status.");
150171
task.mustRunAfter(awsCfnMigrateStack);
172+
task.mustRunAfter(awsCfnExecuteChangeSet);
151173
task.setSuccessStatuses(Arrays.asList("CREATE_COMPLETE", "UPDATE_COMPLETE"));
152174
task.conventionMapping("stackName", () -> cfnExt.getStackName());
153175
});
@@ -156,6 +178,14 @@ private void applyTasks(Project project) { // NOPMD
156178
.dependsOn(awsCfnMigrateStack, awsCfnWaitStackComplete)
157179
.setDescription("Create/Migrate cfn stack, and wait stack for CREATE_COMPLETE or UPDATE_COMPLETE status.");
158180

181+
project.getTasks().create("awsCfnDeploy")
182+
.dependsOn(awsCfnCreateChangeSet, awsCfnWaitStackCompleteAfterCreateChangeSet,
183+
awsCfnWaitCreateChangeSetComplete, awsCfnExecuteChangeSet,
184+
awsCfnWaitStackComplete)
185+
.setDescription(
186+
"Create and execute a change set, waiting for completion. Useful for deploying CloudFormation templates"
187+
+ "containing transforms.");
188+
159189
AmazonCloudFormationDeleteStackTask awsCfnDeleteStack =
160190
project.getTasks().create("awsCfnDeleteStack", AmazonCloudFormationDeleteStackTask.class, task -> {
161191
task.setDescription("Delete cfn stack.");
@@ -174,7 +204,24 @@ private void applyTasks(Project project) { // NOPMD
174204
project.getTasks().create("awsCfnDeleteStackAndWaitCompleted")
175205
.dependsOn(awsCfnDeleteStack, awsCfnWaitStackDeleted)
176206
.setDescription("Delete cfn stack, and wait stack for DELETE_COMPLETE status.");
207+
208+
project.getTasks().create("awsCfnUpdateStackTerminationProtection",
209+
AmazonCloudFormationStackTerminationProtectionTask.class, task -> {
210+
task.setDescription("Update CloudFormation stack termination protection");
211+
task.conventionMapping("stackName", () -> cfnExt.getStackName());
212+
task.conventionMapping("terminationProtected", () -> cfnExt.isTerminationProtected());
213+
});
214+
215+
project.getTasks().create("awsCfnSetStackPolicy",
216+
AmazonCloudFormationStackPolicyTask.class, task -> {
217+
task.setDescription("Set CloudFormation stack policy");
218+
task.conventionMapping("stackName", () -> cfnExt.getStackName());
219+
task.conventionMapping("cfnStackPolicyUrl", () -> cfnExt.getStackPolicyURL());
220+
task.conventionMapping("cfnStackPolicyFile", () -> cfnExt.getStackPolicyFile());
221+
});
222+
177223
}
224+
//CHECKSTYLE:ON
178225

179226
private String createKey(String name, Object version, String prefix) {
180227
String path = name.substring(FilenameUtils.getPrefix(name).length());

src/main/java/jp/classmethod/aws/gradle/cloudformation/AmazonCloudFormationPluginExtension.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ public class AmazonCloudFormationPluginExtension extends BaseRegionAwarePluginEx
110110
@Setter
111111
private Capability useCapabilityIam;
112112

113+
@Getter
114+
@Setter
115+
private boolean terminationProtected;
116+
113117

114118
public AmazonCloudFormationPluginExtension(Project project) {
115119
super(project, AmazonCloudFormationClient.class);
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2015-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package jp.classmethod.aws.gradle.cloudformation;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.util.Locale;
21+
22+
import lombok.Getter;
23+
import lombok.Setter;
24+
25+
import org.apache.commons.io.FileUtils;
26+
import org.gradle.api.internal.ConventionTask;
27+
import org.gradle.api.tasks.TaskAction;
28+
29+
import com.amazonaws.services.cloudformation.AmazonCloudFormation;
30+
import com.amazonaws.services.cloudformation.model.SetStackPolicyRequest;
31+
import com.google.common.base.Strings;
32+
33+
public class AmazonCloudFormationStackPolicyTask extends ConventionTask {
34+
35+
@Getter
36+
@Setter
37+
private String stackName;
38+
39+
@Getter
40+
@Setter
41+
private String cfnStackPolicyUrl;
42+
43+
@Getter
44+
@Setter
45+
private File cfnStackPolicyFile;
46+
47+
48+
public AmazonCloudFormationStackPolicyTask() {
49+
setDescription("Set CloudFormation policy");
50+
setGroup("AWS");
51+
}
52+
53+
@TaskAction
54+
public void setStackPolicy() throws IOException {
55+
AmazonCloudFormationPluginExtension ext =
56+
getProject().getExtensions().getByType(AmazonCloudFormationPluginExtension.class);
57+
AmazonCloudFormation cfn = ext.getClient();
58+
59+
String stackName = getStackName();
60+
String cfnStackPolicyUrl = getCfnStackPolicyUrl();
61+
File cfnStackPolicyFile = getCfnStackPolicyFile();
62+
63+
SetStackPolicyRequest request = new SetStackPolicyRequest().withStackName(stackName);
64+
65+
if (!Strings.isNullOrEmpty(cfnStackPolicyUrl)) {
66+
request.setStackPolicyURL(cfnStackPolicyUrl);
67+
getLogger().info(String.format(Locale.ENGLISH, "Setting stack policy for stack %s using URL %s", stackName,
68+
cfnStackPolicyUrl));
69+
70+
} else if (cfnStackPolicyFile != null) {
71+
request.setStackPolicyBody(
72+
FileUtils.readFileToString(cfnStackPolicyFile));
73+
getLogger().info(String.format(Locale.ENGLISH,
74+
"Setting stack policy for stack " + stackName + " using file " + cfnStackPolicyFile));
75+
76+
}
77+
78+
cfn.setStackPolicy(request);
79+
}
80+
}

0 commit comments

Comments
 (0)
0