Skip to content

Commit 60aa30e

Browse files
author
Yuriy Bezsonov
committed
new infra
1 parent 83b579f commit 60aa30e

11 files changed

Lines changed: 685 additions & 797 deletions

File tree

.kiro/specs/infra/deployment-guide.md

Lines changed: 0 additions & 66 deletions
This file was deleted.
-256 KB
Binary file not shown.

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,8 +88,9 @@ public WorkshopStack(final Construct scope, final String id, final StackProps pr
8888
.profilingAnalysisEnabled(true)
8989
.build());
9090

91-
// Unicorn construct: ECR + Roles (uses unicorn* naming for workshop content compatibility)
91+
// Unicorn construct: ECR + Roles + DB Setup (uses unicorn* naming for workshop content compatibility)
9292
Unicorn unicorn = new Unicorn(this, "Unicorn", Unicorn.UnicornProps.builder()
93+
.vpc(vpc.getVpc())
9394
.eksRolesEnabled(true)
9495
.ecsRolesEnabled(false)
9596
.database(database)

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

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

3-
import software.amazon.awscdk.Duration;
43
import software.amazon.awscdk.RemovalPolicy;
54
import software.amazon.awscdk.SecretValue;
65
import software.amazon.awscdk.SecretsManagerSecretOptions;
7-
import software.amazon.awscdk.CustomResource;
86
import software.amazon.awscdk.services.ec2.IVpc;
97
import software.amazon.awscdk.services.ec2.Port;
108
import software.amazon.awscdk.services.ec2.Peer;
119
import software.amazon.awscdk.services.ec2.SecurityGroup;
1210
import software.amazon.awscdk.services.ec2.SubnetSelection;
1311
import software.amazon.awscdk.services.ec2.SubnetType;
1412
import software.amazon.awscdk.services.ec2.ISecurityGroup;
15-
import software.amazon.awscdk.services.lambda.Code;
16-
import software.amazon.awscdk.services.lambda.Function;
17-
import software.amazon.awscdk.services.lambda.Runtime;
1813
import software.amazon.awscdk.services.rds.AuroraPostgresClusterEngineProps;
1914
import software.amazon.awscdk.services.rds.ServerlessV2ClusterInstanceProps;
2015
import software.amazon.awscdk.services.rds.AuroraPostgresEngineVersion;
@@ -29,12 +24,12 @@
2924
import software.amazon.awscdk.services.secretsmanager.Secret;
3025
import software.constructs.Construct;
3126

32-
import java.io.IOException;
33-
import java.nio.file.Files;
34-
import java.nio.file.Path;
3527
import java.util.List;
36-
import java.util.Map;
3728

29+
/**
30+
* Database construct for workshop infrastructure.
31+
* Creates Aurora PostgreSQL Serverless v2 cluster with supporting resources.
32+
*/
3833
public class Database extends Construct {
3934

4035
private final DatabaseSecret databaseSecret;
@@ -43,7 +38,6 @@ public class Database extends Construct {
4338

4439
private final StringParameter paramDBConnectionString;
4540
private final Secret secretPassword;
46-
private final CustomResource databaseSetupResource;
4741

4842
public Database(final Construct scope, final String id, final IVpc vpc) {
4943
super(scope, id);
@@ -67,8 +61,6 @@ public Database(final Construct scope, final String id, final IVpc vpc) {
6761
Port.tcp(5432),
6862
"Allow Database Traffic from local network");
6963

70-
71-
7264
// Create Aurora PostgreSQL cluster with universal naming
7365
database = DatabaseCluster.Builder.create(this, "Cluster")
7466
.engine(DatabaseClusterEngine.auroraPostgres(
@@ -108,39 +100,6 @@ public Database(final Construct scope, final String id, final IVpc vpc) {
108100
.stringValue(getConnectionString())
109101
.tier(ParameterTier.STANDARD)
110102
.build();
111-
112-
// Create database setup Lambda function
113-
Function databaseSetupFunction = Function.Builder.create(this, "SetupFunction")
114-
.code(Code.fromInline(loadFile("/lambda/database-setup.py")))
115-
.handler("index.lambda_handler")
116-
.runtime(Runtime.PYTHON_3_13)
117-
.functionName("workshop-database-setup")
118-
.timeout(Duration.minutes(3))
119-
.vpc(vpc)
120-
.securityGroups(List.of(databaseSecurityGroup))
121-
.build();
122-
123-
// Grant permissions to setup function
124-
databaseSecret.grantRead(databaseSetupFunction);
125-
database.grantDataApiAccess(databaseSetupFunction);
126-
127-
// Create custom resource for database setup
128-
databaseSetupResource = CustomResource.Builder.create(this, "SetupResource")
129-
.serviceToken(databaseSetupFunction.getFunctionArn())
130-
.properties(Map.of(
131-
"SecretName", databaseSecret.getSecretName(),
132-
"SqlStatements", loadFile("/schema.sql")
133-
))
134-
.build();
135-
databaseSetupResource.getNode().addDependency(database);
136-
}
137-
138-
private String loadFile(String filePath) {
139-
try {
140-
return Files.readString(Path.of(getClass().getResource(filePath).getPath()));
141-
} catch (IOException e) {
142-
throw new RuntimeException("Failed to load file " + filePath, e);
143-
}
144103
}
145104

146105
public String getConnectionString() {

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

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,6 @@ public PerformanceAnalysis(final Construct scope, final String id, final Perform
6363
.bucketName(String.format("workshop-%s-%s-%s", Aws.ACCOUNT_ID, Aws.REGION, timestamp))
6464
.blockPublicAccess(BlockPublicAccess.BLOCK_ALL)
6565
.removalPolicy(RemovalPolicy.DESTROY)
66-
.autoDeleteObjects(true)
6766
.build();
6867

6968
// Create SSM parameter for bucket name discovery
@@ -97,6 +96,13 @@ public PerformanceAnalysis(final Construct scope, final String id, final Perform
9796
))
9897
.build());
9998

99+
// Add Secrets Manager permission for IDE password (webhook auth)
100+
lambdaBedrockRole.addToPolicy(PolicyStatement.Builder.create()
101+
.effect(Effect.ALLOW)
102+
.actions(List.of("secretsmanager:GetSecretValue"))
103+
.resources(List.of("arn:aws:secretsmanager:*:*:secret:workshop-ide-password*"))
104+
.build());
105+
100106
// Add EKS access permissions
101107
lambdaBedrockRole.addToPolicy(PolicyStatement.Builder.create()
102108
.effect(Effect.ALLOW)
@@ -180,7 +186,8 @@ private void createThreadAnalysis(PerformanceAnalysisProps props) {
180186
"S3_THREAD_DUMPS_PREFIX", "thread-dumps/",
181187
"APP_LABEL", "unicorn-store-spring",
182188
"KUBERNETES_AUTH_TYPE", "aws",
183-
"K8S_NAMESPACE", "unicorn-store-spring"
189+
"K8S_NAMESPACE", "unicorn-store-spring",
190+
"SECRET_NAME", "workshop-ide-password"
184191
))
185192
.build();
186193

@@ -219,6 +226,9 @@ private void createThreadAnalysis(PerformanceAnalysisProps props) {
219226
.build())
220227
.build();
221228

229+
// Remove auto-generated endpoint output
230+
threadDumpApi.getNode().tryRemoveChild("Endpoint");
231+
222232
// Add POST method to root
223233
LambdaIntegration lambdaIntegration = LambdaIntegration.Builder.create(threadDumpLambda)
224234
.build();

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

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

3+
import software.amazon.awscdk.CustomResource;
4+
import software.amazon.awscdk.Duration;
35
import software.amazon.awscdk.RemovalPolicy;
46
import software.amazon.awscdk.services.ecr.Repository;
57
import software.amazon.awscdk.services.iam.*;
8+
import software.amazon.awscdk.services.lambda.Code;
9+
import software.amazon.awscdk.services.lambda.Function;
10+
import software.amazon.awscdk.services.lambda.Runtime;
611
import software.amazon.awscdk.services.s3.IBucket;
712
import software.constructs.Construct;
813

14+
import java.io.IOException;
15+
import java.nio.file.Files;
16+
import java.nio.file.Path;
917
import java.util.List;
18+
import java.util.Map;
1019

1120
/**
1221
* Unicorn construct for workshop-specific resources.
@@ -16,6 +25,7 @@
1625
* - ECR repository (unicorn-store-spring)
1726
* - EKS Pod Identity role (unicornstore-eks-pod-role)
1827
* - ECS task roles (unicornstore-ecs-task-role, unicornstore-ecs-task-execution-role)
28+
* - Database schema setup (unicorns table)
1929
*/
2030
public class Unicorn extends Construct {
2131

@@ -29,6 +39,9 @@ public class Unicorn extends Construct {
2939
private Role ecsTaskRole;
3040
private Role ecsTaskExecutionRole;
3141

42+
// Database Setup
43+
private CustomResource databaseSetupResource;
44+
3245
public Unicorn(final Construct scope, final String id, final UnicornProps props) {
3346
super(scope, id);
3447

@@ -49,6 +62,51 @@ public Unicorn(final Construct scope, final String id, final UnicornProps props)
4962
if (props.isEcsRolesEnabled()) {
5063
createEcsRoles(props);
5164
}
65+
66+
// === DATABASE SETUP ===
67+
if (props.getDatabase() != null) {
68+
createDatabaseSetup(props);
69+
}
70+
}
71+
72+
/**
73+
* Creates database setup Lambda and custom resource to initialize unicorns table.
74+
*/
75+
private void createDatabaseSetup(UnicornProps props) {
76+
Database database = props.getDatabase();
77+
78+
// Create database setup Lambda function
79+
Function databaseSetupFunction = Function.Builder.create(this, "DatabaseSetupFunction")
80+
.code(Code.fromInline(loadFile("/lambda/database-setup.py")))
81+
.handler("index.lambda_handler")
82+
.runtime(Runtime.PYTHON_3_13)
83+
.functionName("unicornstore-database-setup")
84+
.timeout(Duration.minutes(3))
85+
.vpc(props.getVpc())
86+
.securityGroups(List.of(database.getDatabaseSecurityGroup()))
87+
.build();
88+
89+
// Grant permissions to setup function
90+
database.getDatabaseSecret().grantRead(databaseSetupFunction);
91+
database.getDatabase().grantDataApiAccess(databaseSetupFunction);
92+
93+
// Create custom resource for database setup
94+
this.databaseSetupResource = CustomResource.Builder.create(this, "DatabaseSetupResource")
95+
.serviceToken(databaseSetupFunction.getFunctionArn())
96+
.properties(Map.of(
97+
"SecretName", database.getDatabaseSecret().getSecretName(),
98+
"SqlStatements", loadFile("/unicorns.sql")
99+
))
100+
.build();
101+
databaseSetupResource.getNode().addDependency(database.getDatabase());
102+
}
103+
104+
private String loadFile(String filePath) {
105+
try {
106+
return Files.readString(Path.of(getClass().getResource(filePath).getPath()));
107+
} catch (IOException e) {
108+
throw new RuntimeException("Failed to load file " + filePath, e);
109+
}
52110
}
53111

54112
/**
@@ -208,12 +266,14 @@ public static class UnicornProps {
208266
private final boolean ecsRolesEnabled;
209267
private final Database database;
210268
private final IBucket workshopBucket;
269+
private final software.amazon.awscdk.services.ec2.IVpc vpc;
211270

212271
private UnicornProps(Builder builder) {
213272
this.eksRolesEnabled = builder.eksRolesEnabled;
214273
this.ecsRolesEnabled = builder.ecsRolesEnabled;
215274
this.database = builder.database;
216275
this.workshopBucket = builder.workshopBucket;
276+
this.vpc = builder.vpc;
217277
}
218278

219279
public static Builder builder() {
@@ -236,11 +296,16 @@ public IBucket getWorkshopBucket() {
236296
return workshopBucket;
237297
}
238298

299+
public software.amazon.awscdk.services.ec2.IVpc getVpc() {
300+
return vpc;
301+
}
302+
239303
public static class Builder {
240304
private boolean eksRolesEnabled = false;
241305
private boolean ecsRolesEnabled = false;
242306
private Database database;
243307
private IBucket workshopBucket;
308+
private software.amazon.awscdk.services.ec2.IVpc vpc;
244309

245310
public Builder eksRolesEnabled(boolean eksRolesEnabled) {
246311
this.eksRolesEnabled = eksRolesEnabled;
@@ -262,6 +327,11 @@ public Builder workshopBucket(IBucket workshopBucket) {
262327
return this;
263328
}
264329

330+
public Builder vpc(software.amazon.awscdk.services.ec2.IVpc vpc) {
331+
this.vpc = vpc;
332+
return this;
333+
}
334+
265335
public UnicornProps build() {
266336
return new UnicornProps(this);
267337
}

infra/cdk/src/main/resources/iam-policy.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"Version": "2012-10-17",
33
"Statement": [
44
{
5-
"Sid": "MarketplaceSubscribe_Claude45Opus_Claude45Sonnet_Claude4Sonnet",
5+
"Sid": "MarketplaceSubscribeClaude45Opus45Sonnet4Sonnet",
66
"Effect": "Allow",
77
"Action": [
88
"aws-marketplace:Subscribe"
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
CREATE TABLE IF NOT EXISTS unicorns(id TEXT DEFAULT gen_random_uuid() PRIMARY KEY, name TEXT, age TEXT, size TEXT, type TEXT);
2-
CREATE EXTENSION IF NOT EXISTS vector;
2+
CREATE EXTENSION IF NOT EXISTS vector;

0 commit comments

Comments
 (0)