Skip to content

Commit

Permalink
Add EngineLifecycleSupport integration for instances and clusters (aw…
Browse files Browse the repository at this point in the history
…s-cloudformation#538)

* Update RDS SDK version to 2.25.56

Version 2.25.56 adds SDK support for EngineLifecycleSupport.
CFN requires this new field as a dependency.

* Add EngineLifecycleSupport integration for instances and clusters

DB instances and clusters can be created and restored
with the EngineLifecycleSupport field via CloudFormation.
All subsequent updates to this field on the resource
will be blocked to prevent unintended customer
resource replacement. EngineLifecycleSupport is marked as
"Some Interruption" causing to preserve future compatibility for
when a formal modify option is available for this field.
  • Loading branch information
anson1014 authored and Diogo Henriques committed Jun 12, 2024
1 parent d5d5fa0 commit 6f48354
Show file tree
Hide file tree
Showing 24 changed files with 124 additions and 18 deletions.
2 changes: 1 addition & 1 deletion aws-rds-cfn-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.12</version>
<version>2.25.56</version>
</dependency>
<dependency>
<groupId>software.amazon.cloudformation</groupId>
Expand Down
2 changes: 1 addition & 1 deletion aws-rds-customdbengineversion/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.12</version>
<version>2.25.56</version>
</dependency>
<dependency>
<groupId>software.amazon.rds.common</groupId>
Expand Down
4 changes: 4 additions & 0 deletions aws-rds-dbcluster/aws-rds-dbcluster.json
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,10 @@
"description": "The name of the database engine to be used for this DB cluster. Valid Values: aurora (for MySQL 5.6-compatible Aurora), aurora-mysql (for MySQL 5.7-compatible Aurora), and aurora-postgresql",
"type": "string"
},
"EngineLifecycleSupport": {
"description": "The life cycle type of the DB cluster. You can use this setting to enroll your DB cluster into Amazon RDS Extended Support.",
"type": "string"
},
"EngineMode": {
"description": "The DB engine mode of the DB cluster, either provisioned, serverless, parallelquery, global, or multimaster.",
"type": "string"
Expand Down
12 changes: 12 additions & 0 deletions aws-rds-dbcluster/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"<a href="#enablehttpendpoint" title="EnableHttpEndpoint">EnableHttpEndpoint</a>" : <i>Boolean</i>,
"<a href="#enableiamdatabaseauthentication" title="EnableIAMDatabaseAuthentication">EnableIAMDatabaseAuthentication</a>" : <i>Boolean</i>,
"<a href="#engine" title="Engine">Engine</a>" : <i>String</i>,
"<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a>" : <i>String</i>,
"<a href="#enginemode" title="EngineMode">EngineMode</a>" : <i>String</i>,
"<a href="#engineversion" title="EngineVersion">EngineVersion</a>" : <i>String</i>,
"<a href="#managemasteruserpassword" title="ManageMasterUserPassword">ManageMasterUserPassword</a>" : <i>Boolean</i>,
Expand Down Expand Up @@ -103,6 +104,7 @@ Properties:
<a href="#enablehttpendpoint" title="EnableHttpEndpoint">EnableHttpEndpoint</a>: <i>Boolean</i>
<a href="#enableiamdatabaseauthentication" title="EnableIAMDatabaseAuthentication">EnableIAMDatabaseAuthentication</a>: <i>Boolean</i>
<a href="#engine" title="Engine">Engine</a>: <i>String</i>
<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a>: <i>String</i>
<a href="#enginemode" title="EngineMode">EngineMode</a>: <i>String</i>
<a href="#engineversion" title="EngineVersion">EngineVersion</a>: <i>String</i>
<a href="#managemasteruserpassword" title="ManageMasterUserPassword">ManageMasterUserPassword</a>: <i>Boolean</i>
Expand Down Expand Up @@ -390,6 +392,16 @@ _Type_: String

_Update requires_: [Some interruptions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-some-interrupt)

#### EngineLifecycleSupport

The life cycle type of the DB cluster. You can use this setting to enroll your DB cluster into Amazon RDS Extended Support.

_Required_: No

_Type_: String

_Update requires_: [Some interruptions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-some-interrupt)

#### EngineMode

The DB engine mode of the DB cluster, either provisioned, serverless, parallelquery, global, or multimaster.
Expand Down
2 changes: 1 addition & 1 deletion aws-rds-dbcluster/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.12</version>
<version>2.25.56</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
import software.amazon.awssdk.services.rds.model.Tag;
import software.amazon.awssdk.services.rds.model.WriteForwardingStatus;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.exceptions.CfnNotStabilizedException;
import software.amazon.cloudformation.exceptions.ResourceNotFoundException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
Expand Down Expand Up @@ -158,7 +159,8 @@ public abstract class BaseHandlerStd extends BaseHandler<CallbackContext> {
KmsKeyNotAccessibleException.class,
StorageTypeNotAvailableException.class,
StorageTypeNotSupportedException.class,
NetworkTypeNotSupportedException.class)
NetworkTypeNotSupportedException.class,
CfnInvalidRequestException.class)
.build();

protected static final ErrorRuleSet ADD_ASSOC_ROLES_SOFTFAIL_ERROR_RULE_SET = ErrorRuleSet
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ static CreateDbClusterRequest createDbClusterRequest(
.storageType(model.getStorageType())
.tags(Tagging.translateTagsToSdk(tagSet))
.vpcSecurityGroupIds(model.getVpcSecurityGroupIds())
.engineLifecycleSupport(model.getEngineLifecycleSupport())
.build();
}

Expand Down Expand Up @@ -123,7 +124,8 @@ static RestoreDbClusterToPointInTimeRequest restoreDbClusterToPointInTimeRequest
.restoreType(model.getRestoreType())
.tags(Tagging.translateTagsToSdk(tagSet))
.useLatestRestorableTime(model.getUseLatestRestorableTime())
.vpcSecurityGroupIds(model.getVpcSecurityGroupIds());
.vpcSecurityGroupIds(model.getVpcSecurityGroupIds())
.engineLifecycleSupport(model.getEngineLifecycleSupport());

if (StringUtils.hasValue(model.getRestoreToTime())
&& BooleanUtils.isNotTrue(model.getUseLatestRestorableTime())) {
Expand Down Expand Up @@ -165,6 +167,7 @@ static RestoreDbClusterFromSnapshotRequest restoreDbClusterFromSnapshotRequest(
.storageType(model.getStorageType())
.tags(Tagging.translateTagsToSdk(tagSet))
.vpcSecurityGroupIds(model.getVpcSecurityGroupIds())
.engineLifecycleSupport(model.getEngineLifecycleSupport())
.build();
}

Expand Down Expand Up @@ -546,6 +549,7 @@ public static ResourceModel translateDbClusterFromSdk(
.build()
)
.engine(dbCluster.engine())
.engineLifecycleSupport(dbCluster.engineLifecycleSupport())
.engineMode(dbCluster.engineMode())
.engineVersion(dbCluster.engineVersion())
.manageMasterUserPassword(dbCluster.masterUserSecret() != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@

import java.time.Instant;
import java.util.HashSet;
import java.util.Objects;

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.SourceType;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.ProgressEvent;
Expand Down Expand Up @@ -65,6 +67,18 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
);
}
return ProgressEvent.progress(desiredResourceState, callbackContext)
.then(progress -> {
try {
if(!Objects.equals(request.getDesiredResourceState().getEngineLifecycleSupport(),
request.getPreviousResourceState().getEngineLifecycleSupport()) &&
!request.getRollback()) {
throw new CfnInvalidRequestException("EngineLifecycleSupport cannot be modified.");
}
} catch (CfnInvalidRequestException e) {
return Commons.handleException(progress, e, DEFAULT_DB_CLUSTER_ERROR_RULE_SET, requestLogger);
}
return progress;
})
.then(progress -> {
if (shouldRemoveFromGlobalCluster(request.getPreviousResourceState(), request.getDesiredResourceState())) {
progress.getCallbackContext().timestampOnce(RESOURCE_UPDATED_AT, Instant.now());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -995,4 +995,17 @@ void handleRequest_ModifyDBCluster_HandleException(
expectResponseCode
);
}

@Test
public void handleRequest_EngineLifecycleSupportShouldFail() {
expectServiceInvocation = false;
test_handleRequest_base(
new CallbackContext(),
ResourceHandlerRequest.<ResourceModel>builder().rollback(false),
null,
() -> RESOURCE_MODEL.toBuilder().engineLifecycleSupport("open-source-rds-extended-support").build(),
() -> RESOURCE_MODEL.toBuilder().engineLifecycleSupport("open-source-rds-extended-support-disabled").build(),
expectFailed(HandlerErrorCode.InvalidRequest)
);
}
}
2 changes: 1 addition & 1 deletion aws-rds-dbclusterendpoint/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.12</version>
<version>2.25.56</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-rpdk-java-plugin -->
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion aws-rds-dbclusterparametergroup/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.12</version>
<version>2.25.56</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-rpdk-java-plugin -->
<dependency>
Expand Down
4 changes: 4 additions & 0 deletions aws-rds-dbinstance/aws-rds-dbinstance.json
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,10 @@
"type": "string",
"description": "The name of the database engine that you want to use for this DB instance."
},
"EngineLifecycleSupport": {
"type": "string",
"description": "The life cycle type of the DB instance. You can use this setting to enroll your DB instance into Amazon RDS Extended Support."
},
"EngineVersion": {
"type": "string",
"description": "The version number of the database engine to use."
Expand Down
12 changes: 12 additions & 0 deletions aws-rds-dbinstance/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ To declare this entity in your AWS CloudFormation template, use the following sy
"<a href="#enableperformanceinsights" title="EnablePerformanceInsights">EnablePerformanceInsights</a>" : <i>Boolean</i>,
"<a href="#endpoint" title="Endpoint">Endpoint</a>" : <i><a href="endpoint.md">Endpoint</a></i>,
"<a href="#engine" title="Engine">Engine</a>" : <i>String</i>,
"<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a>" : <i>String</i>,
"<a href="#engineversion" title="EngineVersion">EngineVersion</a>" : <i>String</i>,
"<a href="#managemasteruserpassword" title="ManageMasterUserPassword">ManageMasterUserPassword</a>" : <i>Boolean</i>,
"<a href="#iops" title="Iops">Iops</a>" : <i>Integer</i>,
Expand Down Expand Up @@ -139,6 +140,7 @@ Properties:
<a href="#enableperformanceinsights" title="EnablePerformanceInsights">EnablePerformanceInsights</a>: <i>Boolean</i>
<a href="#endpoint" title="Endpoint">Endpoint</a>: <i><a href="endpoint.md">Endpoint</a></i>
<a href="#engine" title="Engine">Engine</a>: <i>String</i>
<a href="#enginelifecyclesupport" title="EngineLifecycleSupport">EngineLifecycleSupport</a>: <i>String</i>
<a href="#engineversion" title="EngineVersion">EngineVersion</a>: <i>String</i>
<a href="#managemasteruserpassword" title="ManageMasterUserPassword">ManageMasterUserPassword</a>: <i>Boolean</i>
<a href="#iops" title="Iops">Iops</a>: <i>Integer</i>
Expand Down Expand Up @@ -577,6 +579,16 @@ _Type_: String

_Update requires_: [Some interruptions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-some-interrupt)

#### EngineLifecycleSupport

The life cycle type of the DB instance. You can use this setting to enroll your DB instance into Amazon RDS Extended Support.

_Required_: No

_Type_: String

_Update requires_: [Some interruptions](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-some-interrupt)

#### EngineVersion

The version number of the database engine to use.
Expand Down
2 changes: 1 addition & 1 deletion aws-rds-dbinstance/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.12</version>
<version>2.25.56</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ public abstract class BaseHandlerStd extends BaseHandler<CallbackContext> {
.withErrorClasses(ErrorStatus.failWith(HandlerErrorCode.NotFound),
DbInstanceNotFoundException.class)
.withErrorClasses(ErrorStatus.failWith(HandlerErrorCode.InvalidRequest),
CfnInvalidRequestException.class,
InvalidDbSecurityGroupStateException.class)
.build();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,8 @@ public static RestoreDbInstanceFromDbSnapshotRequest restoreDbInstanceFromSnapsh
.tdeCredentialArn(model.getTdeCredentialArn())
.tdeCredentialPassword(model.getTdeCredentialPassword())
.useDefaultProcessorFeatures(model.getUseDefaultProcessorFeatures())
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null);
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null)
.engineLifecycleSupport(model.getEngineLifecycleSupport());
if (!ResourceModelHelper.isSqlServer(model)) {
builder.allocatedStorage(getAllocatedStorage(model));
builder.iops(model.getIops());
Expand Down Expand Up @@ -329,7 +330,8 @@ public static CreateDbInstanceRequest createDbInstanceRequest(
.tdeCredentialArn(model.getTdeCredentialArn())
.tdeCredentialPassword(model.getTdeCredentialPassword())
.timezone(model.getTimezone())
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null);
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null)
.engineLifecycleSupport(model.getEngineLifecycleSupport());

// Set PerformanceInsightsKMSKeyId only if EnablePerformanceInsights is true.
// The point is that it is a completely legitimate create from the CFN perspective and
Expand Down Expand Up @@ -386,7 +388,8 @@ static RestoreDbInstanceToPointInTimeRequest restoreDbInstanceToPointInTimeReque
.tdeCredentialPassword(model.getTdeCredentialPassword())
.useDefaultProcessorFeatures(model.getUseDefaultProcessorFeatures())
.useLatestRestorableTime(model.getUseLatestRestorableTime())
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null);
.vpcSecurityGroupIds(CollectionUtils.isNotEmpty(model.getVPCSecurityGroups()) ? model.getVPCSecurityGroups() : null)
.engineLifecycleSupport(model.getEngineLifecycleSupport());
if (!ResourceModelHelper.isSqlServer(model)) {
builder.allocatedStorage(getAllocatedStorage(model));
builder.iops(model.getIops());
Expand Down Expand Up @@ -866,6 +869,7 @@ public static ResourceModel.ResourceModelBuilder translateDbInstanceFromSdkBuild
.enablePerformanceInsights(dbInstance.performanceInsightsEnabled())
.endpoint(endpoint)
.engine(dbInstance.engine())
.engineLifecycleSupport(dbInstance.engineLifecycleSupport())
.engineVersion(dbInstance.engineVersion())
.iops(dbInstance.iops())
.kmsKeyId(dbInstance.kmsKeyId())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Function;

Expand All @@ -23,6 +24,7 @@
import software.amazon.awssdk.services.rds.model.DescribeDbParameterGroupsResponse;
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.HandlerErrorCode;
import software.amazon.cloudformation.proxy.ProgressEvent;
Expand All @@ -39,7 +41,6 @@
import software.amazon.rds.dbinstance.status.DBParameterGroupStatus;
import software.amazon.rds.dbinstance.util.ImmutabilityHelper;
import software.amazon.rds.dbinstance.util.ResourceModelHelper;

public class UpdateHandler extends BaseHandlerStd {

public UpdateHandler() {
Expand Down Expand Up @@ -101,6 +102,18 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
final Collection<DBInstanceRole> desiredRoles = request.getDesiredResourceState().getAssociatedRoles();

return ProgressEvent.progress(request.getDesiredResourceState(), callbackContext)
.then(progress -> {
try {
if(!Objects.equals(request.getDesiredResourceState().getEngineLifecycleSupport(),
request.getPreviousResourceState().getEngineLifecycleSupport()) &&
!request.getRollback()) {
throw new CfnInvalidRequestException("EngineLifecycleSupport cannot be modified.");
}
} catch (CfnInvalidRequestException e) {
return Commons.handleException(progress, e, MODIFY_DB_INSTANCE_ERROR_RULE_SET, requestLogger);
}
return progress;
})
.then(progress -> {
if (shouldSetParameterGroupName(request)) {
return setParameterGroupName(rdsClient, progress);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1860,4 +1860,27 @@ public void handleRequest_updateStorageTypeFromIo1ToIo2() {

verify(rdsProxy.client(), times(1)).describeEvents(any(DescribeEventsRequest.class));
}

@Test
public void handleRequest_EngineLifecycleSupportShouldFail() {
expectServiceInvocation = false;
final ResourceModel desiredModel = RESOURCE_MODEL_BLDR()
.engineLifecycleSupport("open-source-rds-extended-support")
.build();

final ResourceModel previousModel = RESOURCE_MODEL_BLDR()
.engineLifecycleSupport("open-source-rds-extended-support-disabled")
.build();

final CallbackContext context = new CallbackContext();

test_handleRequest_base(
context,
ResourceHandlerRequest.<ResourceModel>builder().rollback(false),
null,
() -> previousModel,
() -> desiredModel,
expectFailed(HandlerErrorCode.InvalidRequest)
);
}
}
2 changes: 1 addition & 1 deletion aws-rds-dbparametergroup/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.12</version>
<version>2.25.56</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.cloudformation/aws-cloudformation-rpdk-java-plugin -->
<dependency>
Expand Down
Loading

0 comments on commit 6f48354

Please sign in to comment.