See: Description
| Interface | Description |
|---|---|
| CallbackAdapter<T> |
Interface used to abstract the function of reporting back provisioning
progress to the handler caller
|
| CallChain |
This can be used by Read, Create, Update and Delete handlers when invoking
AWS services.
|
| CallChain.Callback<RequestT,ResponseT,ClientT,ModelT,CallbackT extends StdCallbackContext,ReturnT> |
All service calls made will use the same call back interface for handling
both exceptions as well as actual response received from the call.
|
| CallChain.Caller<RequestT,ClientT,ModelT,CallbackT extends StdCallbackContext> |
This Encapsulates the actual Call to the service that is being made via
caller.
|
| CallChain.Completed<RequestT,ResponseT,ClientT,ModelT,CallbackT extends StdCallbackContext> |
One the call sequence has completed successfully, this is called to provide
the progress event.
|
| CallChain.Exceptional<RequestT,ResponseT,ClientT,ModelT,CallbackT extends StdCallbackContext> |
This provide the handler with the option to provide an explicit exception
handler that would have service exceptions that was received.
|
| CallChain.ExceptionPropagate<RequestT,E extends Exception,ClientT,ModelT,CallbackT extends StdCallbackContext,ReturnT> |
When implementing this interface, developers can either propagate the
exception as is.
|
| CallChain.Initiator<ClientT,ModelT,CallbackT extends StdCallbackContext> |
Provides an API initiator interface that works for all API calls that need
conversion, retry-backoff strategy, common exception handling and more
against desired state of the resource and callback context.
|
| CallChain.RequestMaker<ClientT,ModelT,CallbackT extends StdCallbackContext> |
This performs the translate step between the ModelT properties and what is
needed for making the service call.
|
| CallChain.Stabilizer<RequestT,ResponseT,ClientT,ModelT,CallbackT extends StdCallbackContext> |
This provides an optional stabilization function to be incorporate before we
are done with the actual web service request.
|
| CallGraphNameGenerator<ModelT,RequestT,ClientT,CallbackT extends StdCallbackContext> | |
| Delay |
This interface defines the
Delay that you needed between invocations
of a specific call chain. |
| DelayFactory | |
| Logger | |
| ProxyClient<ClientT> |
This class provides a wrapper for the client and provides methods to inject
scoped credentials for each request context when invoking AWS services.
|
| Class | Description |
|---|---|
| AmazonWebServicesClientProxy |
This implements the proxying mechanism to inject appropriate scoped
credentials into a service call when making Amazon Webservice calls.
|
| CloudFormationCallbackAdapter<T> | |
| Credentials | |
| HandlerRequest<ResourceT,CallbackT> |
This interface describes the request object for the provisioning request
|
| HandlerResponse<ResourceT> |
This interface describes the response object for the provisioning request
|
| LoggerProxy | |
| MetricsPublisherProxy | |
| ProgressEvent<ResourceT,CallbackT> | |
| RequestContext<CallbackT> | |
| RequestData<ResourceT> | |
| ResourceHandlerRequest<T> |
This interface describes the request object for the provisioning request
passed to the implementor.
|
| ResourceHandlerTestPayload<ModelT,CallbackT> |
This POJO is for the test entrypoint that bypasses the wrapper for direct
testing.
|
| StabilizationData | |
| StdCallbackContext |
StdCallbackContext provide a mechanism that automatically provides the
memoization for retention and callback of request, responses, stabilize
handles during handler invocations.
|
| StdCallbackContext.Deserializer | |
| StdCallbackContext.Serializer |
| Enum | Description |
|---|---|
| HandlerErrorCode | |
| OperationStatus | |
| StabilizationMode |
The framework handles the following for developers:
Anatomy of an AWS Web Service call
Most AWS web service API calls follows a typical pattern shown below:
StdCallbackContext is prefixed
with this unique name for tracking E.g.
initiator.initiate("logs:CreateLogGroup") uses the AWS IAM action
name (recommended naming convention to understand permission in code) when
invoking CloudWatchLogs CreateLogGroup API. Developers can retrieve different
parts of this API call from
StdCallbackContext.callGraphs()
map. They can retrieve the CreatLogGroupRequest using
"logs.CreateLogGroup.request" key and the corresponding response
CreateLogGroupResponse using "logs.CreateLogGroup.response" key after
a successful API call is completed. Developers can use them as needed to get
data like ARNs from responses when needed.translate(translator::translatetocreaterequest) translates incoming
CFN resource model to CreateLogGroupRequest for making the API
call.(r, c) -> c.injectCredentialsAndInvokeV2(r, c.client()::createLogGroup))
The above code ensures that the right credentials are used to make the
service call. Developers can create the AWS services client statically and
re-use across all calls without worry. Developers can use response object
from the API call to update CloudFormation resource model.Typically we setup
ARN for the resource post creation. in Create Handlers. It is essential to
set these to communicate back to CloudFormation to indicate the handler did
successfully start creation and is in flight. Developers do not need to worry
about this handshake, the framework already takes care of this. Developers do
need to set primary identifier like ARNs if needed from response object to
update incoming resource model. This is shown in bold in code below
return initiator.initiate("networkmanager:CreateGlobalNetwork")
//
// Make the request object to create from the model
//
.translate(model ->
CreateGlobalNetworkRequest.builder()
.description(model.getDescription())
.tags(Utils.cfnTagsToSdkTags(model.getTags()))
.build())
//
// Make the call the create the global network. Delegate to framework to retry all errors and
// report all errors as appropriate including service quote exceeded and others.
//
.call((r, c) -> {
CreateGlobalNetworkResponse res = c.injectCredentialsAndInvokeV2(r, c.client()::createGlobalNetwork);
GlobalNetwork network = res.globalNetwork();
initiator.getResourceModel().setArn(network.globalNetworkArn());
initiator.getResourceModel().setId(network.globalNetworkId());;
return res;
})
//
// Check to see if Global Network is available to use directly from the response or
// stabilize as needed. Update model with Arn and Id as primary identifier on the model
// object to communicate to CloudFormation about progress
//
.stabilize((_request, _response, _client, _model, _context) -> {
GlobalNetworkState state = _response.globalNetwork().state();
return state == GlobalNetworkState.AVAILABLE ||
Utils.globalNetwork(_client, state.globalNetworkId()).state() == GlobalNetworkState.AVAILABLE;
}).progress();
initiator.initiate("networkmanager:DeleteGlobalNetwork")
//
// convert from ResourceModel to DeleteGlobalNetworkRequest
//
.translate(m ->
DeleteGlobalNetworkRequest.builder()
.globalNetworkId(m.getId())
.build())
//
// Make the call to delete the network
//
.call((r, c) -> {
try {
return c.injectCredentialsAndInvokeV2(r, c.client()::deleteGlobalNetwork);
} catch (ResourceNotFoundException e) {
// Informs CloudFormation that the resources was deleted already
throw new software.amazon.cloudformation.exceptions.ResourceNotFoundException(e);
}
})
//
// Wait for global network to transition to complete delete state, which is returned by a
// ResourceNotFoundException from describe call below.
//
.stabilize(
(_request, _response, _client, _model, _context) -> {
//
// if we successfully describe it it still exists!!!
//
try {
globalNetwork(_client, _model.getId());
} catch (ResourceNotFoundException e) {
return true;
}
return false;
}
)
.done(ignored -> ProgressEvent.success(null, context));
OperationStatus.IN_PROGRESS
indicates that we can the proceed to next part of API calls to make for
resource configuration. E.g. for CloudWatchLogs LogGroup we first create the
LogGroup, then we update retention policy, associated KMS key and finally
delegate to Read Handler to return the complete state for the resource
return
createLogGroup(initiator)
.then(event -> updateRetentionInDays(initiator, event))
.then(event -> associateKMSKey(initiator, event))
// delegate finally to ReadHandler to return complete resource state
.then(event -> new ReadHandler().handleRequest(proxy, request, event.getCallbackContext(), logger));
Usually the final step in the sequence returns
OperationStatus.SUCCESS. If any
of the steps in between has an error the chain will be skipped to return the
error with
OperationStatus.FAILED status
and an appropriate error message
software.amazon.cloudformation.proxy.ProgressEvent#getMessage() E.g. if
associateKMSKey had an error to associate KMS key for CloudWatchLogs to use,
the chain would exit with FAILED stauts and appropriate exception message.
Both OperationStatus.SUCCESS and
OperationStatus.FAILED are pivot
points in the chain that will skip the remainder of the chain.When to re-use rebinding functionality for the model
Rebinding the model is used when the model is immutable by design and we need to create a new instance of the model for each part in the chain. This is to pure for functional programming constructs. Below is an example for traversing list APIs to iterate over to find object of interest. For each iteration the new model must be rebound.
void discoverIfAlreadyExistsWithAlias() {
ListAliasesResponse aliases = ListAliasesResponse.builder().build();
final BiFunction<CallChain.Initiator<KmsClient, ListAliasesResponse, StdCallbackContext>,
Integer,
ProgressEvent<ListAliasesResponse, StdCallbackContext>> invoker =
(initiator_, iteration) ->
initiator_
.initiate("kms:ListAliases-" + iteration)
.translate(m -> ListAliasesRequest.builder().marker(m.nextMarker()).build())
.call((r, c) -> c.injectCredentialsAndInvokeV2(r, c.client()::listAliases))
.success();
int iterationCount = 0;
do {
CallChain.Initiator<KmsClient, ListAliasesResponse, StdCallbackContext> initiator =
this.initiator.rebindModel(aliases);
ProgressEvent<ListAliasesResponse, StdCallbackContext> result = invoker.apply(initiator, iterationCount);
if (!result.isSuccess()) {
throw new RuntimeException("Error retrieving key aliases " + result.getMessage());
}
aliases = result.getResourceModel();
AliasListEntry entry = aliases.aliases().stream().filter(e -> e.aliasName().equals(KEY_ALIAS)).findFirst()
.orElse(null);
if (entry != null) {
kmsKeyId = entry.targetKeyId();
aliasArn = entry.aliasArn();
break;
}
if (aliases.nextMarker() == null) {
break;
}
++iterationCount;
} while (kmsKeyId == null);
}
In the above code
this.initiator.rebindModel(aliases)
to the latest batch of aliasaliases = result.getResourceModel() for
newly retrieved model to rebind for next loopCopyright © 2019 Amazon Web Services, Inc. All Rights Reserved.