Skip to content

Instantly share code, notes, and snippets.

@matthewdowney
Created March 11, 2023 01:19
Show Gist options
  • Select an option

  • Save matthewdowney/eb5ae210627e16a6794f6a408e42cc11 to your computer and use it in GitHub Desktop.

Select an option

Save matthewdowney/eb5ae210627e16a6794f6a408e42cc11 to your computer and use it in GitHub Desktop.

Revisions

  1. matthewdowney created this gist Mar 11, 2023.
    82 changes: 82 additions & 0 deletions deploy.clj
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,82 @@
    (ns deploy
    (:require [clojure.java.io :as io])
    (:import (software.amazon.awscdk App CfnOutput$Builder Stack)
    (software.amazon.awscdk.services.apigatewayv2.alpha AddRoutesOptions HttpApi$Builder HttpMethod)
    (software.amazon.awscdk.services.apigatewayv2.authorizers.alpha HttpIamAuthorizer)
    (software.amazon.awscdk.services.apigatewayv2.integrations.alpha HttpAlbIntegration)
    (software.amazon.awscdk.services.ec2 Vpc$Builder)
    (software.amazon.awscdk.services.ecs Cluster$Builder ContainerImage)
    (software.amazon.awscdk.services.ecs.patterns ApplicationLoadBalancedFargateService$Builder ApplicationLoadBalancedTaskImageOptions)
    (software.amazon.awscdk.services.iam AnyPrincipal)
    (software.amazon.awscdk.services.secretsmanager Secret)))

    ;; A CDK 'app' is the root of a tree of CDK 'constructs', which can be
    ;; CloudFormation stacks or (trees of) AWS resources.
    ;; https://docs.aws.amazon.com/cdk/v2/guide/constructs.html#constructs_tree
    (def app (App.))

    ;; The CloudFormation stack containing the resources. Stateful resources could
    ;; be separated out into a different stack, to better control possible
    ;; operations that would destroy existing state (e.g. in a db).
    ;; https://docs.aws.amazon.com/cdk/v2/guide/best-practices.html#best-practices-apps-separate
    (def stack (Stack. app "AccountingStack"))

    ;;; The resources themselves

    (def vpc (-> (Vpc$Builder/create stack "vpc") (.maxAzs 3) (.natGateways 1) .build))

    ;; ECS cluster (used by fargate)
    (def ecs-cluster
    (-> (Cluster$Builder/create stack "ecs-cluster") (.vpc vpc) .build))

    ;; The API keys that the server needs
    (def api-conf (read-string (slurp (io/resource "conf.edn"))))

    (defn ecs-secret [{:keys [aws-secret]}]
    (software.amazon.awscdk.services.ecs.Secret/fromSecretsManager
    (Secret/fromSecretNameV2 stack aws-secret aws-secret)))

    ;; The code under ./server, as a Fargate service.
    (def fargate-service
    (let [docker-image (-> (ApplicationLoadBalancedTaskImageOptions/builder)
    (.image (ContainerImage/fromAsset "server"))
    (.secrets (update-vals api-conf ecs-secret))
    (.enableLogging true)
    .build)]
    (-> (ApplicationLoadBalancedFargateService$Builder/create stack "fargate-service")
    (.cluster ecs-cluster)
    (.taskImageOptions docker-image)
    (.desiredCount 1)
    (.cpu 256)
    (.memoryLimitMiB 1024)
    (.publicLoadBalancer false)
    .build)))

    ;;; The API Gateway

    (def api-endpoint
    (-> (HttpApi$Builder/create stack "HttpProxyPrivateAPI")
    (.defaultAuthorizer (HttpIamAuthorizer.))
    .build))

    (def default-route
    (-> (AddRoutesOptions/builder)
    (.integration (HttpAlbIntegration. "DefaultIntegration" (.getListener fargate-service)))
    (.path "/{proxy+}")
    (.methods [(HttpMethod/ANY)])
    .build))

    ;; Add the route to the API and grant access to any IAM users in this account
    (let [route (first (.addRoutes api-endpoint default-route))]
    (.grantInvoke route (AnyPrincipal.)))

    ;; Add the URL to the outputs
    (-> (CfnOutput$Builder/create stack "apiGatewayURL")
    (.value (.getUrl api-endpoint))
    (.description "The API gateway URL.")
    (.exportName "apiGatewayURL")
    .build)

    (defn synth [& args]
    (println "Synthesized to:" (.getDirectory (.synth app)))
    (.getDirectory (.synth app)))