Skip to content
Closed
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion user-management/Stickerlandia.UserManagement.sln
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{FC9DDF76-8
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Agnostic", "src\Stickerlandia.UserManagement.Agnostic\Stickerlandia.UserManagement.Agnostic.csproj", "{295E8315-2CD2-4CD8-B7F4-21EF587B74A9}"
EndProject
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}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Stickerlandia.UserManagement.Lambda", "src\Stickerlandia.UserManagement.Lambda\Stickerlandia.UserManagement.Lambda.csproj", "{5C770F92-B6E7-4270-A041-A32F385DA6C4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Driving", "Driving", "{924749FC-98D5-40A6-8F4B-BA9BFDEDDEC3}"
EndProject
Expand Down
8 changes: 8 additions & 0 deletions user-management/infra/aws/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
6 changes: 6 additions & 0 deletions user-management/infra/aws/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
14 changes: 14 additions & 0 deletions user-management/infra/aws/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Welcome to your CDK TypeScript project

This is a blank project for CDK development with TypeScript.

The `cdk.json` file tells the CDK Toolkit how to execute your app.

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
* `npx cdk synth` emits the synthesized CloudFormation template
96 changes: 96 additions & 0 deletions user-management/infra/aws/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
{
"app": "npx ts-node --prefer-ts-exts bin/user-service.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
"@aws-cdk/aws-events:requireEventBusPolicySid": true,
"@aws-cdk/core:aspectPrioritiesMutating": true,
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true
}
}
8 changes: 8 additions & 0 deletions user-management/infra/aws/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};
200 changes: 200 additions & 0 deletions user-management/infra/aws/lib/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { Vpc, SecurityGroup, Port } from "aws-cdk-lib/aws-ec2";
import { Construct } from "constructs";
import { SharedProps } from "./constructs/shared-props";
import {
Cluster,
ContainerImage,
CpuArchitecture,
FargateService,
FargateTaskDefinition,
FirelensLogRouterType,
LogDriver,
LogDrivers,
OperatingSystemFamily,
Protocol,
} from "aws-cdk-lib/aws-ecs";
import { Repository } from "aws-cdk-lib/aws-ecr";
import { Topic } from "aws-cdk-lib/aws-sns";
import { Queue } from "aws-cdk-lib/aws-sqs";
import {
ApplicationLoadBalancer,
ApplicationTargetGroup,
TargetType,
ApplicationProtocol,
HealthCheck,
Protocol as HealthCheckProtocol,
} from "aws-cdk-lib/aws-elasticloadbalancingv2";
import { Duration, CfnOutput } from "aws-cdk-lib";
import { ApplicationLoadBalancedFargateService } from "aws-cdk-lib/aws-ecs-patterns";

export class ApiProps {
sharedProps: SharedProps;
vpc: Vpc;
cluster: Cluster;
}

export class Api extends Construct {
stickerClaimedQueue: Queue;
stickerClaimedDLQ: Queue;
userRegisteredTopic: Topic;
constructor(scope: Construct, id: string, props: ApiProps) {
super(scope, id);

const ecrRepository = Repository.fromRepositoryName(
this,
"UserServiceRepo",
"stickerlandia-user-management"
);

this.userRegisteredTopic = new Topic(this, "UserRegisteredTopic", {
topicName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-registered`,
});
this.stickerClaimedDLQ = new Queue(this, "StickerClaimedDLQ", {
queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed-dlq`,
});
this.stickerClaimedQueue = new Queue(this, "StickerClaimedQueue", {
queueName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-sticker-claimed`,
deadLetterQueue: {
queue: this.stickerClaimedDLQ,
maxReceiveCount: 5, // Messages will be sent to DLQ after 5 failed attempts
},
});

const applicationTaskDef = new FargateTaskDefinition(
this,
"UserServiceTaskDef",
{
runtimePlatform: {
operatingSystemFamily: OperatingSystemFamily.LINUX,
cpuArchitecture: CpuArchitecture.ARM64,
},
cpu: 256,
memoryLimitMiB: 512,
}
);

const containerPort = 8080;

const container = applicationTaskDef.addContainer("UserServiceContainer", {
image: ContainerImage.fromEcrRepository(
ecrRepository,
props.sharedProps.version
),
portMappings: [
{
containerPort: containerPort,
protocol: Protocol.TCP,
},
],
environment: {
ConnectionStrings__messaging: "",
ConnectionStrings__database: props.sharedProps.connectionString,
Aws__UserRegisteredTopicArn: this.userRegisteredTopic.topicArn,
Aws__StickerClaimedQueueUrl: this.stickerClaimedQueue.queueUrl,
Aws__StickerClaimedDLQUrl: this.stickerClaimedDLQ.queueUrl,
DRIVING: "ASPNET",
DRIVEN: "AWS",
DISABLE_SSL: "true",
},
logging: LogDrivers.firelens({
options: {
Name: "datadog",
Host: "http-intake.logs.datadoghq.eu",
TLS: "on",
dd_service: props.sharedProps.serviceName,
dd_source: "aspnet",
dd_message_key: "log",
dd_tags: `project:${props.sharedProps.serviceName}`,
provider: "ecs",
apikey: props.sharedProps.datadog.apiKey,
},
}),
});
container.addDockerLabel(
"com.datadoghq.tags.env",
props.sharedProps.environment
);
container.addDockerLabel(
"com.datadoghq.tags.service",
props.sharedProps.serviceName
);
container.addDockerLabel(
"com.datadoghq.tags.version",
props.sharedProps.version
);

container.addPortMappings({
containerPort: containerPort,
protocol: Protocol.TCP,
});

applicationTaskDef.addContainer("datadog-agent", {
image: ContainerImage.fromRegistry("public.ecr.aws/datadog/agent:latest"),
portMappings: [
{
containerPort: 8125,
protocol: Protocol.UDP, // Dogstatsd port
},
{
containerPort: 8126,
protocol: Protocol.TCP, // APM port
},
],
containerName: "datadog-agent",
environment: {
DD_API_KEY: props.sharedProps.datadog.apiKey,
DD_SITE: props.sharedProps.datadog.site,
DD_APM_ENABLED: "true",
DD_LOGS_ENABLED: "true",
ECS_FARGATE: "true",
DD_APM_NON_LOCAL_TRAFFIC: "true",
DD_DOGSTATSD_NON_LOCAL_TRAFFIC: "true",
DD_APM_IGNORE_RESOURCES: `(GET) /api/users/v1/health`,
},
});
applicationTaskDef.addFirelensLogRouter("firelens-router", {
essential: true,
image: ContainerImage.fromRegistry("amazon/aws-for-fluent-bit:stable"),
firelensConfig: {
type: FirelensLogRouterType.FLUENTBIT,
options: {
enableECSLogMetadata: true,
},
},
});

this.userRegisteredTopic.grantPublish(applicationTaskDef.taskRole);
this.stickerClaimedQueue.grantSendMessages(applicationTaskDef.taskRole);
this.stickerClaimedDLQ.grantSendMessages(applicationTaskDef.taskRole);
this.stickerClaimedQueue.grantConsumeMessages(applicationTaskDef.taskRole);
this.stickerClaimedDLQ.grantConsumeMessages(applicationTaskDef.taskRole);

const service = new ApplicationLoadBalancedFargateService(
this,
"UserServiceFargateService",
{
cluster: props.cluster,
taskDefinition: applicationTaskDef,
assignPublicIp: false,
publicLoadBalancer: true,
desiredCount: 1,
loadBalancerName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service-lb`,
serviceName: `${props.sharedProps.serviceName}-${props.sharedProps.environment}-user-service`,
listenerPort: 80,
protocol: ApplicationProtocol.HTTP,
healthCheckGracePeriod: Duration.minutes(2),
circuitBreaker: {
rollback: true,
enable: true,
},
}
);
service.targetGroup.configureHealthCheck({
path: "/api/users/v1/health",
interval: Duration.seconds(60),
timeout: Duration.seconds(5),
enabled: true,
healthyHttpCodes: "200-499",
});
}
}
Loading
Loading