-
Notifications
You must be signed in to change notification settings - Fork 48
Expand file tree
/
Copy pathStubUpdateHandler.java
More file actions
221 lines (176 loc) · 12.8 KB
/
StubUpdateHandler.java
File metadata and controls
221 lines (176 loc) · 12.8 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
package {{ package_name }};
// TODO: replace all usage of SdkClient with your service client type, e.g; YourServiceAsyncClient
// import software.amazon.awssdk.services.yourservice.YourServiceAsyncClient;
import software.amazon.awssdk.awscore.AwsResponse;
import software.amazon.awssdk.awscore.exception.AwsServiceException;
import software.amazon.awssdk.core.SdkClient;
import software.amazon.cloudformation.exceptions.CfnGeneralServiceException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ProxyClient;
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
public class UpdateHandler extends BaseHandlerStd {
private Logger logger;
private static final String ACCESS_DENIED_EXCEPTION_MESSAGE = "not authorized";
protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
final AmazonWebServicesClientProxy proxy,
final ResourceHandlerRequest<ResourceModel> request,
final CallbackContext callbackContext,
final ProxyClient<SdkClient> proxyClient,
final Logger logger) {
this.logger = logger;
final ResourceModel previousModel = request.getPreviousResourceState();
// TODO: Adjust Progress Chain according to your implementation
// https://github.com/aws-cloudformation/cloudformation-cli-java-plugin/blob/master/src/main/java/software/amazon/cloudformation/proxy/CallChain.java
return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
// STEP 1 [check if resource already exists]
// for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
// if target API does not support 'ResourceNotFoundException' then following check is required
.then(progress ->
// STEP 1.0 [initialize a proxy context]
// If your service API does not return ResourceNotFoundException on update requests against some identifier (e.g; resource Name)
// and instead returns a 200 even though a resource does not exist, you must first check if the resource exists here
// NOTE: If your service API throws 'ResourceNotFoundException' for update requests this method is not necessary
proxy.initiate("{{ call_graph }}::{{ operation }}::PreUpdateCheck", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
// STEP 1.1 [initialize a proxy context]
.translateToServiceRequest(Translator::translateToReadRequest)
// STEP 1.2 [TODO: make an api call]
.makeServiceCall((awsRequest, client) -> {
AwsResponse awsResponse = null;
// TODO: add custom read resource logic
// If describe request does not return ResourceNotFoundException, you must throw ResourceNotFoundException based on
// awsResponse values
logger.log(String.format("%s has successfully been read.", ResourceModel.TYPE_NAME));
return awsResponse;
})
.progress()
)
// STEP 2 [first update/stabilize progress chain - required for resource update]
.then(progress ->
// STEP 2.0 [initialize a proxy context]
// Implement client invocation of the update request through the proxyClient, which is already initialised with
// caller credentials, correct region and retry settings
proxy.initiate("{{ call_graph }}::{{ operation }}::first", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
// STEP 2.1 [TODO: construct a body of a request]
.translateToServiceRequest(Translator::translateToFirstUpdateRequest)
// STEP 2.2 [TODO: make an api call]
.makeServiceCall((awsRequest, client) -> {
AwsResponse awsResponse = null;
try {
// TODO: put your update resource code here
} catch (final AwsServiceException e) {
/*
* While the handler contract states that the handler must always return a progress event,
* you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
* Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
* to more specific error codes
*/
throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
}
logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME));
return awsResponse;
})
// STEP 2.3 [TODO: stabilize step is not necessarily required but typically involves describing the resource until it is in a certain status, though it can take many forms]
// stabilization step may or may not be needed after each API call
// for more information -> https://docs.aws.amazon.com/cloudformation-cli/latest/userguide/resource-type-test-contract.html
.stabilize((awsRequest, awsResponse, client, model, context) -> {
// TODO: put your stabilization code here
final boolean stabilized = true;
logger.log(String.format("%s [%s] update has stabilized: %s", ResourceModel.TYPE_NAME, model.getPrimaryIdentifier(), stabilized));
return stabilized;
})
.progress())
// If your resource is provisioned through multiple API calls, then the following pattern is required (and might take as many postUpdate callbacks as necessary)
// STEP 3 [second update/stabilize progress chain]
.then(progress ->
// STEP 3.0 [initialize a proxy context]
// If your resource is provisioned through multiple API calls, you will need to apply each subsequent update
// step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended.
proxy.initiate("{{ call_graph }}::{{ operation }}::second", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
// STEP 3.1 [TODO: construct a body of a request]
.translateToServiceRequest(Translator::translateToSecondUpdateRequest)
// STEP 3.2 [TODO: make an api call]
.makeServiceCall((awsRequest, client) -> {
AwsResponse awsResponse = null;
try {
// TODO: put your post update resource code here
} catch (final AwsServiceException e) {
/*
* While the handler contract states that the handler must always return a progress event,
* you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
* Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
* to more specific error codes
*/
throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
}
logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME));
return awsResponse;
})
.progress())
// If your resource supports tags, then the following pattern is required to handle stack level tags via soft-failing pattern
// STEP 4 [update stack level tags progress chain]
.then(progress -> {
// STEP 4.0 [initialize a proxy context]
// Stack level tag update should not force user but rather be optional, as it is possible that stack execution role will not have
// enough permissions to do so
// step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended.
ProgressEvent<ResourceModel, CallbackContext> event = proxy.initiate("{{ call_graph }}::{{ operation }}::stack-level-tags", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
// STEP 4.1 [TODO: construct a body of a request]
.translateToServiceRequest((model) -> Translator.translateToStackTagUpdateRequest(request.getPreviousResourceTags(), request.getDesiredResourceTags()))
// STEP 4.2 [TODO: make an api call]
.makeServiceCall((awsRequest, client) -> {
AwsResponse awsResponse = null;
try {
// TODO: put your post update resource code here
} catch (final AwsServiceException e) {
/*
* While the handler contract states that the handler must always return a progress event,
* you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
* Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
* to more specific error codes
*/
throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
}
logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME));
return awsResponse;
})
.progress();
// STEP 4.3 [TODO: check if event 1) failed 2) error code is access denied]
// if (event.isFailed() && ...) {
// return progress;
// }
return event;
})
// If your resource supports tags, then the following pattern is required to handle resource level tags
// STEP 5 [update resource level tags progress chain]
.then(progress ->
// STEP 5.0 [initialize a proxy context]
// Resource level tag update should force user to use right set of permissions
// step in a discrete call/stabilize chain to ensure the entire resource is provisioned as intended.
proxy.initiate("{{ call_graph }}::{{ operation }}::resource-level-tags", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
// STEP 5.1 [TODO: construct a body of a request]
.translateToServiceRequest((model) -> Translator.translateToResourceTagUpdateRequest(previousModel, model))
// STEP 5.2 [TODO: make an api call]
.makeServiceCall((awsRequest, client) -> {
AwsResponse awsResponse = null;
try {
// TODO: put your post update resource code here
} catch (final AwsServiceException e) {
/*
* While the handler contract states that the handler must always return a progress event,
* you may throw any instance of BaseHandlerException, as the wrapper map it to a progress event.
* Each BaseHandlerException maps to a specific error code, and you should map service exceptions as closely as possible
* to more specific error codes
*/
throw new CfnGeneralServiceException(ResourceModel.TYPE_NAME, e);
}
logger.log(String.format("%s has successfully been updated.", ResourceModel.TYPE_NAME));
return awsResponse;
})
.progress())
// STEP 6 [TODO: describe call/chain to return the resource model]
.then(progress -> new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger));
}
}