Skip to content

Commit 370ac79

Browse files
sobrabkaiz-io
andauthored
added java example of how to override logical name allocation of resources (#1041)
Co-authored-by: Michael Kaiser <[email protected]> Co-authored-by: Michael Kaiser <[email protected]>
1 parent 90f8dbe commit 370ac79

File tree

8 files changed

+346
-0
lines changed

8 files changed

+346
-0
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.classpath.txt
2+
target
3+
.classpath
4+
.project
5+
.idea
6+
.settings
7+
.vscode
8+
*.iml
9+
10+
# CDK asset staging directory
11+
.cdk.staging
12+
cdk.out
13+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
# Custom Logical Names
2+
3+
<!--BEGIN STABILITY BANNER-->
4+
5+
---
6+
7+
![Stability: Stable](https://img.shields.io/badge/stability-Stable-success.svg?style=for-the-badge)
8+
9+
> **This is a stable example. It should successfully build out of the box**
10+
>
11+
> This example is built on Construct Libraries marked "Stable" and does not have any infrastructure prerequisites to build.
12+
13+
---
14+
<!--END STABILITY BANNER-->
15+
16+
This sample shows how you can override the behavior for allocating logical names to CloudFormation resources in the CDK.
17+
18+
It implements a feature that allows users to specify a prefix for all logical names using the `prefix` context key.
19+
20+
## Usage
21+
22+
1. Extend your stacks from [`BaseStack`](src/main/java/com/myorg/BaseStack.java) instead of `Stack`.
23+
2. Specify the context when calling the CLI through `--context prefix=PREFIX`.
24+
25+
## Implementation
26+
27+
The [`BaseStack`](src/main/java/com/myorg/BaseStack.java) class implements this custom behavior. Using a base stack is a
28+
common and recommended pattern for reusing policy within an organization.
29+
30+
Then, any stack that derives from [`BaseStack`](src/main/java/com/myorg/BaseStack.java) will automatically have this
31+
behavior.
32+
33+
## Build
34+
35+
To build this example, you need to be in this example's root directory. Then run the following:
36+
37+
```bash
38+
npm install -g aws-cdk
39+
npm install
40+
cdk synth
41+
```
42+
43+
This will install the necessary CDK, then this example's dependencies, and then build the CloudFormation template. The resulting CloudFormation template will be in the `cdk.out` directory.
44+
45+
## Deploy
46+
47+
Run `cdk deploy --context prefix=PREFIX`. This will deploy / redeploy your Stack to your AWS Account.
48+
49+
## Example
50+
51+
Without prefix:
52+
53+
```shell
54+
$ cdk synth -j
55+
{
56+
"Resources": {
57+
"MyTopic86869434": {
58+
"Type": "AWS::SNS::Topic"
59+
},
60+
"MyBucketF68F3FF0": {
61+
"Type": "AWS::S3::Bucket"
62+
}
63+
}
64+
}
65+
```
66+
67+
With prefix:
68+
69+
```shell
70+
$ cdk synth -j -c prefix="MyTeam"
71+
{
72+
"Resources": {
73+
"MyTeamMyTopic86869434": {
74+
"Type": "AWS::SNS::Topic"
75+
},
76+
"MyTeamMyBucketF68F3FF0": {
77+
"Type": "AWS::S3::Bucket"
78+
}
79+
}
80+
}
81+
```
82+
83+
## Useful commands
84+
85+
```
86+
* `mvn clean package` compile and run tests
87+
* `cdk ls` list all stacks in the app
88+
* `cdk synth` emits the synthesized CloudFormation template
89+
* `cdk deploy` deploy this stack to your default AWS account/region
90+
* `cdk diff` compare deployed stack with current state
91+
* `cdk docs` open CDK documentation
92+
```

java/custom-logical-names/cdk.json

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
{
2+
"app": "mvn -e -q compile exec:java",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"target",
11+
"pom.xml",
12+
"src/test"
13+
]
14+
},
15+
"context": {
16+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
17+
"@aws-cdk/core:checkSecretUsage": true,
18+
"@aws-cdk/core:target-partitions": [
19+
"aws",
20+
"aws-cn"
21+
],
22+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
23+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
24+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
25+
"@aws-cdk/aws-iam:minimizePolicies": true,
26+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
27+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
28+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
29+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
30+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
31+
"@aws-cdk/core:enablePartitionLiterals": true,
32+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
33+
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
34+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
35+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
36+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
37+
"@aws-cdk/aws-route53-patters:useCertificate": true,
38+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
39+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
40+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
41+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
42+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
43+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
44+
"@aws-cdk/aws-redshift:columnId": true,
45+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
46+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
47+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
48+
"@aws-cdk/aws-kms:aliasNameRef": true,
49+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
50+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
51+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
52+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
53+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
54+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
55+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
56+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
57+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
58+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
59+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
60+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
61+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
62+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
63+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
64+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
65+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true
66+
}
67+
}

java/custom-logical-names/pom.xml

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
3+
xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
4+
<modelVersion>4.0.0</modelVersion>
5+
6+
<groupId>com.myorg</groupId>
7+
<artifactId>custom-logical-names</artifactId>
8+
<version>0.1</version>
9+
10+
<properties>
11+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
12+
<cdk.version>2.143.0</cdk.version>
13+
<constructs.version>[10.0.0,11.0.0)</constructs.version>
14+
<junit.version>5.7.1</junit.version>
15+
</properties>
16+
17+
<build>
18+
<plugins>
19+
<plugin>
20+
<groupId>org.apache.maven.plugins</groupId>
21+
<artifactId>maven-compiler-plugin</artifactId>
22+
<version>3.11.0</version>
23+
<configuration>
24+
<release>17</release>
25+
</configuration>
26+
</plugin>
27+
28+
<plugin>
29+
<groupId>org.codehaus.mojo</groupId>
30+
<artifactId>exec-maven-plugin</artifactId>
31+
<version>3.1.0</version>
32+
<configuration>
33+
<mainClass>com.myorg.CustomLogicalNamesApp</mainClass>
34+
</configuration>
35+
</plugin>
36+
</plugins>
37+
</build>
38+
39+
<dependencies>
40+
<dependency>
41+
<groupId>software.amazon.awscdk</groupId>
42+
<artifactId>aws-cdk-lib</artifactId>
43+
<version>${cdk.version}</version>
44+
</dependency>
45+
<dependency>
46+
<groupId>software.constructs</groupId>
47+
<artifactId>constructs</artifactId>
48+
<version>${constructs.version}</version>
49+
</dependency>
50+
51+
<dependency>
52+
<groupId>org.junit.jupiter</groupId>
53+
<artifactId>junit-jupiter</artifactId>
54+
<version>${junit.version}</version>
55+
<scope>test</scope>
56+
</dependency>
57+
</dependencies>
58+
</project>
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.myorg;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.jetbrains.annotations.Nullable;
5+
import software.amazon.awscdk.CfnElement;
6+
import software.amazon.awscdk.Stack;
7+
import software.amazon.awscdk.StackProps;
8+
import software.constructs.Construct;
9+
10+
import java.util.Optional;
11+
12+
public class BaseStack extends Stack {
13+
14+
public BaseStack(@Nullable Construct scope, @Nullable String id, @Nullable StackProps props) {
15+
super(scope, id, props);
16+
}
17+
18+
@Override
19+
protected @NotNull String allocateLogicalId(@NotNull CfnElement element) {
20+
String originalLogicalId = super.allocateLogicalId(element);
21+
return Optional.of(this)
22+
.map(Stack::getNode)
23+
.flatMap(
24+
node -> Optional.of("prefix")
25+
.map(node::tryGetContext)
26+
.filter(String.class::isInstance)
27+
.map(String.class::cast)
28+
.map(StringBuilder::new)
29+
)
30+
.flatMap(
31+
stringBuilder -> Optional.of(originalLogicalId)
32+
.map(stringBuilder::append)
33+
.map(StringBuilder::toString)
34+
)
35+
.orElse(originalLogicalId);
36+
}
37+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.myorg;
2+
3+
import software.amazon.awscdk.App;
4+
import software.amazon.awscdk.StackProps;
5+
6+
public class CustomLogicalNamesApp {
7+
public static void main(final String[] args) {
8+
App app = new App();
9+
StackProps stackProps = StackProps.builder().build();
10+
new CustomLogicalNamesStack(app, "CustomLogicalNamesStack", stackProps);
11+
app.synth();
12+
}
13+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.myorg;
2+
3+
import software.amazon.awscdk.StackProps;
4+
import software.amazon.awscdk.services.s3.Bucket;
5+
import software.amazon.awscdk.services.sns.Topic;
6+
import software.constructs.Construct;
7+
8+
public class CustomLogicalNamesStack extends BaseStack {
9+
public CustomLogicalNamesStack(final Construct scope, final String id, final StackProps props) {
10+
super(scope, id, props);
11+
new Topic(this, "MyTopic");
12+
new Bucket(this, "MyBucket");
13+
}
14+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package com.myorg;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.junit.jupiter.api.Assertions;
5+
import org.junit.jupiter.api.Test;
6+
import software.amazon.awscdk.App;
7+
import software.amazon.awscdk.Stack;
8+
import software.amazon.awscdk.StackProps;
9+
import software.amazon.awscdk.assertions.Template;
10+
11+
import java.util.HashSet;
12+
import java.util.Map;
13+
import java.util.Optional;
14+
import java.util.Set;
15+
16+
import static software.amazon.awscdk.App.Builder;
17+
18+
public class CustomLogicalNamesTest {
19+
20+
@Test
21+
public void testStack() {
22+
String prefixKey = "prefix";
23+
String prefixValue = "YourTeam";
24+
App app = Builder.create()
25+
.context(Map.of(prefixKey, prefixValue))
26+
.build();
27+
StackProps stackProps = StackProps.builder().build();
28+
CustomLogicalNamesStack customLogicalNamesStack = new CustomLogicalNamesStack(app, "test", stackProps);
29+
Set<String> resourcesNames = extractResourcesNamesFormStack(customLogicalNamesStack);
30+
// check if the prefix supplied on the context is used for all the resources in the stack
31+
Assertions.assertTrue(resourcesNames.stream().allMatch(name -> name != null && name.startsWith(prefixValue)));
32+
}
33+
34+
private @NotNull Set<String> extractResourcesNamesFormStack(Stack stack) {
35+
var rawNamesSet = Optional.of(stack)
36+
.map(Template::fromStack)
37+
.map(Template::toJSON)
38+
.map(json -> json.get("Resources"))
39+
.filter(Map.class::isInstance)
40+
.map(Map.class::cast)
41+
.map(Map::keySet)
42+
.orElseGet(Set::of);
43+
Set<String> resourcesNames = new HashSet<>();
44+
for (Object resource : rawNamesSet) {
45+
Optional.of(resource)
46+
.filter(String.class::isInstance)
47+
.map(String.class::cast)
48+
.ifPresent(resourcesNames::add);
49+
}
50+
return resourcesNames;
51+
}
52+
}

0 commit comments

Comments
 (0)