Compare commits
32 Commits
main
...
deploy-api
Author | SHA1 | Date | |
---|---|---|---|
|
92848d1689 | ||
|
0281a5384c | ||
|
0aff17162f | ||
|
72a31437fa | ||
|
419cce0d50 | ||
|
b6615a8fcb | ||
|
24343cbb68 | ||
|
f90bf6e741 | ||
|
337c74f27b | ||
|
e5c4764464 | ||
|
b36bc0a41d | ||
|
b5a6bb6757 | ||
|
9617cc148d | ||
|
64992a90ea | ||
|
13448f59d4 | ||
|
78bd0b01d1 | ||
|
5b946c27ff | ||
|
656f06db7e | ||
|
deae23053d | ||
|
f091ba234b | ||
|
8bd6d8ae47 | ||
|
e02216c792 | ||
|
eabf6f7a1b | ||
|
1b55c75266 | ||
|
bfeb1dab1e | ||
|
a63878dda8 | ||
|
914bb70a07 | ||
|
01671f5ed5 | ||
|
7b32f22059 | ||
|
8ecf69fad0 | ||
|
84e982b190 | ||
|
45a8f0381c |
|
@ -0,0 +1,37 @@
|
|||
name: Pulumi Deploy Dev
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- staging
|
||||
|
||||
jobs:
|
||||
pulumi-deploy-dev:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node LTS ✨
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Moving to infra and Installing dependencies 📦️
|
||||
run: |
|
||||
cd infra
|
||||
yarn install
|
||||
|
||||
- name: Pulumi Dev Deploy 🚀
|
||||
uses: pulumi/actions@v4
|
||||
with:
|
||||
command: up
|
||||
cloud-url: ${{ secrets.DEV_PULUMI_S3_BUCKET }}
|
||||
stack-name: organization/cal-infra/${{ secrets.DEV_PULUMI_STACK_NAME }}
|
||||
work-dir: ./infra
|
||||
env:
|
||||
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.DEV_PULUMI_CONFIG_PASSPHRASE }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.PULUMI_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.PULUMI_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.PULUMI_AWS_REGION }}
|
||||
NODE_ENV: development
|
|
@ -0,0 +1,37 @@
|
|||
name: Pulumi Preview Dev
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- staging
|
||||
|
||||
jobs:
|
||||
pulumi-preview-dev:
|
||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||
|
||||
steps:
|
||||
- name: Checkout 🛎️
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup Node LTS ✨
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Moving to infra and Installing dependencies 📦️
|
||||
run: |
|
||||
cd infra
|
||||
yarn install
|
||||
|
||||
- name: pulumi Dev Preview 🚀
|
||||
uses: pulumi/actions@v4
|
||||
with:
|
||||
command: preview
|
||||
cloud-url: ${{ secrets.DEV_PULUMI_S3_BUCKET }}
|
||||
stack-name: organization/cal-infra/${{ secrets.DEV_PULUMI_STACK_NAME }}
|
||||
work-dir: ./infra
|
||||
env:
|
||||
PULUMI_CONFIG_PASSPHRASE: ${{ secrets.DEV_PULUMI_CONFIG_PASSPHRASE }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.PULUMI_AWS_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.PULUMI_AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: ${{ secrets.PULUMI_AWS_REGION }}
|
||||
NODE_ENV: development
|
|
@ -96,3 +96,5 @@ apps/auth
|
|||
!.yarn/releases
|
||||
!.yarn/sdks
|
||||
!.yarn/versions
|
||||
infra/.yarn
|
||||
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
encryptionsalt: v1:ohW6QUKhXRs=:v1:1j0X0vxjTVEogCe3:LTMZNRrrNn9w+qbJ0PnWZZbak+IYbg==
|
||||
config:
|
||||
aws:region: us-east-2
|
||||
base:certificateArn:
|
||||
secure: v1:G/7L0riXLzJTDWje:OA4XaOPMTT04BR8Sc2/oiues/imo5A4ZHhsP8XIEnYA8Yv2wmbFiWn0TroWd5nkr/6Wzu6wctrvSoWWSlW/oHtowoANzr5U7Wm8g/9+UO8IRm49lOcldKsZZovC/ogyGR5pw
|
||||
base:secretKeys:
|
||||
secure: v1:1u/79pchiw9lGfgE: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
|
|
@ -0,0 +1,3 @@
|
|||
name: cal-infra
|
||||
runtime: nodejs
|
||||
description: cal.com IaC
|
|
@ -0,0 +1,376 @@
|
|||
import * as aws from "@pulumi/aws";
|
||||
import type { LogGroup } from "@pulumi/aws/cloudwatch";
|
||||
import type { SecurityGroup } from "@pulumi/aws/ec2";
|
||||
import * as awsx from "@pulumi/awsx";
|
||||
import type { Cluster } from "@pulumi/awsx/classic/ecs/cluster";
|
||||
import type { Vpc } from "@pulumi/awsx/ec2/vpc";
|
||||
import type { Image, Repository } from "@pulumi/awsx/ecr";
|
||||
import type { FargateService } from "@pulumi/awsx/ecs";
|
||||
import type { ApplicationLoadBalancer } from "@pulumi/awsx/lb";
|
||||
import * as pulumi from "@pulumi/pulumi";
|
||||
|
||||
type SecretType = { name: string; valueFrom: string };
|
||||
|
||||
const awsConfig = new pulumi.Config("aws");
|
||||
const baseConfig = new pulumi.Config("base");
|
||||
const awsRegion = awsConfig.require("region");
|
||||
const certificateArn = baseConfig.require("certificateArn");
|
||||
|
||||
if (!awsRegion) {
|
||||
throw new Error("AWS REGION IS NOT SET");
|
||||
}
|
||||
// Get Secrets
|
||||
const getAwsSecrets = async () => {
|
||||
const secretKeys: string[] = JSON.parse(baseConfig.require("secretKeys") ?? "[]");
|
||||
const SECRETS = secretKeys.map((secretKey) => {
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
return `DEV_${secretKey}`;
|
||||
}
|
||||
return secretKey;
|
||||
});
|
||||
|
||||
const res = [];
|
||||
for (let index = 0; index < SECRETS.length; index++) {
|
||||
try {
|
||||
const secretKey = SECRETS[index];
|
||||
const secret = await aws.secretsmanager.getSecret({ name: secretKey });
|
||||
if (secret && secret.arn) res.push({ name: secretKey, valueFrom: secret.arn });
|
||||
} catch (err) {
|
||||
console.info("Secret not found:", SECRETS[index]);
|
||||
}
|
||||
}
|
||||
return res.map((res) => ({
|
||||
name: res.name.replace("DEV_", ""),
|
||||
valueFrom: res.valueFrom,
|
||||
}));
|
||||
};
|
||||
|
||||
const createVpc = (name: string) => {
|
||||
// Create VPC
|
||||
const vpc = new awsx.ec2.Vpc(name, {
|
||||
cidrBlock: "10.0.0.0/16",
|
||||
});
|
||||
return vpc;
|
||||
};
|
||||
|
||||
const createHttpsSecurityGroup = (name: string, vpcId: Vpc["vpcId"]) => {
|
||||
// Create Security Group
|
||||
const sg = new aws.ec2.SecurityGroup(name, {
|
||||
vpcId: vpcId,
|
||||
ingress: [
|
||||
{
|
||||
description: "allow HTTP access from anywhere",
|
||||
fromPort: 80,
|
||||
toPort: 80,
|
||||
protocol: "tcp",
|
||||
cidrBlocks: ["0.0.0.0/0"],
|
||||
},
|
||||
{
|
||||
description: "allow HTTPS access from anywhere",
|
||||
fromPort: 443,
|
||||
toPort: 443,
|
||||
protocol: "tcp",
|
||||
cidrBlocks: ["0.0.0.0/0"],
|
||||
},
|
||||
],
|
||||
egress: [
|
||||
{
|
||||
fromPort: 0,
|
||||
toPort: 0,
|
||||
protocol: "-1",
|
||||
cidrBlocks: ["0.0.0.0/0"],
|
||||
},
|
||||
],
|
||||
});
|
||||
return sg;
|
||||
};
|
||||
|
||||
const createLog = (name: string) => {
|
||||
// Create Cloudwatch LogGroup and Stream
|
||||
const logGroup = new aws.cloudwatch.LogGroup(`${name}-log-group`);
|
||||
const logStream = new aws.cloudwatch.LogStream(`${name}-log-stream`, {
|
||||
logGroupName: logGroup.name,
|
||||
});
|
||||
return { logGroup, logStream };
|
||||
};
|
||||
|
||||
const createAppLoadBalancer = (
|
||||
name: string,
|
||||
publicSubnetIds: Vpc["publicSubnetIds"],
|
||||
securityGroupId: SecurityGroup["id"]
|
||||
) => {
|
||||
// Create Application Load Balancer
|
||||
const lb = new awsx.lb.ApplicationLoadBalancer(name, {
|
||||
securityGroups: [securityGroupId],
|
||||
subnetIds: publicSubnetIds,
|
||||
defaultTargetGroup: { healthCheck: { matcher: "200-299" }, port: 80, protocol: "HTTP" },
|
||||
listeners: [
|
||||
{
|
||||
port: 80,
|
||||
protocol: "HTTP",
|
||||
defaultActions: [
|
||||
{
|
||||
type: "redirect",
|
||||
redirect: {
|
||||
protocol: "HTTPS",
|
||||
port: "443",
|
||||
statusCode: "HTTP_301",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
port: 443,
|
||||
protocol: "HTTPS",
|
||||
certificateArn: certificateArn,
|
||||
},
|
||||
],
|
||||
});
|
||||
return lb;
|
||||
};
|
||||
|
||||
const createDockerImagesRepo = (name: string) => {
|
||||
// Create ECR Image Repository
|
||||
const repository = new awsx.ecr.Repository(name, {});
|
||||
return repository;
|
||||
};
|
||||
|
||||
const createDockerImage = ({
|
||||
imageName,
|
||||
repoUrl,
|
||||
dockerFilePath,
|
||||
buildContextPath,
|
||||
}: {
|
||||
imageName: string;
|
||||
repoUrl: Repository["url"];
|
||||
dockerFilePath: string;
|
||||
buildContextPath: string;
|
||||
}) => {
|
||||
// Create Docker Image of Api and Store in Repo
|
||||
const image = new awsx.ecr.Image(`${imageName}:latest`, {
|
||||
repositoryUrl: repoUrl,
|
||||
dockerfile: dockerFilePath,
|
||||
context: buildContextPath,
|
||||
cacheFrom: [pulumi.interpolate`${repoUrl}:latest`],
|
||||
platform: "linux/amd64",
|
||||
});
|
||||
return image;
|
||||
};
|
||||
|
||||
const createElasticContainerCluster = (clusterName: string) => {
|
||||
// Create ECS cluster
|
||||
const cluster = new awsx.classic.ecs.Cluster(clusterName, {});
|
||||
return cluster;
|
||||
};
|
||||
|
||||
const createFargateServiceWithSecrets = ({
|
||||
secrets,
|
||||
imageUri,
|
||||
logGroupName,
|
||||
loadBalancerTargetGroup,
|
||||
ecsClusterArn,
|
||||
privateSubnetIds,
|
||||
securityGroupId,
|
||||
serviceName,
|
||||
desiredNbOfTasks,
|
||||
cpu,
|
||||
memory,
|
||||
}: {
|
||||
secrets: SecretType[];
|
||||
imageUri: Image["imageUri"];
|
||||
logGroupName: LogGroup["name"];
|
||||
loadBalancerTargetGroup: ApplicationLoadBalancer["defaultTargetGroup"];
|
||||
ecsClusterArn: Cluster["cluster"]["arn"];
|
||||
privateSubnetIds: Vpc["privateSubnetIds"];
|
||||
securityGroupId: SecurityGroup["id"];
|
||||
serviceName: string;
|
||||
desiredNbOfTasks: number;
|
||||
cpu: 512 | 1024 | 2048;
|
||||
memory: 1000 | 2000 | 3000 | 4000;
|
||||
}) => {
|
||||
// Policy For Secrets
|
||||
const secretsManagerAccessPolicy = new aws.iam.Policy(`${serviceName}-fargate-secrets-policy`, {
|
||||
policy: {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Effect: "Allow",
|
||||
Action: "secretsmanager:GetSecretValue",
|
||||
Resource: "*",
|
||||
},
|
||||
{
|
||||
Effect: "Allow",
|
||||
Action: [
|
||||
"ecr:GetAuthorizationToken",
|
||||
"ecr:BatchCheckLayerAvailability",
|
||||
"ecr:GetDownloadUrlForLayer",
|
||||
"ecr:BatchGetImage",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents",
|
||||
],
|
||||
Resource: "*",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// IAM Role To Attach To Fargate Service for Accessing Secrets
|
||||
const taskRole = new aws.iam.Role("task-exec-role", {
|
||||
assumeRolePolicy: {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Action: "sts:AssumeRole",
|
||||
Principal: {
|
||||
Service: "ecs-tasks.amazonaws.com",
|
||||
},
|
||||
Effect: "Allow",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// Attach Policy and Role
|
||||
new aws.iam.RolePolicyAttachment(`${serviceName}-task-exec-policy-attach`, {
|
||||
role: taskRole,
|
||||
policyArn: secretsManagerAccessPolicy.arn,
|
||||
});
|
||||
|
||||
// Create Fargate Service
|
||||
const service = new awsx.ecs.FargateService(`${serviceName}-fargate-service`, {
|
||||
cluster: ecsClusterArn,
|
||||
networkConfiguration: {
|
||||
subnets: privateSubnetIds,
|
||||
securityGroups: [securityGroupId],
|
||||
assignPublicIp: true,
|
||||
},
|
||||
|
||||
desiredCount: desiredNbOfTasks,
|
||||
taskDefinitionArgs: {
|
||||
executionRole: { roleArn: taskRole.arn },
|
||||
logGroup: { skip: true },
|
||||
runtimePlatform: {
|
||||
cpuArchitecture: "X86_64",
|
||||
},
|
||||
container: {
|
||||
name: serviceName,
|
||||
image: imageUri,
|
||||
cpu: cpu,
|
||||
memory: memory,
|
||||
essential: true,
|
||||
portMappings: [
|
||||
{
|
||||
containerPort: 80,
|
||||
hostPort: 80,
|
||||
targetGroup: loadBalancerTargetGroup,
|
||||
},
|
||||
],
|
||||
logConfiguration: {
|
||||
logDriver: "awslogs",
|
||||
options: {
|
||||
"awslogs-group": logGroupName,
|
||||
"awslogs-stream-prefix": serviceName,
|
||||
"awslogs-region": `${awsRegion}`,
|
||||
},
|
||||
},
|
||||
secrets: secrets ?? [],
|
||||
},
|
||||
},
|
||||
});
|
||||
return service;
|
||||
};
|
||||
|
||||
const createAutoScalingCpu = ({
|
||||
name,
|
||||
ecsClusterName,
|
||||
serviceName,
|
||||
cpuTargetValue,
|
||||
maxCapacity,
|
||||
minCapacity,
|
||||
scaleInCooldown,
|
||||
scaleOutCooldown,
|
||||
}: {
|
||||
name: string;
|
||||
ecsClusterName: Cluster["cluster"]["name"];
|
||||
serviceName: FargateService["service"]["name"];
|
||||
cpuTargetValue: number;
|
||||
maxCapacity: number;
|
||||
minCapacity: number;
|
||||
scaleInCooldown: number;
|
||||
scaleOutCooldown: number;
|
||||
}) => {
|
||||
// Create Autoscaling for the ECS service, Scale when CPU > 75%
|
||||
const autoscaling = new aws.appautoscaling.Policy(name, {
|
||||
serviceNamespace: "ecs",
|
||||
scalableDimension: "ecs:service:DesiredCount",
|
||||
resourceId: pulumi.interpolate`service/${ecsClusterName}/${serviceName}`,
|
||||
policyType: "TargetTrackingScaling",
|
||||
targetTrackingScalingPolicyConfiguration: {
|
||||
targetValue: cpuTargetValue,
|
||||
predefinedMetricSpecification: {
|
||||
predefinedMetricType: "ECSServiceAverageCPUUtilization",
|
||||
},
|
||||
scaleInCooldown,
|
||||
scaleOutCooldown,
|
||||
},
|
||||
});
|
||||
// Set Min and Max Number of Tasks
|
||||
const autoscalingTarget = new aws.appautoscaling.Target(`${name}-scaling-target`, {
|
||||
maxCapacity: maxCapacity, // maximum number of tasks
|
||||
minCapacity: minCapacity, // minimum number of tasks
|
||||
resourceId: pulumi.interpolate`service/${ecsClusterName}/${serviceName}`,
|
||||
scalableDimension: "ecs:service:DesiredCount",
|
||||
serviceNamespace: "ecs",
|
||||
});
|
||||
|
||||
return { autoscaling, autoscalingTarget };
|
||||
};
|
||||
|
||||
const addSuffixToName = (name: string) => {
|
||||
const suffix = process.env.NODE_ENV === "development" ? "dev" : "prod";
|
||||
return `${name}-${suffix}`;
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const awsSecrets = await getAwsSecrets();
|
||||
const vpc = createVpc(addSuffixToName("cal-api-vpc"));
|
||||
const httpsSg = createHttpsSecurityGroup(addSuffixToName("cal-api-sg"), vpc.vpcId);
|
||||
const apiAlb = createAppLoadBalancer(addSuffixToName("cal-api-lb"), vpc.publicSubnetIds, httpsSg.id);
|
||||
const { logGroup } = createLog(addSuffixToName("cal-api"));
|
||||
const repo = createDockerImagesRepo(addSuffixToName("cal-api-repo"));
|
||||
const apiImage = createDockerImage({
|
||||
imageName: addSuffixToName("cal-api-image"),
|
||||
repoUrl: repo.url,
|
||||
dockerFilePath: "./docker/api/Dockerfile",
|
||||
buildContextPath: "../",
|
||||
});
|
||||
const ecsCluster = createElasticContainerCluster(addSuffixToName("cal-api-cluster"));
|
||||
const apiService = createFargateServiceWithSecrets({
|
||||
desiredNbOfTasks: 2,
|
||||
privateSubnetIds: vpc.privateSubnetIds,
|
||||
securityGroupId: httpsSg.id,
|
||||
secrets: awsSecrets,
|
||||
imageUri: apiImage.imageUri,
|
||||
logGroupName: logGroup.name,
|
||||
loadBalancerTargetGroup: apiAlb.defaultTargetGroup,
|
||||
ecsClusterArn: ecsCluster.cluster.arn,
|
||||
serviceName: addSuffixToName("cal-api-fargate"),
|
||||
cpu: 1024,
|
||||
memory: 2000,
|
||||
});
|
||||
const _ = createAutoScalingCpu({
|
||||
name: addSuffixToName("cal-api-cpu-scaling"),
|
||||
ecsClusterName: ecsCluster.cluster.name,
|
||||
serviceName: apiService.service.name,
|
||||
minCapacity: 1,
|
||||
maxCapacity: 3,
|
||||
cpuTargetValue: 75,
|
||||
scaleInCooldown: 120,
|
||||
scaleOutCooldown: 60,
|
||||
});
|
||||
return { apiDnsName: pulumi.interpolate`apiUrl: ${apiAlb.loadBalancer.dnsName}` };
|
||||
};
|
||||
|
||||
main().then(({ apiDnsName }) => {
|
||||
console.info(apiDnsName);
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"name": "@calcom/infra",
|
||||
"main": "index.ts",
|
||||
"scripts": {
|
||||
"deploy-dev": "NODE_ENV=development pulumi up",
|
||||
"deploy-prod": "NODE_ENV=production pulumi up"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.9.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pulumi/aws": "^6.0.0",
|
||||
"@pulumi/awsx": "^2.0.2",
|
||||
"@pulumi/pulumi": "^3.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"outDir": "bin",
|
||||
"target": "es2016",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"experimentalDecorators": true,
|
||||
"pretty": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"files": ["index.ts"]
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -47,6 +47,10 @@
|
|||
"cache": false,
|
||||
"dependsOn": []
|
||||
},
|
||||
"@calcom/api#start": {
|
||||
"cache": false,
|
||||
"dependsOn": []
|
||||
},
|
||||
"@calcom/ai#build": {
|
||||
"env": [
|
||||
"FRONTEND_URL",
|
||||
|
|
Loading…
Reference in New Issue
Block a user