Skip to content

add global cluster endpoint to rds-globalcluster #585

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

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
8e148cd
add global cluster endpoint to rds-globalcluster
akwan224 Dec 2, 2024
f1647c9
[DBCluster] Support EnableHttpEndpoint for Aurora MySQL engine to sup…
kylenie-aws Sep 16, 2024
ee79959
[DBInstance] - Port should not be createOnly/writeOnlyProperty
angusy29 Oct 8, 2024
082bddc
fix: relax KMS key ID property transforms
zrfr Oct 15, 2024
994891c
[DBInstance] Support for Oracle Multitenant SID
angusy29 Jun 4, 2024
60ee46f
I had a PR blocked on a coverage limit and decided the coverage repor…
Nov 8, 2024
fb39767
Adding an .editorconfig so that code formatters will produce consiste…
Oct 30, 2024
74622da
[Refactoring] Decomposing DBInstance BaseHandlerStd in advance of exp…
Nov 5, 2024
0e082a6
[DbShardGroup] Introduce DBShardGroup resource type
philicai Sep 18, 2024
4885684
fix: handle exceptions in AbstractTestBase
zrfr Nov 19, 2024
b7d59c5
feat(DBCluster): add SecondsUntilAutoPause to ServerlessV2ScalingConf…
zrfr Nov 19, 2024
997e353
chore: remove generated docs
zrfr Nov 24, 2024
45441c9
fix(autopause) - Drift detection not including SecondsUntilAutoPause
angusy29 Dec 11, 2024
d9526f5
CFN-564(DBClusterParameterGroup) - Fix DBClusterParameterGroup not re…
angusy29 Nov 22, 2024
f111b37
feat(dbcluster) CFN-459 - Database Insights Mode CloudFormation support
angusy29 Dec 12, 2024
3a210bc
fix(DBCluster): fix NPE when null capacity specified for Serverless V2
zrfr Dec 23, 2024
92c7dc1
[Common] Fix the build failures caused by deprecated Python version
ccmlst Feb 16, 2025
487c40e
[DBShardGroup] Introduce tag-on-create behavior
philicai Oct 30, 2024
5c4cd68
fix: make requestLogger proper initialized
kylenie-aws Feb 19, 2025
05792ad
fix(schema): Missing properties in readOnlyProperties for nested prop…
angusy29 Mar 12, 2025
2773043
V735402726 fix(autoscale): Fix when making CFN update, an autoscaled …
angusy29 Mar 13, 2025
d8adcdb
[DBInstance] Introduce ApplyImmediately flag to address unintended re…
ccmlst Jan 26, 2025
dd8e573
fix(dependencies): Clean up dependencies and fix outdated awssdk depe…
angusy29 Mar 20, 2025
e62bc74
[Common]Fix CLI version mismatch
ccmlst Apr 9, 2025
c165742
[DBCluster] Reenable PIEM Parameters onto restore-db-cluster-from-sn…
shownd-AWS Mar 28, 2025
f48b71b
GetClusterScalabilityTypeFromSnapshot returns default when instance s…
shownd-AWS Feb 24, 2025
31078f9
Add unit tests to increase test coverage
ccmlst Apr 9, 2025
22d8cce
[DBInstance] Introduce AutomaticBackupReplicationRetentionPeriod to a…
cheajer Mar 21, 2025
e3976a9
fix (DBInstance): Remove backup retention period validation from DB i…
cheajer Mar 26, 2025
8f452aa
[Common] Removed the pinned cli version
ccmlst Apr 11, 2025
6523910
never reset to default VPC when both previous and desired security gr…
Mar 28, 2025
85115b2
Add tests for VPC class
ccmlst Apr 11, 2025
fdb2b99
feat(dbinstance): DatabaseInsightsMode support for all RDS engines
angusy29 Apr 9, 2025
6fbeebb
fix(Integration): fix Java version
zrfr Apr 15, 2025
c3142e6
fix: add IdempotencyHelper
zrfr Apr 15, 2025
cbb4015
fix: handle non-idempotent create APIs
zrfr Apr 15, 2025
fe8b818
fix (DBInstance): Handle throttling error for DBInstance resource
kylenie-aws May 2, 2025
589954e
fix(dbcluster) - Add delay to Aurora Serverless V2 update changes
angusy29 May 1, 2025
3588dc9
update globalEndpoint to readOnly property
akwan224 May 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,229 changes: 1,229 additions & 0 deletions .editorconfig

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions .github/workflows/maven-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ jobs:
- name: Setup Python
uses: actions/[email protected]
with:
python-version: 3.7
python-version: 3.13
- name: Install Python packages
run: pip install pre-commit cloudformation-cli cloudformation-cli-java-plugin
run: pip install pre-commit cloudformation-cli cloudformation-cli-java-plugin setuptools
- name: Run pre-commit
run: pre-commit run --all-files
- name: Verify AWS::RDS::Test::Common
Expand Down
39 changes: 33 additions & 6 deletions aws-rds-cfn-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,18 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>utils</artifactId>
<version>2.25.12</version>
<version>2.30.38</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>rds</artifactId>
<version>2.25.56</version>
<version>2.30.38</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/aws-query-protocol -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>aws-query-protocol</artifactId>
<version>2.30.38</version>
</dependency>
<dependency>
<groupId>software.amazon.cloudformation</groupId>
Expand All @@ -53,7 +59,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<version>1.18.30</version>
<scope>provided</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -98,6 +104,27 @@
<version>1.0</version>
<scope>test</scope>
</dependency>
<!-- Overrides transitive dependencies from cloudformation rpdk java plugin -->
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cloudformation</artifactId>
<version>2.30.38</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cloudwatch</artifactId>
<version>2.30.38</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cloudwatchevents</artifactId>
<version>2.30.38</version>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>cloudwatchlogs</artifactId>
<version>2.30.38</version>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -221,17 +248,17 @@
<configuration>
<rules>
<rule>
<element>PACKAGE</element>
<element>BUNDLE</element>
<limits>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.8</minimum>
<minimum>0.7</minimum>
</limit>
<limit>
<counter>INSTRUCTION</counter>
<value>COVEREDRATIO</value>
<minimum>0.8</minimum>
<minimum>0.0</minimum>
</limit>
</limits>
</rule>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ static ErrorStatus ignore(final OperationStatus status) {
}

static ErrorStatus retry(final int callbackDelay) {
return new RetryErrorStatus(OperationStatus.IN_PROGRESS, callbackDelay);
return retry(callbackDelay, null);
}

static ErrorStatus retry(final int callbackDelay, final HandlerErrorCode handlerErrorCode) {
return new RetryErrorStatus(OperationStatus.IN_PROGRESS, callbackDelay, handlerErrorCode);
}

static ErrorStatus conditional(Function<Exception, ErrorStatus> condition) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package software.amazon.rds.common.error;

import lombok.Getter;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.OperationStatus;

public class RetryErrorStatus implements ErrorStatus {
Expand All @@ -10,8 +11,12 @@ public class RetryErrorStatus implements ErrorStatus {
@Getter
private final int callbackDelay;

public RetryErrorStatus(final OperationStatus status, int callbackDelay) {
@Getter
HandlerErrorCode handlerErrorCode;

public RetryErrorStatus(final OperationStatus status, int callbackDelay, final HandlerErrorCode handlerErrorCode) {
this.status = status;
this.callbackDelay = callbackDelay;
this.handlerErrorCode = handlerErrorCode;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import software.amazon.awssdk.core.exception.SdkServiceException;
import software.amazon.awssdk.services.rds.model.KmsKeyNotAccessibleException;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.OperationStatus;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.cloudformation.resource.ResourceTypeSchema;
import software.amazon.rds.common.error.ErrorRuleSet;
Expand Down Expand Up @@ -54,6 +55,14 @@ public final class Commons {
SdkClientException.class)
.build();

public static final ErrorRuleSet ACCESS_DENIED_RULE_SET = ErrorRuleSet.extend(ErrorRuleSet.EMPTY_RULE_SET)
.withErrorCodes(ErrorStatus.failWith(HandlerErrorCode.AccessDenied),
ErrorCode.AccessDenied,
ErrorCode.AccessDeniedException,
ErrorCode.NotAuthorized,
ErrorCode.UnauthorizedOperation)
.build();

private Commons() {
}

Expand Down Expand Up @@ -85,7 +94,17 @@ public static <M, C> ProgressEvent<M, C> handleException(
}
} else if (errorStatus instanceof RetryErrorStatus) {
RetryErrorStatus retryErrorStatus = (RetryErrorStatus) errorStatus;
return ProgressEvent.defaultInProgressHandler(context, retryErrorStatus.getCallbackDelay(), model);
if (retryErrorStatus.getHandlerErrorCode() == null) {
return ProgressEvent.defaultInProgressHandler(context, retryErrorStatus.getCallbackDelay(), model);
} else {
return ProgressEvent.<M, C>builder()
.callbackContext(context)
.resourceModel(model)
.errorCode(retryErrorStatus.getHandlerErrorCode())
.callbackDelaySeconds(retryErrorStatus.getCallbackDelay())
.status(OperationStatus.IN_PROGRESS)
.build();
}
} else if (errorStatus instanceof HandlerErrorStatus) {
final HandlerErrorStatus handlerErrorStatus = (HandlerErrorStatus) errorStatus;
// We need to set model and context to null in case of AlreadyExists errors
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package software.amazon.rds.common.handler;

import com.amazonaws.util.CollectionUtils;

import java.util.List;

public final class Vpc {

public Vpc() {
}

/**
* Is very important that we never reset to default VPC when both previous and desired security group is null,
* or we would be potentially doing a modification that is not clearly intended from the model.
*/
public static boolean shouldSetDefaultVpcId(
final List<String> previousResourceStateVPCSecurityGroups,
final List<String> desiredResourceStateVPCSecurityGroups
) {
if (!CollectionUtils.isNullOrEmpty(previousResourceStateVPCSecurityGroups) && CollectionUtils.isNullOrEmpty(desiredResourceStateVPCSecurityGroups)) {
// The only condition when we should update the default VPC is when the model is unsetting the securityGroup, and never in any other condition.
// For example, when a customer import an existing resource (DBInstance or DBCluster), we should never change the VPC security groups regardless
// of what the model says, because is not intuitive from the model definition that we are changing to a default VPC
return true;
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public ValidatedRequest(final ResourceHandlerRequest<T> base) {
base.getUpdatePolicy(),
base.getCreationPolicy(),
base.getRegion(),
base.getStackId());
base.getStackId(),
base.getMaxResults()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package software.amazon.rds.common.util;

import com.amazonaws.arn.Arn;
import com.amazonaws.arn.ArnResource;
import com.amazonaws.util.StringUtils;


public final class ArnHelper {
private static String RDS_SERVICE = "rds";
public enum ResourceType {
DB_INSTANCE_SNAPSHOT("snapshot"),
DB_CLUSTER_SNAPSHOT("cluster-snapshot");

private String value;

ResourceType(String value) {
this.value = value;
}

public static ResourceType fromString(final String resourceString) {
if (!StringUtils.isNullOrEmpty(resourceString)) {
for (final ResourceType type : ResourceType.values()) {
if (type.value.equals(resourceString)) {
return type;
}
}
}
return null;
}
}

public static boolean isValidArn(final String arn) {
if (StringUtils.isNullOrEmpty(arn)) {
return false;
}
try {
Arn.fromString(arn);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}

public static String getRegionFromArn(final String arn) {
return Arn.fromString(arn).getRegion();
}

public static String getResourceNameFromArn(final String arn) {
return Arn.fromString(arn).getResource().getResource();
}

public static String getAccountIdFromArn(final String arn) {
return Arn.fromString(arn).getAccountId();
}

public static ResourceType getResourceType(String potentialArn) {
if (isValidArn(potentialArn)) {
final Arn arn = Arn.fromString(potentialArn);
if (!RDS_SERVICE.equalsIgnoreCase(arn.getService())) {
return null;
}
final ArnResource resource = arn.getResource();
return ResourceType.fromString(resource.getResourceType());
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package software.amazon.rds.common.util;

import java.util.function.Function;
import java.util.function.UnaryOperator;

import com.google.common.annotations.VisibleForTesting;
import software.amazon.cloudformation.exceptions.CfnAlreadyExistsException;
import software.amazon.cloudformation.exceptions.CfnNotFoundException;
import software.amazon.cloudformation.proxy.HandlerErrorCode;
import software.amazon.cloudformation.proxy.ProgressEvent;
import software.amazon.rds.common.logging.RequestLogger;

/**
* Utility class containing functions to handle non-idempotent APIs.
*/
public final class IdempotencyHelper {

private static boolean bypass = false;

@VisibleForTesting
public static void setBypass(boolean bypass) {
IdempotencyHelper.bypass = bypass;
}

/**
* Wraps a non-idempotent create operation to prevent spurious "already exists" errors.
*
* @param checkExistenceFunction a function that checks if the resource exists and returns the resource if it does,
* or if it does not, then null or a CfnNotFoundException.
* @param createFunction the non-idempotent create operation
* @param resourceTypeName the resource type name (used to form the error message)
* @param resourceIdentifier the resource identifier (used to form the error message)
*/
public static <ResourceT, CallbackT extends PreExistenceContext> ProgressEvent<ResourceT, CallbackT> safeCreate(
final Function<ResourceT, ?> checkExistenceFunction,
final UnaryOperator<ProgressEvent<ResourceT, CallbackT>> createFunction,
final String resourceTypeName,
final String resourceIdentifier,
final ProgressEvent<ResourceT, CallbackT> progress,
final RequestLogger requestLogger
) {
// The approach recommended by CloudFormation is as follows:
// - First, check whether the requested resource already exists. If it does, then fail immediately. Otherwise,
// return IN_PROGRESS with a non-zero callback delay. This forces the handler to return back to CFN, which
// will persist the status so that the pre-existence check is not repeated in case the next step fails.
// - Then, perform the create operation. If it returns an AlreadyExists error, then ignore it.

if (bypass) {
return createFunction.apply(progress);
}

final var preExistenceCheckDone = progress.getCallbackContext().getPreExistenceCheckDone();
if (preExistenceCheckDone == null || !preExistenceCheckDone) {
try {
final var existingResource = checkExistenceFunction.apply(progress.getResourceModel());
if (existingResource != null) {
requestLogger.log("Resource already exists");
throw new CfnAlreadyExistsException(resourceTypeName, resourceIdentifier);
}
} catch (final CfnNotFoundException ignored) {
requestLogger.log("CfnNotFoundException thrown during pre-existence check (all good)");
}

progress.getCallbackContext().setPreExistenceCheckDone(true);

// !!!: The callbackDelaySeconds of 1 is important here. (See canContinueProgress in ProgressEvent)
return ProgressEvent.defaultInProgressHandler(progress.getCallbackContext(), 1,
progress.getResourceModel());
} else {
final var result = createFunction.apply(progress);
if (result.isFailed() && result.getErrorCode() == HandlerErrorCode.AlreadyExists) {
requestLogger.log("Ignoring AlreadyExists error from create operation");
return ProgressEvent.defaultInProgressHandler(progress.getCallbackContext(), 0,
progress.getResourceModel());
} else {
return result;
}
}
}

public interface PreExistenceContext {
Boolean getPreExistenceCheckDone();

void setPreExistenceCheckDone(Boolean preExistenceCheckDone);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package software.amazon.rds.common.util;

import software.amazon.cloudformation.proxy.ProgressEvent;

public class WaiterHelper {
/**
* A function which introduces artificial delay during any ProgressEvent, usually used in Aurora codepaths, for example
* there might be asynchronous workflows running to update resources after the resource has become available and which
* we don't have a valid status to stabilise on
*
* This function will wait for callbackDelay, return the progress event and allow the handler
* to be reinvoke itself and keep retrying until the maxTimeSeconds is breached
*
* @param evt ProgressEvent of the handler
* @param maxSeconds The max total time we will wait
* @param pollSeconds Wait time before the next invocation of the handler, until we reach maxSeconds
* @return ProgressEvent
* @param <ResourceT> The generic resource model
* @param <CallbackT> The generic callback
*/
public static <ResourceT, CallbackT extends DelayContext> ProgressEvent<ResourceT, CallbackT> delay(final ProgressEvent<ResourceT, CallbackT> evt, final int maxSeconds, final int pollSeconds) {
final CallbackT callbackContext = evt.getCallbackContext();
if (callbackContext.getWaitTime() <= maxSeconds) {
callbackContext.setWaitTime(callbackContext.getWaitTime() + pollSeconds);
return ProgressEvent.defaultInProgressHandler(callbackContext, pollSeconds, evt.getResourceModel());
} else {
return ProgressEvent.progress(evt.getResourceModel(), callbackContext);
}
}

public interface DelayContext {
int getWaitTime();
void setWaitTime(int waitTime);
}
}
Loading