package software.amazon.rds.dbinstance; import java.time.Instant; import java.util.Collection; import java.util.Collections; import org.apache.commons.lang3.BooleanUtils; import com.amazonaws.util.StringUtils; import software.amazon.awssdk.services.ec2.Ec2Client; import software.amazon.awssdk.services.rds.RdsClient; import software.amazon.awssdk.services.rds.model.DBSnapshot; import software.amazon.awssdk.services.rds.model.SourceType; import software.amazon.awssdk.utils.ImmutableMap; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.ProgressEvent; import software.amazon.cloudformation.proxy.ProxyClient; import software.amazon.cloudformation.proxy.ResourceHandlerRequest; import software.amazon.rds.common.handler.Commons; import software.amazon.rds.common.handler.Events; import software.amazon.rds.common.handler.HandlerConfig; import software.amazon.rds.common.handler.HandlerMethod; import software.amazon.rds.common.handler.Tagging; import software.amazon.rds.common.logging.RequestLogger; import software.amazon.rds.common.request.RequestValidationException; import software.amazon.rds.common.request.ValidatedRequest; import software.amazon.rds.common.request.Validations; import software.amazon.rds.common.util.IdentifierFactory; import software.amazon.rds.dbinstance.client.ApiVersion; import software.amazon.rds.dbinstance.client.VersionedProxyClient; import software.amazon.rds.dbinstance.util.ResourceModelHelper; public class CreateHandler extends BaseHandlerStd { private static final IdentifierFactory instanceIdentifierFactory = new IdentifierFactory( STACK_NAME, RESOURCE_IDENTIFIER, RESOURCE_ID_MAX_LENGTH ); public CreateHandler() { this(DB_INSTANCE_HANDLER_CONFIG_36H); } public CreateHandler(final HandlerConfig config) { super(config); } @Override protected void validateRequest(final ResourceHandlerRequest request) throws RequestValidationException { super.validateRequest(request); validateDeletionPolicyForClusterInstance(request); Validations.validateTimestamp(request.getDesiredResourceState().getRestoreTime()); } private void validateDeletionPolicyForClusterInstance(final ResourceHandlerRequest request) throws RequestValidationException { if (isDBClusterMember(request.getDesiredResourceState()) && BooleanUtils.isTrue(request.getSnapshotRequested())) { throw new RequestValidationException(ILLEGAL_DELETION_POLICY_ERROR); } } protected ProgressEvent handleRequest( final AmazonWebServicesClientProxy proxy, final ValidatedRequest request, final CallbackContext callbackContext, final VersionedProxyClient rdsProxyClient, final VersionedProxyClient ec2ProxyClient, final RequestLogger logger ) { final ResourceModel model = request.getDesiredResourceState(); final Collection desiredRoles = model.getAssociatedRoles(); final boolean isMultiAZ = BooleanUtils.isTrue(model.getMultiAZ()); if (StringUtils.isNullOrEmpty(model.getDBInstanceIdentifier())) { model.setDBInstanceIdentifier(instanceIdentifierFactory.newIdentifier() .withStackId(request.getStackId()) .withResourceId(request.getLogicalResourceIdentifier()) .withRequestToken(request.getClientRequestToken()) .toString()); } final Tagging.TagSet allTags = Tagging.TagSet.builder() .systemTags(Tagging.translateTagsToSdk(request.getSystemTags())) .stackTags(Tagging.translateTagsToSdk(request.getDesiredResourceTags())) .resourceTags(Translator.translateTagsToSdk(request.getDesiredResourceState().getTags())) .build(); return ProgressEvent.progress(model, callbackContext) .then(progress -> Commons.execOnce(progress, () -> { if (ResourceModelHelper.isRestoreToPointInTime(progress.getResourceModel())) { // restoreDBInstanceToPointInTime is not a versioned call. return safeAddTags(this::restoreDbInstanceToPointInTimeRequest) .invoke(proxy, rdsProxyClient.defaultClient(), progress, allTags); } else if (ResourceModelHelper.isReadReplica(progress.getResourceModel())) { // createDBInstanceReadReplica is not a versioned call. return safeAddTags(this::createDbInstanceReadReplica) .invoke(proxy, rdsProxyClient.defaultClient(), progress, allTags); } else if (ResourceModelHelper.isRestoreFromSnapshot(progress.getResourceModel()) || ResourceModelHelper.isRestoreFromClusterSnapshot(progress.getResourceModel())) { if (ResourceModelHelper.isRestoreFromSnapshot(progress.getResourceModel()) && !isMultiAZ) { try { final DBSnapshot snapshot = fetchDBSnapshot(rdsProxyClient.defaultClient(), model); final String engine = snapshot.engine(); if (StringUtils.isNullOrEmpty(progress.getResourceModel().getEngine())) { progress.getResourceModel().setEngine(engine); } if (progress.getResourceModel().getMultiAZ() == null) { progress.getResourceModel().setMultiAZ(ResourceModelHelper.getDefaultMultiAzForEngine(engine)); } } catch (Exception e) { return Commons.handleException(progress, e, RESTORE_DB_INSTANCE_ERROR_RULE_SET); } } return versioned(proxy, rdsProxyClient, progress, allTags, ImmutableMap.of( ApiVersion.V12, this::restoreDbInstanceFromSnapshotV12, ApiVersion.DEFAULT, safeAddTags(this::restoreDbInstanceFromSnapshot) )); } return versioned(proxy, rdsProxyClient, progress, allTags, ImmutableMap.of( ApiVersion.V12, this::createDbInstanceV12, ApiVersion.DEFAULT, safeAddTags(this::createDbInstance) )); }, CallbackContext::isCreated, CallbackContext::setCreated)) .then(progress -> Commons.execOnce(progress, () -> { final Tagging.TagSet extraTags = Tagging.TagSet.builder() .stackTags(allTags.getStackTags()) .resourceTags(allTags.getResourceTags()) .build(); return updateTags(proxy, rdsProxyClient.defaultClient(), progress, Tagging.TagSet.emptySet(), extraTags); }, CallbackContext::isAddTagsComplete, CallbackContext::setAddTagsComplete)) .then(progress -> ensureEngineSet(rdsProxyClient.defaultClient(), progress)) .then(progress -> { if (ResourceModelHelper.shouldUpdateAfterCreate(progress.getResourceModel())) { return Commons.execOnce(progress, () -> { progress.getCallbackContext().timestampOnce(RESOURCE_UPDATED_AT, Instant.now()); return versioned(proxy, rdsProxyClient, progress, null, ImmutableMap.of( ApiVersion.V12, (pxy, pcl, prg, tgs) -> updateDbInstanceAfterCreateV12(pxy, request, pcl, prg), ApiVersion.DEFAULT, (pxy, pcl, prg, tgs) -> updateDbInstanceAfterCreate(pxy, request, pcl, prg) )).then(p -> Events.checkFailedEvents( rdsProxyClient.defaultClient(), p.getResourceModel().getDBInstanceIdentifier(), SourceType.DB_INSTANCE, p.getCallbackContext().getTimestamp(RESOURCE_UPDATED_AT), p, this::isFailureEvent, logger )); }, CallbackContext::isUpdated, CallbackContext::setUpdated) .then(p -> Commons.execOnce(p, () -> { if (ResourceModelHelper.shouldReboot(p.getResourceModel())) { return rebootAwait(proxy, rdsProxyClient.defaultClient(), p); } return p; }, CallbackContext::isRebooted, CallbackContext::setRebooted)); } return progress; }) .then(progress -> Commons.execOnce(progress, () -> updateAssociatedRoles(proxy, rdsProxyClient.defaultClient(), progress, Collections.emptyList(), desiredRoles), CallbackContext::isUpdatedRoles, CallbackContext::setUpdatedRoles)) .then(progress -> { model.setTags(Translator.translateTagsFromSdk(Tagging.translateTagsToSdk(allTags))); return Commons.reportResourceDrift( model, new ReadHandler().handleRequest(proxy, request, progress.getCallbackContext(), rdsProxyClient, ec2ProxyClient, logger), resourceTypeSchema, logger ); }); } private HandlerMethod safeAddTags(final HandlerMethod handlerMethod) { return (proxy, rdsProxyClient, progress, tagSet) -> progress.then(p -> Tagging.safeCreate(proxy, rdsProxyClient, handlerMethod, progress, tagSet)); } private ProgressEvent createDbInstanceV12( final AmazonWebServicesClientProxy proxy, final ProxyClient rdsProxyClient, final ProgressEvent progress, final Tagging.TagSet tagSet ) { return proxy.initiate( "rds::create-db-instance-v12", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext() ).translateToServiceRequest(Translator::createDbInstanceRequestV12) .backoffDelay(config.getBackoff()) .makeServiceCall((createRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( createRequest, proxyInvocation.client()::createDBInstance )) .stabilize((request, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((request, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, CREATE_DB_INSTANCE_ERROR_RULE_SET )) .progress(); } private ProgressEvent createDbInstance( final AmazonWebServicesClientProxy proxy, final ProxyClient rdsProxyClient, final ProgressEvent progress, final Tagging.TagSet tagSet ) { return proxy.initiate( "rds::create-db-instance", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext() ).translateToServiceRequest(model -> Translator.createDbInstanceRequest(model, tagSet)) .backoffDelay(config.getBackoff()) .makeServiceCall((createRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( createRequest, proxyInvocation.client()::createDBInstance )) .stabilize((request, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((request, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, CREATE_DB_INSTANCE_ERROR_RULE_SET )) .progress(); } private ProgressEvent restoreDbInstanceFromSnapshotV12( final AmazonWebServicesClientProxy proxy, final ProxyClient rdsProxyClient, final ProgressEvent progress, final Tagging.TagSet tagSet ) { return proxy.initiate( "rds::restore-db-instance-from-snapshot-v12", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext() ).translateToServiceRequest(Translator::restoreDbInstanceFromSnapshotRequestV12) .backoffDelay(config.getBackoff()) .makeServiceCall((restoreRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( restoreRequest, proxyInvocation.client()::restoreDBInstanceFromDBSnapshot )) .stabilize((request, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((request, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, RESTORE_DB_INSTANCE_ERROR_RULE_SET )) .progress(); } private ProgressEvent restoreDbInstanceFromSnapshot( final AmazonWebServicesClientProxy proxy, final ProxyClient rdsProxyClient, final ProgressEvent progress, final Tagging.TagSet tagSet ) { return proxy.initiate( "rds::restore-db-instance-from-snapshot", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext() ).translateToServiceRequest(model -> Translator.restoreDbInstanceFromSnapshotRequest(model, tagSet)) .backoffDelay(config.getBackoff()) .makeServiceCall((restoreRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( restoreRequest, proxyInvocation.client()::restoreDBInstanceFromDBSnapshot )) .stabilize((request, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((request, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, RESTORE_DB_INSTANCE_ERROR_RULE_SET )) .progress(); } private ProgressEvent restoreDbInstanceToPointInTimeRequest( final AmazonWebServicesClientProxy proxy, final ProxyClient rdsProxyClient, final ProgressEvent progress, final Tagging.TagSet tagSet ) { return proxy.initiate( "rds::restore-db-instance-to-point-in-time", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext() ).translateToServiceRequest(model -> Translator.restoreDbInstanceToPointInTimeRequest(model, tagSet)) .backoffDelay(config.getBackoff()) .makeServiceCall((restoreRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( restoreRequest, proxyInvocation.client()::restoreDBInstanceToPointInTime )) .stabilize((request, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((request, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, RESTORE_DB_INSTANCE_ERROR_RULE_SET )) .progress(); } private ProgressEvent createDbInstanceReadReplica( final AmazonWebServicesClientProxy proxy, final ProxyClient rdsProxyClient, final ProgressEvent progress, final Tagging.TagSet tagSet ) { return proxy.initiate( "rds::create-db-instance-read-replica", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext() ).translateToServiceRequest(model -> Translator.createDbInstanceReadReplicaRequest(model, tagSet)) .backoffDelay(config.getBackoff()) .makeServiceCall((createRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( createRequest, proxyInvocation.client()::createDBInstanceReadReplica )) .stabilize((request, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((request, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, CREATE_DB_INSTANCE_READ_REPLICA_ERROR_RULE_SET )) .progress(); } protected ProgressEvent updateDbInstanceAfterCreateV12( final AmazonWebServicesClientProxy proxy, final ResourceHandlerRequest request, final ProxyClient rdsProxyClient, final ProgressEvent progress ) { return proxy.initiate("rds::modify-db-instance-v12", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext()) .translateToServiceRequest(resourceModel -> Translator.modifyDbInstanceAfterCreateRequestV12(request.getDesiredResourceState())) .backoffDelay(config.getBackoff()) .makeServiceCall((modifyRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( modifyRequest, proxyInvocation.client()::modifyDBInstance )) .stabilize((modifyRequest, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((modifyRequest, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, MODIFY_DB_INSTANCE_ERROR_RULE_SET )) .progress(); } protected ProgressEvent updateDbInstanceAfterCreate( final AmazonWebServicesClientProxy proxy, final ResourceHandlerRequest request, final ProxyClient rdsProxyClient, final ProgressEvent progress ) { return proxy.initiate("rds::modify-db-instance", rdsProxyClient, progress.getResourceModel(), progress.getCallbackContext()) .translateToServiceRequest(resourceModel -> Translator.modifyDbInstanceAfterCreateRequest(request.getDesiredResourceState())) .backoffDelay(config.getBackoff()) .makeServiceCall((modifyRequest, proxyInvocation) -> proxyInvocation.injectCredentialsAndInvokeV2( modifyRequest, proxyInvocation.client()::modifyDBInstance )) .stabilize((modifyRequest, response, proxyInvocation, model, context) -> isDBInstanceStabilizedAfterMutate(proxyInvocation, model, context)) .handleError((modifyRequest, exception, client, model, context) -> Commons.handleException( ProgressEvent.progress(model, context), exception, MODIFY_DB_INSTANCE_ERROR_RULE_SET )) .progress(); } }