Skip to content

Commit

Permalink
Aws organizations account idempotency (#90)
Browse files Browse the repository at this point in the history
* Implementation of Idempotency for Account Resource Handler

* Idempotency Implementatiom for Policy Resource Handler

* Revert " Implementation of Idempotency for Account Resource Handler"

This reverts commit 54ab944.

* Revert "Idempotency Implementatiom for Policy Resource Handler"

This reverts commit 86ed07e.

* Idempotency Implementatiom for Policy Resource Handler

* Revert "Idempotency Implementatiom for Policy Resource Handler"

This reverts commit cd1ee5d.

* Implementation of Idempotency for Account Resource Handler

* A small idempotency change
  • Loading branch information
ramsriu authored Jan 8, 2025
1 parent 6295bca commit d20c265
Show file tree
Hide file tree
Showing 3 changed files with 209 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public void setCurrentRetryAttempt(final AccountConstants.Action actionName, fin
}
// used in CREATE handler
private boolean isAccountCreated = false;
private boolean isPreExistenceCheckComplete = false;
private boolean isDidResourceAlreadyExist = false;
private String createAccountRequestId;
private String failureReason;
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
package software.amazon.organizations.account;

import org.apache.commons.collections4.CollectionUtils;
import software.amazon.awssdk.services.organizations.model.Account;
import software.amazon.awssdk.services.organizations.OrganizationsClient;
import software.amazon.awssdk.services.organizations.model.CreateAccountRequest;
import software.amazon.awssdk.services.organizations.model.CreateAccountResponse;
import software.amazon.awssdk.services.organizations.model.DescribeCreateAccountStatusResponse;
import software.amazon.awssdk.services.organizations.model.DuplicateAccountException;
import software.amazon.awssdk.services.organizations.model.ListAccountsRequest;
import software.amazon.awssdk.services.organizations.model.ListParentsRequest;
import software.amazon.awssdk.services.organizations.model.ListParentsResponse;
import software.amazon.awssdk.services.organizations.model.MoveAccountRequest;
Expand All @@ -17,6 +19,7 @@
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
import software.amazon.organizations.utils.OrgsLoggerWrapper;

import java.util.Optional;
import java.util.Set;

public class CreateHandler extends BaseHandlerStd {
Expand Down Expand Up @@ -47,28 +50,57 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
}

return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
.then(progress -> {
if (progress.getCallbackContext().isAccountCreated()) {
log.log(String.format("Account has already been created in previous handler invoke with account Id: [%s]. Skip create account.", model.getAccountId()));
return ProgressEvent.progress(model, callbackContext);
}
return awsClientProxy.initiate("AWS-Organizations-Account::CreateAccount", orgsClient, progress.getResourceModel(), progress.getCallbackContext())
.translateToServiceRequest(Translator::translateToCreateAccountRequest)
.makeServiceCall(this::createAccount)
.handleError((organizationsRequest, e, proxyClient1, model1, context) -> handleError(organizationsRequest, request, e, proxyClient1, model1, context, logger))
.done(CreateAccountResponse -> {
callbackContext.setCreateAccountRequestId(CreateAccountResponse.createAccountStatus().id());
logger.log(String.format("Successfully initiated new account creation request with CreateAccountRequestId [%s]", callbackContext.getCreateAccountRequestId()));
return ProgressEvent.progress(model, callbackContext);
});
}

)
.then(progress -> checkIfAccountExists(awsClientProxy, progress, orgsClient))
.then(progress -> {
if (progress.getCallbackContext().isPreExistenceCheckComplete() && progress.getCallbackContext().isDidResourceAlreadyExist()) {
return ProgressEvent.failed(model, callbackContext, HandlerErrorCode.AlreadyExists,
String.format("Account with email [%s] already exists.", model.getEmail()));
}
if (progress.getCallbackContext().isAccountCreated()) {
log.log(String.format("Account has already been created in previous handler invoke with account Id: [%s]. Skip create account.", model.getAccountId()));
return ProgressEvent.progress(model, callbackContext);
}
return awsClientProxy.initiate("AWS-Organizations-Account::CreateAccount", orgsClient, progress.getResourceModel(), progress.getCallbackContext())
.translateToServiceRequest(Translator::translateToCreateAccountRequest)
.makeServiceCall(this::createAccount)
.handleError((organizationsRequest, e, proxyClient1, model1, context) -> handleError(organizationsRequest, request, e, proxyClient1, model1, context, logger))
.done(CreateAccountResponse -> {
callbackContext.setCreateAccountRequestId(CreateAccountResponse.createAccountStatus().id());
logger.log(String.format("Successfully initiated new account creation request with CreateAccountRequestId [%s]", callbackContext.getCreateAccountRequestId()));
return ProgressEvent.progress(model, callbackContext);
});
})
.then(progress -> describeCreateAccountStatus(awsClientProxy, request, model, callbackContext, orgsClient, logger))
.then(progress -> moveAccount(awsClientProxy, request, model, callbackContext, orgsClient, logger))
.then(progress -> ProgressEvent.success(progress.getResourceModel(), progress.getCallbackContext()));
}

private ProgressEvent<ResourceModel, CallbackContext> checkIfAccountExists(
final AmazonWebServicesClientProxy awsClientProxy,
ProgressEvent<ResourceModel, CallbackContext> progress,
final ProxyClient<OrganizationsClient> orgsClient) {

ResourceModel model = progress.getResourceModel();

return awsClientProxy.initiate("AWS-Organizations-Account::ListAccounts", orgsClient, model, progress.getCallbackContext())
.translateToServiceRequest(resourceModel -> ListAccountsRequest.builder().build())
.makeServiceCall((listAccountsRequest, proxyClient) -> proxyClient.injectCredentialsAndInvokeV2(listAccountsRequest, proxyClient.client()::listAccounts))
.done((listAccountsRequest, listAccountsResponse, proxyClient, resourceModel, context) -> {
Optional<Account> existingAccount = listAccountsResponse.accounts().stream()
.filter(account -> account.email().equals(model.getEmail()))
.findFirst();

if (existingAccount.isPresent()) {
model.setAccountId(existingAccount.get().id());
context.setDidResourceAlreadyExist(true);
log.log(String.format("Failing PreExistenceCheck: Account with email [%s] already exists with Id: [%s]", model.getEmail(), model.getAccountId()));
}

context.setPreExistenceCheckComplete(true);
return ProgressEvent.progress(model, context);
});
}

protected ProgressEvent<ResourceModel, CallbackContext> describeCreateAccountStatus(
final AmazonWebServicesClientProxy awsClientProxy,
final ResourceHandlerRequest<ResourceModel> request,
Expand Down
Loading

0 comments on commit d20c265

Please sign in to comment.