Skip to content

Commit 731cb61

Browse files
author
Yuriy Bezsonov
committed
Refactoring CDK
1 parent 395cfe9 commit 731cb61

9 files changed

Lines changed: 77 additions & 109 deletions

File tree

apps/java25/jvm-ai-analyzer/pom.xml

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
2424
<spring-ai.version>1.1.1</spring-ai.version>
2525
<testcontainers.version>2.0.3</testcontainers.version>
26-
<resilience4j.version>2.3.0</resilience4j.version>
2726
</properties>
2827

2928
<dependencyManagement>
@@ -87,13 +86,6 @@
8786
</exclusions>
8887
</dependency>
8988

90-
<!-- Resilience4j for retry -->
91-
<dependency>
92-
<groupId>io.github.resilience4j</groupId>
93-
<artifactId>resilience4j-spring-boot3</artifactId>
94-
<version>${resilience4j.version}</version>
95-
</dependency>
96-
9789
<!-- Observability -->
9890
<dependency>
9991
<groupId>io.micrometer</groupId>

apps/java25/jvm-ai-analyzer/src/main/java/com/unicorn/jvm/AnalyzerService.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.unicorn.jvm;
22

3-
import io.github.resilience4j.retry.annotation.Retry;
43
import org.slf4j.Logger;
54
import org.slf4j.LoggerFactory;
65
import org.springframework.beans.factory.annotation.Value;
@@ -66,20 +65,18 @@ private void processAlert(AlertWebhookRequest.Alert alert) {
6665
}
6766
}
6867

69-
@Retry(name = "threadDump", fallbackMethod = "getThreadDumpFallback")
7068
String getThreadDump(String podIp) {
7169
var url = threadDumpUrlTemplate.replace("{podIp}", podIp);
7270
logger.info("Fetching thread dump from: {}", url);
7371

74-
return restClient.get()
75-
.uri(url)
76-
.retrieve()
77-
.body(String.class);
78-
}
79-
80-
@SuppressWarnings("unused") // Used by Resilience4j
81-
String getThreadDumpFallback(String podIp, Exception ex) {
82-
logger.warn("Failed to get thread dump for pod IP {} after retries: {}", podIp, ex.getMessage());
83-
return "Failed to retrieve thread dump: " + ex.getMessage();
72+
try {
73+
return restClient.get()
74+
.uri(url)
75+
.retrieve()
76+
.body(String.class);
77+
} catch (Exception e) {
78+
logger.warn("Failed to get thread dump for pod IP {}: {}", podIp, e.getMessage());
79+
return "Failed to retrieve thread dump: " + e.getMessage();
80+
}
8481
}
8582
}

apps/java25/jvm-ai-analyzer/src/test/java/com/unicorn/jvm/integration/TestInfrastructureInitializer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public void beforeAll(final ExtensionContext context) {
3232
}
3333
}
3434

35+
@SuppressWarnings("resource")
3536
private void initializeTestcontainers() {
3637
try {
3738
if (localstack == null) {

apps/java25/unicorn-store-spring/src/test/java/com/unicorn/store/integration/TestInfrastructureInitializer.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ public void beforeAll(final ExtensionContext context) {
3232
}
3333
}
3434

35+
@SuppressWarnings("resource")
3536
private void initializeTestcontainers() {
3637
try {
3738
// Start PostgreSQL container with reuse for faster tests

infra/cdk/src/main/java/sample/com/WorkshopStack.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public WorkshopStack(final Construct scope, final String id, final StackProps pr
5858
.vpc(vpc.getVpc())
5959
.gitBranch(gitBranch)
6060
.templateType(templateType)
61-
.ideArch(Ide.IdeArch.X86_64)
6261
.build();
6362
Ide ide = new Ide(this, "Ide", ideProps);
6463

infra/cdk/src/main/java/sample/com/constructs/CodeBuild.java

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -172,22 +172,6 @@ public CodeBuild(final Construct scope, final String id, final CodeBuildProps pr
172172
this.customResource.getNode().addDependency(reportBuildFunction);
173173
}
174174

175-
private String calculateMd5(String input) {
176-
try {
177-
MessageDigest md = MessageDigest.getInstance("MD5");
178-
byte[] hash = md.digest(input.getBytes("UTF-8"));
179-
StringBuilder hexString = new StringBuilder();
180-
for (byte b : hash) {
181-
String hex = Integer.toHexString(0xff & b);
182-
if (hex.length() == 1) hexString.append('0');
183-
hexString.append(hex);
184-
}
185-
return hexString.toString();
186-
} catch (Exception e) {
187-
throw new RuntimeException(e);
188-
}
189-
}
190-
191175
public Project getCodeBuildProject() {
192176
return this.codebuildProject;
193177
}

infra/cdk/src/main/java/sample/com/constructs/Database.java

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package sample.com.constructs;
22

33
import software.amazon.awscdk.RemovalPolicy;
4-
import software.amazon.awscdk.SecretValue;
5-
import software.amazon.awscdk.SecretsManagerSecretOptions;
64
import software.amazon.awscdk.services.ec2.IVpc;
75
import software.amazon.awscdk.services.ec2.Port;
86
import software.amazon.awscdk.services.ec2.Peer;
@@ -21,7 +19,6 @@
2119

2220
import software.amazon.awscdk.services.ssm.ParameterTier;
2321
import software.amazon.awscdk.services.ssm.StringParameter;
24-
import software.amazon.awscdk.services.secretsmanager.Secret;
2522
import software.constructs.Construct;
2623

2724
import java.util.List;
@@ -37,7 +34,6 @@ public class Database extends Construct {
3734
private final ISecurityGroup databaseSecurityGroup;
3835

3936
private final StringParameter paramDBConnectionString;
40-
private final Secret secretPassword;
4137

4238
public static class DatabaseProps {
4339
private String prefix = "workshop";
@@ -110,13 +106,6 @@ public Database(final Construct scope, final String id, final DatabaseProps prop
110106
.removalPolicy(RemovalPolicy.DESTROY)
111107
.build();
112108

113-
// Create separate password secret for services that need plain password
114-
secretPassword = Secret.Builder.create(this, "PasswordSecret")
115-
.secretName(prefix + "-db-password-secret")
116-
.secretStringValue(SecretValue.secretsManager(databaseSecret.getSecretName(),
117-
SecretsManagerSecretOptions.builder().jsonField("password").build()))
118-
.build();
119-
120109
// Create parameter store entry for connection string
121110
paramDBConnectionString = StringParameter.Builder.create(this, "ConnectionString")
122111
.allowedPattern(".*")
@@ -148,10 +137,6 @@ public StringParameter getParamDBConnectionString() {
148137
return paramDBConnectionString;
149138
}
150139

151-
public Secret getSecretPassword() {
152-
return secretPassword;
153-
}
154-
155140
public String getDatabaseSecretString() {
156141
return databaseSecret.secretValueFromJson("password").toString();
157142
}

infra/cdk/src/main/java/sample/com/constructs/Ide.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public static class IdeProps {
8686
private String instanceName = "ide";
8787
private int diskSize = 50;
8888
private IVpc vpc;
89-
private IdeArch ideArch = IdeArch.ARM64;
89+
private IdeArch ideArch = IdeArch.X86_64;
9090
private IdeType ideType = IdeType.CODE_EDITOR;
9191
private List<ISecurityGroup> additionalSecurityGroups = new ArrayList<>();
9292
private int bootstrapTimeoutMinutes = 30;
@@ -99,8 +99,6 @@ public static class IdeProps {
9999
Arrays.asList("m7g.xlarge", "m6g.xlarge", "c7g.xlarge", "t4g.xlarge");
100100
private static final List<String> X86_64_INSTANCE_TYPES =
101101
Arrays.asList("m6i.xlarge", "m5.xlarge", "m6a.xlarge", "m7i-flex.xlarge", "m7a.xlarge", "t3.xlarge");
102-
// Old instance list (x86_64) for reference:
103-
// Arrays.asList("m7i-flex.xlarge", "m7a.xlarge", "m6i.xlarge", "m6a.xlarge", "m5.xlarge", "t3.xlarge");
104102

105103
public static IdeProps.Builder builder() { return new Builder(); }
106104

infra/cdk/src/main/java/sample/com/constructs/Unicorn.java

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import software.amazon.awscdk.RemovalPolicy;
66
import software.amazon.awscdk.services.ecr.Repository;
77
import software.amazon.awscdk.services.ecr.TagMutability;
8+
import software.amazon.awscdk.services.events.EventBus;
89
import software.amazon.awscdk.services.iam.*;
910
import software.amazon.awscdk.services.lambda.Code;
1011
import software.amazon.awscdk.services.lambda.Function;
@@ -24,19 +25,27 @@
2425
*
2526
* Contains:
2627
* - ECR repository (unicorn-store-spring)
28+
* - EventBus (unicorns)
2729
* - EKS Pod Identity role (unicornstore-eks-pod-role)
28-
* - ECS task roles (unicornstore-ecs-task-role, unicornstore-ecs-task-execution-role)
30+
* - ECS Express Mode roles:
31+
* - Infrastructure role (unicornstore-ecs-infrastructure-role)
32+
* - Task execution role (unicornstore-ecs-task-execution-role)
33+
* - Task role (unicornstore-ecs-task-role)
2934
* - Database schema setup (unicorns table)
3035
*/
3136
public class Unicorn extends Construct {
3237

3338
// ECR Repository
3439
private Repository ecrRepository;
3540

41+
// EventBus
42+
private EventBus eventBus;
43+
3644
// EKS Roles
3745
private Role eksPodRole;
3846

3947
// ECS Roles
48+
private Role ecsInfrastructureRole;
4049
private Role ecsTaskRole;
4150
private Role ecsTaskExecutionRole;
4251

@@ -55,6 +64,11 @@ public Unicorn(final Construct scope, final String id, final UnicornProps props)
5564
.emptyOnDelete(true)
5665
.build();
5766

67+
// === EVENTBUS ===
68+
this.eventBus = EventBus.Builder.create(this, "UnicornEventBus")
69+
.eventBusName("unicorns")
70+
.build();
71+
5872
// === EKS ROLES ===
5973
if (props.isEksRolesEnabled()) {
6074
createEksRoles(props);
@@ -115,9 +129,9 @@ private String loadFile(String filePath) {
115129
* Creates EKS Pod Identity role with permissions for:
116130
* - X-Ray tracing
117131
* - CloudWatch metrics
118-
* - Bedrock AI access
119132
* - S3 bucket access
120133
* - Database secrets access
134+
* - EventBridge PutEvents
121135
*/
122136
private void createEksRoles(UnicornProps props) {
123137
ServicePrincipal eksPods = ServicePrincipal.Builder.create("pods.eks.amazonaws.com")
@@ -149,9 +163,6 @@ private void createEksRoles(UnicornProps props) {
149163
// CloudWatch Agent
150164
eksPodRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("CloudWatchAgentServerPolicy"));
151165

152-
// Bedrock AI access
153-
eksPodRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AmazonBedrockLimitedAccess"));
154-
155166
// S3 bucket access (if provided)
156167
if (props.getWorkshopBucket() != null) {
157168
eksPodRole.addToPolicy(PolicyStatement.Builder.create()
@@ -169,91 +180,91 @@ private void createEksRoles(UnicornProps props) {
169180
props.getDatabase().getDatabaseSecret().grantRead(eksPodRole);
170181
props.getDatabase().getParamDBConnectionString().grantRead(eksPodRole);
171182
}
183+
184+
// EventBridge access
185+
eventBus.grantPutEventsTo(eksPodRole);
172186
}
173187

174188
/**
175-
* Creates ECS task roles with permissions for:
176-
* - X-Ray tracing
177-
* - CloudWatch logs
178-
* - SSM parameter access
179-
* - Database secrets access
189+
* Creates ECS roles for Express Mode (Fargate):
190+
* - Infrastructure role: manages ALB, security groups, auto scaling
191+
* - Task execution role: pulls images, writes logs, injects secrets
192+
* - Task role: app runtime permissions (X-Ray, EventBridge)
180193
*/
181194
private void createEcsRoles(UnicornProps props) {
182-
ServicePrincipal ecsTasks = ServicePrincipal.Builder.create("ecs-tasks.amazonaws.com")
183-
.build();
195+
// === ECS Infrastructure Role (for Express Mode) ===
196+
ServicePrincipal ecsService = ServicePrincipal.Builder.create("ecs.amazonaws.com").build();
184197

185-
// OpenTelemetry policy for observability
186-
PolicyStatement otelPolicy = PolicyStatement.Builder.create()
187-
.effect(Effect.ALLOW)
188-
.actions(List.of(
189-
"logs:PutLogEvents", "logs:CreateLogGroup", "logs:CreateLogStream",
190-
"logs:DescribeLogStreams", "logs:DescribeLogGroups", "logs:PutRetentionPolicy",
191-
"xray:PutTraceSegments", "xray:PutTelemetryRecords",
192-
"xray:GetSamplingRules", "xray:GetSamplingTargets", "xray:GetSamplingStatisticSummaries",
193-
"cloudwatch:PutMetricData", "ssm:GetParameters"
194-
))
195-
.resources(List.of("*"))
198+
this.ecsInfrastructureRole = Role.Builder.create(this, "UnicornStoreEcsInfrastructureRole")
199+
.roleName("unicornstore-ecs-infrastructure-role")
200+
.assumedBy(ecsService)
201+
.description("ECS infrastructure role for Express Mode services")
196202
.build();
197203

198-
// ECS Task Role
199-
this.ecsTaskRole = Role.Builder.create(this, "UnicornStoreEcsTaskRole")
200-
.roleName("unicornstore-ecs-task-role")
204+
ecsInfrastructureRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(
205+
"service-role/AmazonECSInfrastructureRoleforExpressGatewayServices"));
206+
207+
// === ECS Task Execution Role ===
208+
ServicePrincipal ecsTasks = ServicePrincipal.Builder.create("ecs-tasks.amazonaws.com").build();
209+
210+
this.ecsTaskExecutionRole = Role.Builder.create(this, "UnicornStoreEcsTaskExecutionRole")
211+
.roleName("unicornstore-ecs-task-execution-role")
201212
.assumedBy(ecsTasks)
202-
.description("ECS task role for Unicorn Store application")
213+
.description("ECS task execution role for pulling images and injecting secrets")
203214
.build();
204215

205-
ecsTaskRole.addToPolicy(PolicyStatement.Builder.create()
216+
// Base permissions: ECR pull + CloudWatch logs
217+
ecsTaskExecutionRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName(
218+
"service-role/AmazonECSTaskExecutionRolePolicy"));
219+
220+
// Allow creating log groups (not in managed policy)
221+
ecsTaskExecutionRole.addToPolicy(PolicyStatement.Builder.create()
206222
.effect(Effect.ALLOW)
207-
.actions(List.of("xray:PutTraceSegments"))
223+
.actions(List.of("logs:CreateLogGroup"))
208224
.resources(List.of("*"))
209225
.build());
210226

211-
ecsTaskRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess"));
212-
ecsTaskRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMReadOnlyAccess"));
213-
ecsTaskRole.addToPolicy(otelPolicy);
214-
215-
// Database secrets access (if provided)
227+
// Database secrets injection at container startup (scoped)
216228
if (props.getDatabase() != null) {
217-
props.getDatabase().getDatabaseSecret().grantRead(ecsTaskRole);
218-
props.getDatabase().getSecretPassword().grantRead(ecsTaskRole);
219-
props.getDatabase().getParamDBConnectionString().grantRead(ecsTaskRole);
229+
props.getDatabase().getDatabaseSecret().grantRead(ecsTaskExecutionRole);
230+
props.getDatabase().getParamDBConnectionString().grantRead(ecsTaskExecutionRole);
220231
}
221232

222-
// ECS Task Execution Role
223-
this.ecsTaskExecutionRole = Role.Builder.create(this, "UnicornStoreEcsTaskExecutionRole")
224-
.roleName("unicornstore-ecs-task-execution-role")
233+
// === ECS Task Role (app runtime permissions) ===
234+
this.ecsTaskRole = Role.Builder.create(this, "UnicornStoreEcsTaskRole")
235+
.roleName("unicornstore-ecs-task-role")
225236
.assumedBy(ecsTasks)
226-
.description("ECS task execution role for Unicorn Store application")
237+
.description("ECS task role for application runtime permissions")
227238
.build();
228239

229-
ecsTaskExecutionRole.addToPolicy(PolicyStatement.Builder.create()
240+
// X-Ray tracing
241+
ecsTaskRole.addToPolicy(PolicyStatement.Builder.create()
230242
.effect(Effect.ALLOW)
231-
.actions(List.of("logs:CreateLogGroup"))
243+
.actions(List.of("xray:PutTraceSegments"))
232244
.resources(List.of("*"))
233245
.build());
234246

235-
ecsTaskExecutionRole.addManagedPolicy(
236-
ManagedPolicy.fromAwsManagedPolicyName("service-role/AmazonECSTaskExecutionRolePolicy"));
237-
ecsTaskExecutionRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("CloudWatchLogsFullAccess"));
238-
ecsTaskExecutionRole.addManagedPolicy(ManagedPolicy.fromAwsManagedPolicyName("AmazonSSMReadOnlyAccess"));
239-
ecsTaskExecutionRole.addToPolicy(otelPolicy);
240-
241-
// Database secrets access for execution role (if provided)
242-
if (props.getDatabase() != null) {
243-
props.getDatabase().getDatabaseSecret().grantRead(ecsTaskExecutionRole);
244-
props.getDatabase().getSecretPassword().grantRead(ecsTaskExecutionRole);
245-
}
247+
// EventBridge access
248+
eventBus.grantPutEventsTo(ecsTaskRole);
246249
}
247250

248251
// Getters
249252
public Repository getEcrRepository() {
250253
return ecrRepository;
251254
}
252255

256+
public EventBus getEventBus() {
257+
return eventBus;
258+
}
259+
253260
public Role getEksPodRole() {
254261
return eksPodRole;
255262
}
256263

264+
public Role getEcsInfrastructureRole() {
265+
return ecsInfrastructureRole;
266+
}
267+
257268
public Role getEcsTaskRole() {
258269
return ecsTaskRole;
259270
}

0 commit comments

Comments
 (0)