articles/aws static website presentation

Static Websites in AWS

Use only US-East (N. Virginia). Some functions are not available elsewhere.

Goal

S3

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicReadGetObject",
            "Effect": "Allow",
            "Principal": "*",
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::bucket.name/*.html"
        }
    ]
}

Route 53

ACM

CloudFront

Lambda

'use strict';
exports.handler = (event, context, callback) => {
    var request = event.Records[0].cf.request;
    var uri = request.uri;

    // Add /index.html to directory listings
    uri = uri.replace(/\/$/, '\/index.html');

    // Add .html when suffix missing
    uri = uri.replace(/\/([a-z0-9_\-]+)$/i, '/$1.html')

    console.log("Old URI: " + request.uri);
    console.log("New URI: " + uri);
    request.uri = uri;
    return callback(null, request);

};

CircleCI

version: 2
jobs:
  build:
    docker:
      - image: circleci/python:3.6.1
    working_directory: ~/repo

    steps:
      - checkout

      - restore_cache:
          keys:
          - v1-dependencies-{{ checksum ".circleci/requirements.txt" }}
          - v1-dependencies-

      - run:
          name: install dependencies
          command: |
            python3 -m venv venv
            . venv/bin/activate
            pip install -r .circleci/requirements.txt

      - save_cache:
          paths:
            - ./venv
          key: v1-dependencies-{{ checksum ".circleci/requirements.txt" }}

      - run:
          name: sync with s3
          command: |
            . venv/bin/activate
            aws s3 sync . s3://bucket.name --exclude "*" --include "*.md"

Pricing

All prices are per month

S3

Cloudfront

Lambda

Route 53

CircleCI

Github

Pricing example

Average sized blog

Montly TCO

Bonus

You can automate S3 to generate html from md files.

import urllib.parse
import boto3
import re
import markdown2


s3 = boto3.client('s3')

def get_header(metadata):
    return f'''
<html>
    <header>
        <title>{metadata['title']}</title>
    </header>
<body style="font-size: 130%">
    '''


def get_footer():
    return '''
    </body>
</html>
    '''


def get_title(key):
    title = re.sub('^.*/', '', key)
    title = re.sub('\.\w+$', '', key)
    return title.replace('-', ' ')


def get_html_key(key):
    return re.sub('\.md$', '.html', key)


def write_html(bucket, key, content):
    content = content.encode("utf-8")
    bucket_name = bucket
    s3.put_object(Bucket=bucket, Key=key, Body=content, ContentType='text/html')


def get_html(key, md_body):
    html_body = markdown2.markdown(md_body, extras=[
            'fenced-code-blocks',
            'header-ids',
            'metadata'
        ])
    metadata = html_body.metadata
    metadata.setdefault('title', get_title(key))
    return get_header(html_body.metadata) + html_body + get_footer()


def get_md(bucket, key):
    response = s3.get_object(Bucket=bucket, Key=key)
    return response['Body'].read().decode('utf-8')


def parse_event(event):
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(
        event['Records'][0]['s3']['object']['key'],
        encoding='utf-8'
    )
    return bucket, key


def lambda_handler(event, context):
    try:
        bucket, key = parse_event(event)
        md_body = get_md(bucket, key)
        html_key = get_html_key(key)
        html_body = get_html(key, md_body)
        write_html(bucket, html_key, html_body)
        return True
    except Exception as e:
        print(e)
        raise e

The above code is dependent on Markdown2. You can just insert the code, no need for instalation