CloudClamp

2020/05/13

#fsharp #aws #cloud

Table of contents

CloudClamp

Type safe infrastructure as code with the full power of F#.

Why?

I am tired of dealing with configuration files and interpreters that has the expresiveness of Go, safetiness of C and performance of Ruby. Illegal configuration must be made impossible by the type system. ADTs are great for this. Compilation has to check as much as possible and using all language features is a must. C# has libraries for pretty much every single vendor out there or it is trivial to implement the lacking support. There is a giant community of senior software engineers available on StackOverflow or LinkedIN. Debugging and performance tracing is largely solved.

Usage

CloudClamp has 3 concepts:

cloudclamp --stage prod --command deploy --service CloudClamp.Website

Right now there is no local state but this might change in the future. State must be per service/stage to avoid deployments blocking each other. There is a small amount of configuration in JSON (type safe) to configure basic things. More complex things live in code.

Example resource (website)

This website uses AWS S3. It only has one stage: prod. The bucket configuration is typed. You cannot accidentally try to create illegal configuration. Possible configurations can be narrowed down by the actual company or department. For example, public buckets can be disabled. Tagging is flexible, you can add more for billing breakdown purposes.

  // Tags

    let websiteTags = 
      [   ("Name", "l1x.be");   ("Environment", "website"); 
          ("Scope", "global");  ("Stage", "prod");         ]

    // logs.l1x.be

    let s3BucketWithConfigLogs = 
      createPrivateBucketConfig 
        "logs.l1x.be"     // name
        "eu-west-1"       // region
        "prod"            // stage
        websiteTags       // tagging
        None              // policy
        None              // logging
    
    createS3Bucket amazonS3client s3BucketWithConfigLogs |> ignore
    
    // dev.l1x.be

    let websiteDocuments : WebsiteDocuments = 
      { IndexDocument = "index.html"; ErrorDocument = "error.html"; }  

    let s3BucketWithConfigDev = 
      createWebsiteBucketConfig 
        "dev.l1x.be"        // name
        "eu-west-1"         // region
        "prod"              // stage
        websiteDocuments    // website
        websiteTags         // tagging
        None                // policy
        None                // logging

    createS3Bucket amazonS3client s3BucketWithConfigDev |> ignore
    
    // redirect l1x.be -> dev.l1x.be

    let redirectTo : RedirectOnly = 
      { RedirectTo = "dev.l1x.be" }

    let s3BucketWithConfigApex = 
      createRedirectBucketConfig 
        "l1x.be"          // name
        "eu-west-1"       // region
        "prod"            // stage
        redirectTo        // website
        websiteTags       // tagging
        None              // policy
        None              // logging
   
    createS3Bucket amazonS3client s3BucketWithConfigApex |> ignore

Cloud Resources

AWS

IAM

ACM

Route53

S3

Bucket

Bucket life cycle:

sequenceDiagram
    participant I as Initial
    participant N as NonExistent
    participant C as Created
    participant E as Err

    I->>C: getState
    I->>E: getState
    I->>N: getState

    N->>C: putBucket
    N->>E: putBucket

    C->>C:   PutBucketTagging
    C->>E:   PutBucketTagging

    C->>C:  PutBucketWebsite
    C->>E:   PutBucketWebsite

    C->>C:  PutBucketPolicy
    C->>E:   PutBucketPolicy

    C-->>C: DeleteBucketTagging
    C-->>E: DeleteBucketTagging

    C-->>C: DeleteBucketWebsite
    C-->>E:  DeleteBucketWebsite  
    
    C-->>C: DeleteBucketPolicy
    C-->>E:  DeleteBucketPolicy  

    C-->>N: deleteBucket
    C-->>E: deleteBucket

Api Gateway

Lambda

DynamoDB

Fargate