Skip to content

Commit

Permalink
[DBInstance] Detect engine before running Create workflow (#460)
Browse files Browse the repository at this point in the history
  • Loading branch information
khebul committed Aug 21, 2023
1 parent 5d70473 commit c83bb50
Show file tree
Hide file tree
Showing 10 changed files with 565 additions and 44 deletions.
3 changes: 3 additions & 0 deletions aws-rds-dbinstance/aws-rds-dbinstance.json
Original file line number Diff line number Diff line change
Expand Up @@ -550,6 +550,9 @@
"rds:CreateDBInstance",
"rds:CreateDBInstanceReadReplica",
"rds:DescribeDBInstances",
"rds:DescribeDBClusters",
"rds:DescribeDBClusterSnapshots",
"rds:DescribeDBInstanceAutomatedBackups",
"rds:DescribeDBSnapshots",
"rds:DescribeEvents",
"rds:ModifyDBInstance",
Expand Down
2 changes: 2 additions & 0 deletions aws-rds-dbinstance/resource-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ Resources:
- "rds:CreateDBInstance"
- "rds:CreateDBInstanceReadReplica"
- "rds:DeleteDBInstance"
- "rds:DescribeDBClusterSnapshots"
- "rds:DescribeDBClusters"
- "rds:DescribeDBEngineVersions"
- "rds:DescribeDBInstanceAutomatedBackups"
- "rds:DescribeDBInstances"
- "rds:DescribeDBParameterGroups"
- "rds:DescribeDBSnapshots"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@
import software.amazon.awssdk.services.rds.model.AuthorizationNotFoundException;
import software.amazon.awssdk.services.rds.model.CertificateNotFoundException;
import software.amazon.awssdk.services.rds.model.DBCluster;
import software.amazon.awssdk.services.rds.model.DBClusterSnapshot;
import software.amazon.awssdk.services.rds.model.DBInstance;
import software.amazon.awssdk.services.rds.model.DBInstanceAutomatedBackup;
import software.amazon.awssdk.services.rds.model.DBSnapshot;
import software.amazon.awssdk.services.rds.model.DbClusterNotFoundException;
import software.amazon.awssdk.services.rds.model.DbClusterSnapshotNotFoundException;
Expand All @@ -42,7 +44,10 @@
import software.amazon.awssdk.services.rds.model.DbSubnetGroupDoesNotCoverEnoughAZsException;
import software.amazon.awssdk.services.rds.model.DbSubnetGroupNotFoundException;
import software.amazon.awssdk.services.rds.model.DbUpgradeDependencyFailureException;
import software.amazon.awssdk.services.rds.model.DescribeDbClusterSnapshotsResponse;
import software.amazon.awssdk.services.rds.model.DescribeDbClustersResponse;
import software.amazon.awssdk.services.rds.model.DescribeDbInstanceAutomatedBackupsRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbInstanceAutomatedBackupsResponse;
import software.amazon.awssdk.services.rds.model.DescribeDbInstancesResponse;
import software.amazon.awssdk.services.rds.model.DescribeDbSnapshotsResponse;
import software.amazon.awssdk.services.rds.model.DomainMembership;
Expand All @@ -67,6 +72,7 @@
import software.amazon.awssdk.services.rds.model.StorageQuotaExceededException;
import software.amazon.awssdk.services.rds.model.StorageTypeNotSupportedException;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.exceptions.CfnNotStabilizedException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
Expand Down Expand Up @@ -234,6 +240,12 @@ public abstract class BaseHandlerStd extends BaseHandler<CallbackContext> {
InvalidSubnetException.class)
.build();

protected static final ErrorRuleSet DB_INSTANCE_FETCH_ENGINE_RULE_SET = ErrorRuleSet
.extend(DEFAULT_DB_INSTANCE_ERROR_RULE_SET)
.withErrorClasses(ErrorStatus.failWith(HandlerErrorCode.InvalidRequest),
CfnInvalidRequestException.class)
.build();

public static final ErrorRuleSet RESTORE_DB_INSTANCE_ERROR_RULE_SET = ErrorRuleSet
.extend(DEFAULT_DB_INSTANCE_ERROR_RULE_SET)
.withErrorCodes(ErrorStatus.failWith(HandlerErrorCode.AlreadyExists),
Expand Down Expand Up @@ -479,6 +491,39 @@ protected DBInstance fetchDBInstance(
return response.dbInstances().get(0);
}

protected DBInstance fetchDBInstance(
final ProxyClient<RdsClient> rdsProxyClient,
final String dbInstanceIdentifier
) {
final DescribeDbInstancesResponse response = rdsProxyClient.injectCredentialsAndInvokeV2(
Translator.describeDbInstanceByDBInstanceIdentifierRequest(dbInstanceIdentifier),
rdsProxyClient.client()::describeDBInstances
);
return response.dbInstances().get(0);
}

protected DBInstance fetchDBInstanceByResourceId(
final ProxyClient<RdsClient> rdsProxyClient,
final String resourceId
) {
final DescribeDbInstancesResponse response = rdsProxyClient.injectCredentialsAndInvokeV2(
Translator.describeDbInstanceByResourceIdRequest(resourceId),
rdsProxyClient.client()::describeDBInstances
);
return response.dbInstances().get(0);
}

protected DBInstanceAutomatedBackup fetchAutomaticBackup(
final ProxyClient<RdsClient> rdsProxyClient,
final String automaticBackupArn
) {
final DescribeDbInstanceAutomatedBackupsResponse response = rdsProxyClient.injectCredentialsAndInvokeV2(
Translator.describeDBInstanceAutomaticBackup(automaticBackupArn),
rdsProxyClient.client()::describeDBInstanceAutomatedBackups
);
return response.dbInstanceAutomatedBackups().get(0);
}

protected DBCluster fetchDBCluster(
final ProxyClient<RdsClient> rdsProxyClient,
final ResourceModel model
Expand All @@ -490,6 +535,17 @@ protected DBCluster fetchDBCluster(
return response.dbClusters().get(0);
}

protected DBCluster fetchDBCluster(
final ProxyClient<RdsClient> rdsProxyClient,
final String dbClusterIdentifier
) {
final DescribeDbClustersResponse response = rdsProxyClient.injectCredentialsAndInvokeV2(
Translator.describeDbClusterRequest(dbClusterIdentifier),
rdsProxyClient.client()::describeDBClusters
);
return response.dbClusters().get(0);
}

protected DBSnapshot fetchDBSnapshot(
final ProxyClient<RdsClient> rdsProxyClient,
final ResourceModel model
Expand All @@ -501,6 +557,17 @@ protected DBSnapshot fetchDBSnapshot(
return response.dbSnapshots().get(0);
}

protected DBClusterSnapshot fetchDBClusterSnapshot(
final ProxyClient<RdsClient> rdsProxyClient,
final ResourceModel model
) {
final DescribeDbClusterSnapshotsResponse response = rdsProxyClient.injectCredentialsAndInvokeV2(
Translator.describeDbClusterSnapshotsRequest(model),
rdsProxyClient.client()::describeDBClusterSnapshots
);
return response.dbClusterSnapshots().get(0);
}

protected SecurityGroup fetchSecurityGroup(
final ProxyClient<Ec2Client> ec2ProxyClient,
final String vpcId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
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.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.proxy.ProxyClient;
Expand Down Expand Up @@ -86,6 +87,16 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
.build();

return ProgressEvent.progress(model, callbackContext)
.then(progress -> {
if (StringUtils.isNullOrEmpty(progress.getResourceModel().getEngine())) {
try {
model.setEngine(fetchEngine(rdsProxyClient.defaultClient(), progress.getResourceModel()));
} catch (Exception e) {
return Commons.handleException(progress, e, DB_INSTANCE_FETCH_ENGINE_RULE_SET);
}
}
return progress;
})
.then(progress -> Commons.execOnce(progress, () -> {
if (ResourceModelHelper.isRestoreToPointInTime(progress.getResourceModel())) {
// restoreDBInstanceToPointInTime is not a versioned call.
Expand Down Expand Up @@ -175,6 +186,36 @@ private HandlerMethod<ResourceModel, CallbackContext> safeAddTags(final HandlerM
return (proxy, rdsProxyClient, progress, tagSet) -> progress.then(p -> Tagging.safeCreate(proxy, rdsProxyClient, handlerMethod, progress, tagSet));
}

private String fetchEngine(final ProxyClient<RdsClient> client, final ResourceModel model) {
if (ResourceModelHelper.isRestoreFromSnapshot(model)) {
return fetchDBSnapshot(client, model).engine();
}
if (ResourceModelHelper.isRestoreFromClusterSnapshot(model)) {
return fetchDBClusterSnapshot(client, model).engine();
}

if (ResourceModelHelper.isDBInstanceReadReplica(model)) {
return fetchDBInstance(client, model.getSourceDBInstanceIdentifier()).engine();
}
if (ResourceModelHelper.isDBClusterReadReplica(model)) {
return fetchDBCluster(client, model.getSourceDBClusterIdentifier()).engine();
}

if (ResourceModelHelper.isRestoreToPointInTime(model)) {
if (StringUtils.hasValue(model.getSourceDBInstanceIdentifier())) {
return fetchDBInstance(client, model.getSourceDBInstanceIdentifier()).engine();
}
if (StringUtils.hasValue(model.getSourceDbiResourceId())) {
return fetchDBInstanceByResourceId(client, model.getSourceDbiResourceId()).engine();
}
if (StringUtils.hasValue(model.getSourceDBInstanceAutomatedBackupsArn())) {
return fetchAutomaticBackup(client, model.getSourceDBInstanceAutomatedBackupsArn()).engine();
}
}

throw new CfnInvalidRequestException("Cannot fetch the engine based on current template. Please add the Engine parameter to the template and try again.");
}

private ProgressEvent<ResourceModel, CallbackContext> createDbInstanceV12(
final AmazonWebServicesClientProxy proxy,
final ProxyClient<RdsClient> rdsProxyClient,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
import software.amazon.awssdk.services.rds.model.CreateDbInstanceReadReplicaRequest;
import software.amazon.awssdk.services.rds.model.CreateDbInstanceRequest;
import software.amazon.awssdk.services.rds.model.DeleteDbInstanceRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbClusterSnapshotsRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbClustersRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbEngineVersionsRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbInstanceAutomatedBackupsRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbInstancesRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbParameterGroupsRequest;
import software.amazon.awssdk.services.rds.model.DescribeDbSnapshotsRequest;
Expand Down Expand Up @@ -56,18 +58,49 @@ public static DescribeDbClustersRequest describeDbClustersRequest(final Resource
.build();
}

public static DescribeDbClustersRequest describeDbClusterRequest(final String dbClusterIdentifier) {
return DescribeDbClustersRequest.builder()
.dbClusterIdentifier(dbClusterIdentifier)
.build();
}

public static DescribeDbInstancesRequest describeDbInstancesRequest(final String nextToken) {
return DescribeDbInstancesRequest.builder()
.marker(nextToken)
.build();
}

public static DescribeDbInstancesRequest describeDbInstanceByDBInstanceIdentifierRequest(final String dbInstanceIdentifier) {
return DescribeDbInstancesRequest.builder()
.dbInstanceIdentifier(dbInstanceIdentifier)
.build();
}

public static DescribeDbInstancesRequest describeDbInstanceByResourceIdRequest(final String resourceId) {
return DescribeDbInstancesRequest.builder()
.filters(software.amazon.awssdk.services.rds.model.Filter.builder()
.name("dbi-resource-id").values(resourceId).build())
.build();
}

public static DescribeDbInstanceAutomatedBackupsRequest describeDBInstanceAutomaticBackup(final String automaticBackupArn) {
return DescribeDbInstanceAutomatedBackupsRequest.builder()
.dbInstanceAutomatedBackupsArn(automaticBackupArn)
.build();
}

public static DescribeDbSnapshotsRequest describeDbSnapshotsRequest(final ResourceModel model) {
return DescribeDbSnapshotsRequest.builder()
.dbSnapshotIdentifier(model.getDBSnapshotIdentifier())
.build();
}

public static DescribeDbClusterSnapshotsRequest describeDbClusterSnapshotsRequest(final ResourceModel model) {
return DescribeDbClusterSnapshotsRequest.builder()
.dbClusterSnapshotIdentifier(model.getDBClusterSnapshotIdentifier())
.build();
}

public static CreateDbInstanceReadReplicaRequest createDbInstanceReadReplicaRequest(
final ResourceModel model,
final Tagging.TagSet tagSet
Expand Down Expand Up @@ -104,6 +137,12 @@ public static CreateDbInstanceReadReplicaRequest createDbInstanceReadReplicaRequ
.tags(Tagging.translateTagsToSdk(tagSet))
.useDefaultProcessorFeatures(model.getUseDefaultProcessorFeatures())
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null);
if (!ResourceModelHelper.isSqlServer(model)) {
builder.allocatedStorage(getAllocatedStorage(model));
builder.iops(model.getIops());
builder.storageThroughput(model.getStorageThroughput());
builder.storageType(model.getStorageType());
}
return builder.build();
}

Expand Down Expand Up @@ -163,6 +202,12 @@ public static RestoreDbInstanceFromDbSnapshotRequest restoreDbInstanceFromSnapsh
.tdeCredentialPassword(model.getTdeCredentialPassword())
.useDefaultProcessorFeatures(model.getUseDefaultProcessorFeatures())
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null);
if (!ResourceModelHelper.isSqlServer(model)) {
builder.allocatedStorage(getAllocatedStorage(model));
builder.iops(model.getIops());
builder.storageThroughput(model.getStorageThroughput());
builder.storageType(model.getStorageType());
}
return builder.build();
}

Expand Down Expand Up @@ -306,7 +351,12 @@ static RestoreDbInstanceToPointInTimeRequest restoreDbInstanceToPointInTimeReque
.useDefaultProcessorFeatures(model.getUseDefaultProcessorFeatures())
.useLatestRestorableTime(model.getUseLatestRestorableTime())
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null);

if (!ResourceModelHelper.isSqlServer(model)) {
builder.allocatedStorage(getAllocatedStorage(model));
builder.iops(model.getIops());
builder.storageThroughput(model.getStorageThroughput());
builder.storageType(model.getStorageType());
}
return builder.build();
}

Expand Down Expand Up @@ -509,7 +559,7 @@ public static ModifyDbInstanceRequest modifyDbInstanceAfterCreateRequest(final R
.preferredBackupWindow(model.getPreferredBackupWindow())
.preferredMaintenanceWindow(model.getPreferredMaintenanceWindow());

if (ResourceModelHelper.isStorageParametersModified(model)) {
if (ResourceModelHelper.isStorageParametersModified(model) && ResourceModelHelper.isSqlServer(model)) {
builder.allocatedStorage(getAllocatedStorage(model));
builder.iops(model.getIops());
builder.storageThroughput(model.getStorageThroughput());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public final class ResourceModelHelper {
"sqlserver-ee",
"sqlserver-se"
);
private static final String SQLSERVER_ENGINE = "sqlserver";

public static boolean shouldUpdateAfterCreate(final ResourceModel model) {
return (isReadReplica(model) ||
Expand All @@ -31,13 +32,20 @@ public static boolean shouldUpdateAfterCreate(final ResourceModel model) {
StringUtils.hasValue(model.getMonitoringRoleArn()) ||
Optional.ofNullable(model.getBackupRetentionPeriod()).orElse(0) > 0 ||
Optional.ofNullable(model.getMonitoringInterval()).orElse(0) > 0 ||
isStorageParametersModified(model) ||
(isSqlServer(model) && isStorageParametersModified(model)) ||
BooleanUtils.isTrue(model.getManageMasterUserPassword()) ||
BooleanUtils.isTrue(model.getDeletionProtection()) ||
BooleanUtils.isTrue(model.getEnablePerformanceInsights())
);
}

public static boolean isSqlServer(final ResourceModel model) {
final String engine = model.getEngine();
// treat unknown engines as SQLServer
return engine == null || engine.contains(SQLSERVER_ENGINE);
}


public static boolean isStorageParametersModified(final ResourceModel model) {
return StringUtils.hasValue(model.getAllocatedStorage()) ||
Optional.ofNullable(model.getIops()).orElse(0) > 0 ||
Expand All @@ -54,8 +62,15 @@ public static boolean isRestoreToPointInTime(final ResourceModel model) {
}

public static boolean isReadReplica(final ResourceModel model) {
return StringUtils.hasValue(model.getSourceDBInstanceIdentifier())
|| StringUtils.hasValue(model.getSourceDBClusterIdentifier());
return isDBInstanceReadReplica(model) || isDBClusterReadReplica(model);
}

public static boolean isDBInstanceReadReplica(final ResourceModel model) {
return StringUtils.hasValue(model.getSourceDBInstanceIdentifier());
}

public static boolean isDBClusterReadReplica(final ResourceModel model) {
return StringUtils.hasValue(model.getSourceDBClusterIdentifier());
}

public static boolean isRestoreFromSnapshot(final ResourceModel model) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ public abstract class AbstractHandlerTest extends AbstractTestBase<DBInstance, R
protected static final Boolean STORAGE_ENCRYPTED_NO = false;
protected static final String STORAGE_TYPE_STANDARD = "standard";
protected static final String STORAGE_TYPE_GP2 = "gp2";
protected static final String STORAGE_TYPE_GP3 = "gp3";

protected static final String STORAGE_TYPE_IO1 = "io1";
protected static final String TIMEZONE_DEFAULT = null;
protected static final Boolean USE_DEFAULT_PROCESSOR_FEATURES_YES = true;
Expand Down
Loading

0 comments on commit c83bb50

Please sign in to comment.