Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Aws organizations account idempotency #90

Merged
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;
Copy link
Contributor

@pns-amzn pns-amzn Jan 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDidResourceAlreadyExist -> didResourceAlreadyExist

Without this update, will isDidResourceAlreadyExist and setDidResourceAlreadyExist work correctly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes the Functionality of it doesn't have any affect(if it has any may be I would have caught in the unit tests), it is the name that looks a bit redundant may be not following the naming conventions in java properly, I followed because to maintain similarity with the other intializations

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
Loading