Skip to content
This repository was archived by the owner on Oct 23, 2025. It is now read-only.

Commit 7e9d835

Browse files
Wait for secret deletion in the stabilize operation of namespace delete handler (#60)
1 parent 3cf0fcf commit 7e9d835

File tree

22 files changed

+159
-42
lines changed

22 files changed

+159
-42
lines changed

aws-redshiftserverless-namespace/.rpdk-config

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@
2020
],
2121
"codegen_template_path": "guided_aws",
2222
"protocolVersion": "2.0.0"
23-
}
23+
},
24+
"executableEntrypoint": "software.amazon.redshiftserverless.namespace.HandlerWrapperExecutable"
2425
}

aws-redshiftserverless-namespace/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ The code uses [Lombok](https://projectlombok.org/), and [you may have to install
3131
## Note: This should be tested in the same region as your deployed resource.
3232
## This would just test the resource handler changes and deploy resource in your account. This uses your local aws credentials
3333
## The permissions provided here doesn't truly represent the necessary permission defined in the schema and need to be updated before you execute contract tests.
34-
## References:
34+
## References:
3535
## - https://w.amazon.com/bin/view/AWS21/Design/Uluru/Onboarding_Guide/Uluru_OpenSource_And_Developing_In_Amazon/IntegrationTests/
3636

3737
1. cd <resource_folder> && source <virtualenvfolder>/bin/activate && sam local start-lambda
@@ -51,7 +51,7 @@ The code uses [Lombok](https://projectlombok.org/), and [you may have to install
5151
## References:
5252
## - https://docs.aws.amazon.com/cli/latest/reference/cloudformation/create-stack.html
5353

54-
1. cp <resource_folder>/local-test-artifacts/create-<resource>.yaml cp local-test-artifacts/create-<resource>-<feature>.json
54+
1. cp <resource_folder>/local-test-artifacts/create-<resource>.yaml cp local-test-artifacts/create-<resource>-<feature>.json
5555
2. cd <resource_folder> && aws cloudformation create-stack --stack-name <stack-name> --template-body file://local-test-artifacts/create-<resource>-<feature>.yaml> --region <region>
5656
3. Verify if the cloudformation template is deployed successfully into your account.
5757

aws-redshiftserverless-namespace/docs/namespace.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,3 @@ _Required_: No
3737
_Type_: String
3838

3939
_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
40-

aws-redshiftserverless-namespace/docs/tag.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,3 @@ _Type_: String
4343
_Maximum Length_: <code>256</code>
4444

4545
_Update requires_: [No interruption](https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/using-cfn-updating-stacks-update-behaviors.html#update-no-interrupt)
46-

aws-redshiftserverless-namespace/pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,12 @@
4343
<artifactId>redshift</artifactId>
4444
<version>2.22.5</version>
4545
</dependency>
46+
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/secretsmanager -->
47+
<dependency>
48+
<groupId>software.amazon.awssdk</groupId>
49+
<artifactId>secretsmanager</artifactId>
50+
<version>2.22.5</version>
51+
</dependency>
4652
<dependency>
4753
<groupId>software.amazon.awssdk</groupId>
4854
<artifactId>aws-core</artifactId>

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/BaseHandlerStd.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@
2323
import software.amazon.cloudformation.proxy.ProxyClient;
2424
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
2525
import software.amazon.cloudformation.proxy.delay.Constant;
26+
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
27+
import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretRequest;
28+
import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretResponse;
29+
import com.amazonaws.util.StringUtils;
2630

2731
import java.time.Duration;
2832

@@ -44,6 +48,7 @@ public final ProgressEvent<ResourceModel, CallbackContext> handleRequest(
4448
callbackContext != null ? callbackContext : new CallbackContext(),
4549
proxy.newProxy(ClientBuilder::getClient),
4650
proxy.newProxy(ClientBuilder::redshiftClient),
51+
proxy.newProxy(ClientBuilder::secretsManagerClient),
4752
logger
4853
);
4954
}
@@ -54,6 +59,7 @@ protected abstract ProgressEvent<ResourceModel, CallbackContext> handleRequest(
5459
final CallbackContext callbackContext,
5560
final ProxyClient<RedshiftServerlessClient> proxyClient,
5661
final ProxyClient<RedshiftClient> redshiftProxyClient,
62+
final ProxyClient<SecretsManagerClient> secretsManagerProxyClient,
5763
final Logger logger);
5864

5965
protected boolean isNamespaceActive (final ProxyClient<RedshiftServerlessClient> proxyClient, ResourceModel resourceModel, CallbackContext context) {
@@ -77,6 +83,24 @@ protected boolean isNamespaceActiveAfterDelete (final ProxyClient<RedshiftServer
7783
return false;
7884
}
7985

86+
protected boolean isNamespaceSecretDeleted (final ProxyClient<SecretsManagerClient> secretsManagerProxyClient, CallbackContext context) {
87+
String namespaceSecretArn = context.getAdminPasswordSecretArn();
88+
89+
// For namespaces that aren't opted in to Redshift Managed Passwords, AdminPasswordSecretArn is null
90+
if (StringUtils.isNullOrEmpty(namespaceSecretArn)) {
91+
return true;
92+
}
93+
94+
DescribeSecretRequest describeSecretRequest = DescribeSecretRequest.builder().secretId(namespaceSecretArn).build();
95+
try {
96+
secretsManagerProxyClient.injectCredentialsAndInvokeV2(describeSecretRequest, secretsManagerProxyClient.client()::describeSecret);
97+
} catch (final software.amazon.awssdk.services.secretsmanager.model.ResourceNotFoundException e) {
98+
logger.log(String.format("Secret %s has successfully been deleted.", namespaceSecretArn));
99+
return true;
100+
}
101+
return false;
102+
}
103+
80104
protected ListSnapshotCopyConfigurationsResponse listSnapshotCopyConfigurations(final ListSnapshotCopyConfigurationsRequest listRequest,
81105
final ProxyClient<RedshiftServerlessClient> proxyClient) {
82106
ListSnapshotCopyConfigurationsResponse listResponse = proxyClient.injectCredentialsAndInvokeV2(listRequest, proxyClient.client()::listSnapshotCopyConfigurations);
@@ -92,6 +116,21 @@ protected CreateSnapshotCopyConfigurationResponse createSnapshotCopyConfiguratio
92116
return createResponse;
93117
}
94118

119+
protected String getNamespaceSecretArn(final ProxyClient<RedshiftServerlessClient> proxyClient, final String namespaceName) {
120+
String namespaceSecretArn = null;
121+
GetNamespaceResponse getNamespaceResponse = null;
122+
123+
GetNamespaceRequest getNamespaceRequest = GetNamespaceRequest.builder().namespaceName(namespaceName).build();
124+
125+
try {
126+
getNamespaceResponse = proxyClient.injectCredentialsAndInvokeV2(getNamespaceRequest, proxyClient.client()::getNamespace);
127+
namespaceSecretArn = getNamespaceResponse.namespace().adminPasswordSecretArn();
128+
} catch (final ResourceNotFoundException e) {
129+
// do nothing here, we will handle this in .handleError part of the handler instead
130+
}
131+
return namespaceSecretArn;
132+
}
133+
95134
protected <T> ProgressEvent<ResourceModel, CallbackContext> defaultErrorHandler(final T request,
96135
final Exception exception,
97136
final ProxyClient<RedshiftServerlessClient> client,

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/CallbackContext.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
public class CallbackContext extends StdCallbackContext {
1010
String namespaceArn = null;
1111
boolean callBackForDelete = false;
12+
String adminPasswordSecretArn = null;
1213

1314
public void setNamespaceArn(String namespaceArn) {this.namespaceArn = namespaceArn; }
1415

@@ -21,4 +22,12 @@ public void setCallBackForDelete(boolean callBackForDelete) {
2122
public boolean getCallBackForDelete() {
2223
return callBackForDelete;
2324
}
25+
26+
public String getAdminPasswordSecretArn() {
27+
return adminPasswordSecretArn;
28+
}
29+
30+
public void setAdminPasswordSecretArn(String adminPasswordSecretArn) {
31+
this.adminPasswordSecretArn = adminPasswordSecretArn;
32+
}
2433
}

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/ClientBuilder.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import software.amazon.awssdk.services.redshift.RedshiftClient;
44
import software.amazon.awssdk.services.redshiftserverless.RedshiftServerlessClient;
5+
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
56
import software.amazon.cloudformation.LambdaWrapper;
67

78
import java.util.function.Supplier;
@@ -18,4 +19,9 @@ public static RedshiftClient redshiftClient() {
1819
.httpClient(LambdaWrapper.HTTP_CLIENT)
1920
.build();
2021
}
22+
public static SecretsManagerClient secretsManagerClient() {
23+
return SecretsManagerClient.builder()
24+
.httpClient(LambdaWrapper.HTTP_CLIENT)
25+
.build();
26+
}
2127
}

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/CreateHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import software.amazon.cloudformation.proxy.ProgressEvent;
2121
import software.amazon.cloudformation.proxy.ProxyClient;
2222
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
23+
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
2324

2425
import java.util.Collections;
2526
import java.util.Optional;
@@ -32,6 +33,7 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
3233
final CallbackContext callbackContext,
3334
final ProxyClient<RedshiftServerlessClient> proxyClient,
3435
final ProxyClient<RedshiftClient> redshiftProxyClient,
36+
final ProxyClient<SecretsManagerClient> secretsManagerProxyClient,
3537
final Logger logger) {
3638

3739
this.logger = logger;
@@ -68,7 +70,7 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
6870
return progress;
6971
})
7072
.then(progress ->
71-
new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, redshiftProxyClient, logger)
73+
new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, redshiftProxyClient, secretsManagerProxyClient, logger)
7274
);
7375
}
7476

aws-redshiftserverless-namespace/src/main/java/software/amazon/redshiftserverless/namespace/DeleteHandler.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
import software.amazon.cloudformation.proxy.ProgressEvent;
1010
import software.amazon.cloudformation.proxy.ProxyClient;
1111
import software.amazon.cloudformation.proxy.ResourceHandlerRequest;
12+
import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient;
13+
import software.amazon.awssdk.services.redshiftserverless.model.GetNamespaceRequest;
14+
import software.amazon.awssdk.services.redshiftserverless.model.GetNamespaceResponse;
15+
import software.amazon.awssdk.services.redshiftserverless.model.ResourceNotFoundException;
1216

1317
public class DeleteHandler extends BaseHandlerStd {
1418
protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
@@ -17,29 +21,31 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
1721
final CallbackContext callbackContext,
1822
final ProxyClient<RedshiftServerlessClient> proxyClient,
1923
final ProxyClient<RedshiftClient> redshiftProxyClient,
24+
final ProxyClient<SecretsManagerClient> secretsManagerProxyClient,
2025
final Logger logger) {
2126

2227
this.logger = logger;
2328

2429
final ResourceModel model = request.getDesiredResourceState();
30+
31+
// Set the secret ARN in the callback context. We will use this in the stabilize operation of this handler
32+
if (callbackContext.getAdminPasswordSecretArn() == null) {
33+
String adminPasswordSecretArn = getNamespaceSecretArn(proxyClient, model.getNamespaceName());
34+
callbackContext.setAdminPasswordSecretArn(adminPasswordSecretArn);
35+
logger.log(String.format("Set namespace secret ARN in callback context: %s", adminPasswordSecretArn));
36+
}
37+
2538
return ProgressEvent.progress(model, callbackContext)
2639
.then(progress ->
2740
proxy.initiate("AWS-RedshiftServerless-Namespace::Delete", proxyClient, model, callbackContext)
2841
.translateToServiceRequest(Translator::translateToDeleteRequest)
2942
.backoffDelay(BACKOFF_STRATEGY)
3043
.makeServiceCall(this::deleteNamespace)
31-
.stabilize((_awsRequest, _awsResponse, _client, _model, _context) -> isNamespaceActiveAfterDelete(_client, _model, _context))
44+
.stabilize((_awsRequest, _awsResponse, _client, _model, _context) -> isNamespaceActiveAfterDelete(_client, model, _context) &&
45+
isNamespaceSecretDeleted(secretsManagerProxyClient, _context))
3246
.handleError(this::defaultErrorHandler)
3347
.done(deleteNamespaceResponse -> {
3448
logger.log(String.format("%s %s deleted.",ResourceModel.TYPE_NAME, model.getNamespaceName()));
35-
// TODO: Need to add a stabilize operation to verify if secret is deleted
36-
// This is a temporary fix to handle deletion of secrets for managed passwords
37-
// Since deletion of secret is handled async CTv2 is failing even in SingleTestMode
38-
try {
39-
Thread.sleep(30000);
40-
} catch(InterruptedException ex) {
41-
Thread.currentThread().interrupt();
42-
}
4349
return ProgressEvent.defaultSuccessHandler(null);
4450
})
4551
);

0 commit comments

Comments
 (0)