diff --git a/dependencies/code_builder/buildspec.yml b/dependencies/code_builder/buildspec.yml new file mode 100644 index 00000000..e87efc7c --- /dev/null +++ b/dependencies/code_builder/buildspec.yml @@ -0,0 +1,51 @@ +version: 0.2 + +phases: + build: + commands: + - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin https://$AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com + - | + PULL_FROM_DOCKER=("grafana/grafana" "grafana/loki" "grafana/promtail" "grafana/fluent-bit-plugin-loki" "nginx" "juspaydotin/hyperswitch-router" "juspaydotin/hyperswitch-producer" "juspaydotin/hyperswitch-consumer" "juspaydotin/hyperswitch-control-center") + PULL_FROM_K8S=("metrics-server") + PULL_FROM_AWS=("eks/aws-load-balancer-controller" "ebs-csi-driver/aws-ebs-csi-driver" "eks-distro/kubernetes-csi/external-provisioner" "eks-distro/kubernetes-csi/external-attacher" "eks-distro/kubernetes-csi/external-snapshotter/csi-snapshotter" "eks-distro/kubernetes-csi/livenessprobe" "eks-distro/kubernetes-csi/external-resizer" "eks-distro/kubernetes-csi/node-driver-registrar" "ebs-csi-driver/volume-modifier-for-k8s") + + for IMAGE in "${PULL_FROM_DOCKER[@]}"; do + aws ecr create-repository --repository-name $IMAGE --image-scanning-configuration scanOnPush=true + done + + for IMAGE in "${PULL_FROM_K8S[@]}"; do + aws ecr create-repository --repository-name $IMAGE --image-scanning-configuration scanOnPush=true + done + + for IMAGE in "${PULL_FROM_AWS[@]}"; do + aws ecr create-repository --repository-name $IMAGE --image-scanning-configuration scanOnPush=true + done + + for IMAGE in "${PULL_FROM_DOCKER[@]}"; do + if [[ $IMAGE == "juspaydotin/hyperswitch-router" || $IMAGE == "juspaydotin/hyperswitch-producer" || $IMAGE == "juspaydotin/hyperswitch-consumer" ]]; then + docker pull $IMAGE:v1.105.0 + docker tag $IMAGE:v1.105.0 $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v1.105.0 + docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v1.105.0 + elif [[ $IMAGE == "juspaydotin/hyperswitch-control-center" ]]; then + docker pull $IMAGE:v1.17.0 + docker tag $IMAGE:v1.17.0 $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v1.17.0 + docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v1.17.0 + else + docker pull $IMAGE + docker tag $IMAGE $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:latest + docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE + fi + done + + for IMAGE in "${PULL_FROM_K8S[@]}"; do + docker pull registry.k8s.io/$IMAGE:v0.3.6 + docker tag registry.k8s.io/$IMAGE:v0.3.6 $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v0.3.6 + docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v0.3.6 + done + + for IMAGE in "${PULL_FROM_AWS[@]}"; do + docker pull public.ecr.aws/$IMAGE:v2.7.1 + docker tag public.ecr.aws/$IMAGE:v2.7.1 $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v2.7.1 + docker push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$IMAGE:v2.7.1 + done + diff --git a/dependencies/code_builder/start_build.py b/dependencies/code_builder/start_build.py new file mode 100644 index 00000000..1631096a --- /dev/null +++ b/dependencies/code_builder/start_build.py @@ -0,0 +1,58 @@ +import boto3 +import os +import json +import urllib3 + +http = urllib3.PoolManager() + +def send(event, context, responseStatus, responseData, physicalResourceId=None, noEcho=False, reason=None): + responseUrl = event['ResponseURL'] + + responseBody = { + 'Status': responseStatus, + 'Reason': reason or "See the details in CloudWatch Log Stream: {}".format(context.log_stream_name), + 'PhysicalResourceId': physicalResourceId or context.log_stream_name, + 'StackId': event['StackId'], + 'RequestId': event['RequestId'], + 'LogicalResourceId': event['LogicalResourceId'], + 'NoEcho': noEcho, + 'Data': responseData + } + + json_responseBody = json.dumps(responseBody) + + print("Response body:") + print(json_responseBody) + + headers = { + 'content-type': '', + 'content-length': str(len(json_responseBody)) + } + + try: + response = http.request( + 'PUT', responseUrl, headers=headers, body=json_responseBody) + print("Status code:", response.status) + return responseBody + + except Exception as e: + + print("send(..) failed executing http.request(..):", e) + return {} + + +def lambda_handler(event, context): + # get the details from the codebuild project from the environment variables and trigger the codebuild project + if event['RequestType'] == 'Create': + try: + codebuild = boto3.client('codebuild') + response = codebuild.start_build( + projectName=os.environ['PROJECT_NAME'], + ) + send(event, context, "SUCCESS", {"message": "CodeBuild project started"}) + except Exception as e: + send(event, context, "FAILED", {"message": str(e)}) + else: + send(event, context, "SUCCESS", {"message": "No action required"}) + + return '{ "status": 200, "message": "success" }' diff --git a/lib/aws/eks.ts b/lib/aws/eks.ts index 72319edf..cf1eb08b 100644 --- a/lib/aws/eks.ts +++ b/lib/aws/eks.ts @@ -16,6 +16,7 @@ import * as ssm from 'aws-cdk-lib/aws-ssm'; import * as elbv2 from "aws-cdk-lib/aws-elasticloadbalancingv2"; import { LockerSetup } from "./card-vault/components"; import { Trigger } from "aws-cdk-lib/triggers"; +import * as codebuild from "aws-cdk-lib/aws-codebuild"; // import { LockerSetup } from "./card-vault/components"; export class EksStack { @@ -31,6 +32,9 @@ export class EksStack { admin_api_key: string, locker: LockerSetup | undefined, ) { + + const ecr = new DockerImagesToEcr(scope); + const cluster = new eks.Cluster(scope, "HSEKSCluster", { version: eks.KubernetesVersion.of("1.28"), kubectlLayer: new KubectlLayer(scope, "KubectlLayer"), @@ -39,6 +43,8 @@ export class EksStack { clusterName: "hs-eks-cluster", }); + cluster.node.addDependency(ecr.codebuildTrigger); + cdk.Tags.of(cluster).add("SubStack", "HyperswitchEKS"); const addClusterRole = (awsArn: string, name: string) => { @@ -720,3 +726,111 @@ class KmsSecrets { this.kms_encrypted_api_hash_key = ssm.StringParameter.valueForStringParameter(scope, "/hyperswitch/kms-encrypted-api-hash-key", 1); } } + +class DockerImagesToEcr { + + codebuildProject: codebuild.Project; + codebuildTrigger: cdk.CustomResource; + + constructor(scope: Construct) { + + const ecrRole = new iam.Role(scope, "ECRRole", { + assumedBy: new iam.ServicePrincipal("codebuild.amazonaws.com"), + }); + + const ecrPolicy = new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: [ + "ecr:CompleteLayerUpload", + "ecr:GetAuthorizationToken", + "ecr:UploadLayerPart", + "ecr:InitiateLayerUpload", + "ecr:BatchCheckLayerAvailability", + "ecr:PutImage", + ], + resources: ["*"], + }), + ] + }); + + ecrRole.attachInlinePolicy( + new iam.Policy(scope, "ECRFullAccessPolicy", { + document: ecrPolicy, + }), + ); + + this.codebuildProject = new codebuild.Project(scope, "ECRImageTransfer", { + environmentVariables: { + AWS_ACCOUNT_ID: { + value: process.env.CDK_DEFAULT_ACCOUNT, + }, + AWS_DEFAULT_REGION: { + value: process.env.CDK_DEFAULT_REGION, + }, + }, + environment: { + buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_5, + }, + role: ecrRole, + buildSpec: codebuild.BuildSpec.fromAsset("./dependencies/code_builder/buildspec.yml"), + }); + + const lambdaStartBuildCode = readFileSync('./dependencies/code_builder/start_build.py').toString(); + + const triggerCodeBuildRole = new iam.Role(scope, "ECRImageTransferLambdaRole", { + assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"), + }); + + const triggerCodeBuildPolicy = new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: [ + "codebuild:StartBuild", + ], + resources: [this.codebuildProject.projectArn], + }), + ] + }); + + const logsPolicy = new iam.PolicyDocument({ + statements: [ + new iam.PolicyStatement({ + actions: [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + ], + resources: ["*"], + }) + ] + }) + + triggerCodeBuildRole.attachInlinePolicy( + new iam.Policy(scope, "ECRImageTransferLambdaPolicy", { + document: triggerCodeBuildPolicy, + }), + ); + + triggerCodeBuildRole.attachInlinePolicy( + new iam.Policy(scope, "ECRImageTransferLambdaLogsPolicy", { + document: logsPolicy, + }), + ); + + const triggerCodeBuild = new Function(scope, "ECRImageTransferLambda", { + runtime: Runtime.PYTHON_3_9, + handler: "index.lambda_handler", + code: Code.fromInline(lambdaStartBuildCode), + timeout: cdk.Duration.minutes(15), + role: triggerCodeBuildRole, + environment: { + PROJECT_NAME: this.codebuildProject.projectName, + }, + }); + + this.codebuildTrigger = new cdk.CustomResource(scope, "ECRImageTransferCR", { + serviceToken: triggerCodeBuild.functionArn, + }); + } +} \ No newline at end of file