Skip to content

Commit a0b9565

Browse files
committed
feat: add AWS implementation and IaC
1 parent b458dd2 commit a0b9565

60 files changed

Lines changed: 5838 additions & 1114 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

user-management/Stickerlandia.UserManagement.sln

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{FC9DDF76-8
2727
EndProject
2828
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Agnostic", "src\Stickerlandia.UserManagement.Agnostic\Stickerlandia.UserManagement.Agnostic.csproj", "{295E8315-2CD2-4CD8-B7F4-21EF587B74A9}"
2929
EndProject
30-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Lambda", "src\Stickerlandia.UserManagement.Lambda\src\Stickerlandia.UserManagement.Lambda\Stickerlandia.UserManagement.Lambda.csproj", "{5C770F92-B6E7-4270-A041-A32F385DA6C4}"
30+
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Lambda", "src\Stickerlandia.UserManagement.Lambda\Stickerlandia.UserManagement.Lambda.csproj", "{5C770F92-B6E7-4270-A041-A32F385DA6C4}"
3131
EndProject
3232
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Driving", "Driving", "{924749FC-98D5-40A6-8F4B-BA9BFDEDDEC3}"
3333
EndProject
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
*.js
2+
!jest.config.js
3+
*.d.ts
4+
node_modules
5+
6+
# CDK asset staging directory
7+
.cdk.staging
8+
cdk.out
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
*.ts
2+
!*.d.ts
3+
4+
# CDK asset staging directory
5+
.cdk.staging
6+
cdk.out
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Welcome to your CDK TypeScript project
2+
3+
This is a blank project for CDK development with TypeScript.
4+
5+
The `cdk.json` file tells the CDK Toolkit how to execute your app.
6+
7+
## Useful commands
8+
9+
* `npm run build` compile typescript to js
10+
* `npm run watch` watch for changes and compile
11+
* `npm run test` perform the jest unit tests
12+
* `npx cdk deploy` deploy this stack to your default AWS account/region
13+
* `npx cdk diff` compare deployed stack with current state
14+
* `npx cdk synth` emits the synthesized CloudFormation template

user-management/infra/aws/cdk.json

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{
2+
"app": "npx ts-node --prefer-ts-exts bin/user-service.ts",
3+
"watch": {
4+
"include": [
5+
"**"
6+
],
7+
"exclude": [
8+
"README.md",
9+
"cdk*.json",
10+
"**/*.d.ts",
11+
"**/*.js",
12+
"tsconfig.json",
13+
"package*.json",
14+
"yarn.lock",
15+
"node_modules",
16+
"test"
17+
]
18+
},
19+
"context": {
20+
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
21+
"@aws-cdk/core:checkSecretUsage": true,
22+
"@aws-cdk/core:target-partitions": [
23+
"aws",
24+
"aws-cn"
25+
],
26+
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
27+
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
28+
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
29+
"@aws-cdk/aws-iam:minimizePolicies": true,
30+
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
31+
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
32+
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
33+
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
34+
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
35+
"@aws-cdk/core:enablePartitionLiterals": true,
36+
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
37+
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
38+
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
39+
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
40+
"@aws-cdk/aws-route53-patters:useCertificate": true,
41+
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
42+
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
43+
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
44+
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
45+
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
46+
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
47+
"@aws-cdk/aws-redshift:columnId": true,
48+
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
49+
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
50+
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
51+
"@aws-cdk/aws-kms:aliasNameRef": true,
52+
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
53+
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
54+
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
55+
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
56+
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
57+
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
58+
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
59+
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
60+
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
61+
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
62+
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
63+
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
64+
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
65+
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
66+
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
67+
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
68+
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
69+
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
70+
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
71+
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
72+
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
73+
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
74+
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
75+
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
76+
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
77+
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
78+
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
79+
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
80+
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
81+
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
82+
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
83+
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
84+
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
85+
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
86+
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
87+
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
88+
"@aws-cdk/aws-events:requireEventBusPolicySid": true,
89+
"@aws-cdk/core:aspectPrioritiesMutating": true,
90+
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
91+
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
92+
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
93+
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
94+
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true
95+
}
96+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
module.exports = {
2+
testEnvironment: 'node',
3+
roots: ['<rootDir>/test'],
4+
testMatch: ['**/*.test.ts'],
5+
transform: {
6+
'^.+\\.tsx?$': 'ts-jest'
7+
}
8+
};
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { Vpc, SecurityGroup, Port } from "aws-cdk-lib/aws-ec2";
2+
import { Construct } from "constructs";
3+
import { SharedProps } from "./constructs/shared-props";
4+
import {
5+
Cluster,
6+
ContainerImage,
7+
CpuArchitecture,
8+
FargateService,
9+
FargateTaskDefinition,
10+
FirelensLogRouterType,
11+
LogDriver,
12+
LogDrivers,
13+
OperatingSystemFamily,
14+
Protocol,
15+
} from "aws-cdk-lib/aws-ecs";
16+
import { Repository } from "aws-cdk-lib/aws-ecr";
17+
import { Topic } from "aws-cdk-lib/aws-sns";
18+
import { Queue } from "aws-cdk-lib/aws-sqs";
19+
import {
20+
ApplicationLoadBalancer,
21+
ApplicationTargetGroup,
22+
TargetType,
23+
ApplicationProtocol,
24+
HealthCheck,
25+
Protocol as HealthCheckProtocol,
26+
} from "aws-cdk-lib/aws-elasticloadbalancingv2";
27+
import { Duration, CfnOutput } from "aws-cdk-lib";
28+
import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns";
29+
30+
export class ApiProps {
31+
sharedProps: SharedProps;
32+
vpc: Vpc;
33+
cluster: Cluster;
34+
}
35+
36+
export class Api extends Construct {
37+
stickerClaimedQueue: Queue;
38+
stickerClaimedDLQ: Queue;
39+
userRegisteredTopic: Topic;
40+
constructor(scope: Construct, id: string, props: ApiProps) {
41+
super(scope, id);
42+
43+
const ecrRepository = Repository.fromRepositoryName(
44+
this,
45+
"UserServiceRepo",
46+
"stickerlandia-user-management"
47+
);
48+
49+
this.userRegisteredTopic = new Topic(this, "UserRegisteredTopic", {
50+
topicName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-registered`,
51+
});
52+
this.stickerClaimedDLQ = new Queue(this, "StickerClaimedDLQ", {
53+
queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed-dlq`,
54+
});
55+
this.stickerClaimedQueue = new Queue(this, "StickerClaimedQueue", {
56+
queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed`,
57+
deadLetterQueue: {
58+
queue: this.stickerClaimedDLQ,
59+
maxReceiveCount: 5, // Messages will be sent to DLQ after 5 failed attempts
60+
},
61+
});
62+
63+
const applicationTaskDef = new FargateTaskDefinition(
64+
this,
65+
"UserServiceTaskDef",
66+
{
67+
runtimePlatform: {
68+
operatingSystemFamily: OperatingSystemFamily.LINUX,
69+
cpuArchitecture: CpuArchitecture.ARM64,
70+
},
71+
cpu: 256,
72+
memoryLimitMiB: 512,
73+
}
74+
);
75+
76+
const containerPort = 8080;
77+
78+
const container = applicationTaskDef.addContainer("UserServiceContainer", {
79+
image: ContainerImage.fromEcrRepository(
80+
ecrRepository,
81+
props.sharedProps.version
82+
),
83+
portMappings: [
84+
{
85+
containerPort: containerPort,
86+
protocol: Protocol.TCP,
87+
},
88+
],
89+
environment: {
90+
ConnectionStrings__messaging: "",
91+
ConnectionStrings__database: props.sharedProps.connectionString,
92+
Aws__UserRegisteredTopicArn: this.userRegisteredTopic.topicArn,
93+
Aws__StickerClaimedQueueUrl: this.stickerClaimedQueue.queueUrl,
94+
Aws__StickerClaimedDLQUrl: this.stickerClaimedDLQ.queueUrl,
95+
DRIVING: "ASPNET",
96+
DRIVEN: "AWS",
97+
DISABLE_SSL: "true",
98+
},
99+
logging: LogDrivers.firelens({
100+
options: {
101+
Name: "datadog",
102+
Host: "http-intake.logs.datadoghq.eu",
103+
TLS: "on",
104+
dd_service: props.sharedProps.serviceName,
105+
dd_source: "aspnet",
106+
dd_message_key: "log",
107+
dd_tags: `project:${props.sharedProps.serviceName}`,
108+
provider: "ecs",
109+
apikey: props.sharedProps.datadog.apiKey,
110+
},
111+
}),
112+
});
113+
container.addDockerLabel(
114+
"com.datadoghq.tags.env",
115+
props.sharedProps.environment
116+
);
117+
container.addDockerLabel(
118+
"com.datadoghq.tags.service",
119+
props.sharedProps.serviceName
120+
);
121+
container.addDockerLabel(
122+
"com.datadoghq.tags.version",
123+
props.sharedProps.version
124+
);
125+
126+
container.addPortMappings({
127+
containerPort: containerPort,
128+
protocol: Protocol.TCP,
129+
});
130+
131+
applicationTaskDef.addContainer("datadog-agent", {
132+
image: ContainerImage.fromRegistry("public.ecr.aws/datadog/agent:latest"),
133+
portMappings: [
134+
{
135+
containerPort: 8125,
136+
protocol: Protocol.UDP, // Dogstatsd port
137+
},
138+
{
139+
containerPort: 8126,
140+
protocol: Protocol.TCP, // APM port
141+
},
142+
],
143+
containerName: "datadog-agent",
144+
environment: {
145+
DD_API_KEY: props.sharedProps.datadog.apiKey,
146+
DD_SITE: props.sharedProps.datadog.site,
147+
DD_APM_ENABLED: "true",
148+
DD_LOGS_ENABLED: "true",
149+
ECS_FARGATE: "true",
150+
DD_APM_NON_LOCAL_TRAFFIC: "true",
151+
DD_DOGSTATSD_NON_LOCAL_TRAFFIC: "true",
152+
DD_APM_IGNORE_RESOURCES: `(GET) /api/users/v1/health`,
153+
},
154+
});
155+
applicationTaskDef.addFirelensLogRouter("firelens-router", {
156+
essential: true,
157+
image: ContainerImage.fromRegistry("amazon/aws-for-fluent-bit:stable"),
158+
firelensConfig: {
159+
type: FirelensLogRouterType.FLUENTBIT,
160+
options: {
161+
enableECSLogMetadata: true,
162+
},
163+
},
164+
});
165+
166+
this.userRegisteredTopic.grantPublish(applicationTaskDef.taskRole);
167+
this.stickerClaimedQueue.grantSendMessages(applicationTaskDef.taskRole);
168+
this.stickerClaimedDLQ.grantSendMessages(applicationTaskDef.taskRole);
169+
this.stickerClaimedQueue.grantConsumeMessages(applicationTaskDef.taskRole);
170+
this.stickerClaimedDLQ.grantConsumeMessages(applicationTaskDef.taskRole);
171+
172+
const service = new ApplicationLoadBalancedFargateService(
173+
this,
174+
"UserServiceFargateService",
175+
{
176+
cluster: props.cluster,
177+
taskDefinition: applicationTaskDef,
178+
assignPublicIp: false,
179+
publicLoadBalancer: true,
180+
desiredCount: 1,
181+
loadBalancerName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service-lb`,
182+
serviceName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service`,
183+
listenerPort: 80,
184+
protocol: ApplicationProtocol.HTTP,
185+
healthCheckGracePeriod: Duration.minutes(2),
186+
circuitBreaker: {
187+
rollback: true,
188+
enable: true,
189+
},
190+
}
191+
);
192+
service.targetGroup.configureHealthCheck({
193+
path: "/api/users/v1/health",
194+
interval: Duration.seconds(60),
195+
timeout: Duration.seconds(5),
196+
enabled: true,
197+
healthyHttpCodes: "200-499",
198+
});
199+
}
200+
}

0 commit comments

Comments
 (0)