Ops

Β TALKS

Knowledge worth sharing

#04

Florian Dambrine - Principal Engineer - @GumGum

Hugo & Preact

Agenda

What DOES it DO

***

Basics

***

DEEP dive

***

CHEATSHEET

What does it do

/ Static site generators /

Static site generators & HUGO

Think of a static site generatorΒ as a script which takes in data, content and templates, processes them, and outputs a folder full of all the resultant pages and assets.

What is Hugo ?

What are Static Site Generators (SSGs) ?

  • Blazing fast SSG written in Go !Β 
  • General-purpose Website framework

Build

Content Delivery Network (CDN)
Browser
Browser
Web Server
App Server
Database

Static

Dynamic

Content Delivery Network (CDN)
Navigateur

Optimal performance (CDN)

More Secured (Behind CDN)

Simplified hosting (S3 bucket)

DΓ©ploiement atomique (CDN Invalidation)

Git History and atomique rollbacks

CMS for content management

Static Websites Rock !

Git Commit
CMS
Automated Deployment
On Save
Hosting (Github Pages, S3)

Static Websites Deployment

hugo WEb sites examples

Basics

/ πŸ™Œ getting Started / Hugo Basics / πŸ™Œ Hands On basics /

Hugo - Getting Started

brew install hugo
hugo new site hugo-tutorial
cd hugo-tutorial
hugo serve
# Visit localhost:1313
git init
git submodule add https://github.com/giraffeacademy/ga-hugo-theme.git themes/ga-hugo-theme
echo 'theme = "ga-hugo-theme"' >> config.toml

Optional

Step 1

Step 2

Step 3

πŸ™Œ Hands On

Hugo BASICS - Markdown DEMYSTIFIED

+++
title = "Verity"
description = "Verity Homepage"
date = 2019-09-03T07:43:09Z
weight = 3
chapter = true
pre = "<b><i class='fa fa-caret-square-up'></i> </b>"
draft = false
+++

# Verity

Welcome to the Verity homepage. This documentation aims to deep dive into Verity eco-system.

Verity eco-system is made of **3 major components**:

* `Verity-Api` submit user requests for processing
* CV microservices for images
* NLP for text processing / keywords extraction / threat classification and a lot more

## Global Architecture Diagram

{{< figure src="img/verity-infrastucture-diagram.png" title="Global infrastructure diagram" >}}

{{% children %}}

Hugo Basics - Folder Layout

.
β”œβ”€β”€ archetypes
β”œβ”€β”€ config.toml
β”œβ”€β”€ content
β”œβ”€β”€ data
β”œβ”€β”€ layouts
β”œβ”€β”€ static
└── themes
  • archetypes content template files in the archetypes directoryΒ of your project that contain preconfigured front matterΒ 
    Β 
  • config.toml Hugo provides a Β large number of configuration directivesΒ this file usually defines global site parameters
    Β 
  • content All content for your website will live inside this directory.
    Β 
  • data This directory is used to store configuration files that can be used by Hugo when generating your website.
    Β 
  • static Stores all the static content: images, CSS, JavaScript, etc. When Hugo builds your site, all assets inside your static directory are copied over as-is.
    Β 
  • layouts Stores templates in the form of .html files that specify how views of your content will be rendered into a static website.

Hugo Basics - Leaf and Page bundles

β”œβ”€β”€ content
β”œβ”€β”€ dir1
β”œβ”€β”€ A1.md
β”œβ”€β”€ A2.md
β”œβ”€β”€ B.md
β”œβ”€β”€ C.md
β”œβ”€β”€ dir2
β”œβ”€β”€ index.md

Page bundle

(list.html)

Leaf bundles

(single.html)

Let's Practice

Let's kick off this Hugo-Tutorial website and understand Hugo internals

https://github.com/Lowess/hugo-tutorial

Fork
# Go inside project
cd hugo-tutorial

# Fetch Hugo theme
git submodule update --init --recursive

# Start Hugo server from a terminal
hugo serve

πŸ™Œ Hands On

GO further...

Full series of youtube tutorials with articles and a github project

-- By Mike Dane

Deep-Dive

/ πŸ™ŒΒ  HUGO CONTINUOUS DEPLOYMENT / πŸ™Œ Static JSON API / πŸ™Œ Headless CMS with forestry /

/ CONTINUOUS DEPLOYMENT /

Hugo Continuous deployment

Create a new SSH Key and store the public one in Github > https://github.com/settings/keys

# /!\ Be careful to not override your ~/.ssh/id_rsa
$ ssh-keygen -t rsa -b 4096 -C "drone-hugo@ops-talks.com

# Enter file in which to save the key (/Users/florian/.ssh/id_rsa): drone-hugo
# Enter passphrase (empty for no passphrase): (empty)

# Your identification has been saved in drone-hugo.
# Your public key has been saved in drone-hugo.pub.

πŸ™Œ Hands On

Login and peer with your Github account https://cloud.drone.io/. Once logged in search for hugo-tutorial and activate the project

Add a your Private SSH key as a Drone Secret. Name it github_ssh_key

git commit --allow-empty -m 'πŸš€ Deploy' && git push

/ Static API /

Hugo to serve a static API

Hugo can output content in multiple formats, including calendar events, e-book formats, Google AMP, and JSON search indexes, or any custom text format.

https://github.com/Lowess/hugo-tutorial

<...>

# JSON templates should go in 
# layouts/_default/{pageKind}.{outputFormatName}.{extension}

[outputs]
  home = [ "HTML", "JSON" ]
  page = ["HTML", "JSON"]
  section = ["HTML", "JSON"]
hugo-tutorial/config.toml

πŸ™Œ Hands On

Hugo to serve a static API (1/2)

layouts/_default/baseof.json.json
{{ define "response" }}
{
  "kind" : "{{ .Kind }}",
  "count" : "{{ len .Pages }}",
  "items" : [
      {{ range $i, $e := .Pages -}}
          {{ if $i }}, {{ end }}{{ .Render "item" }}
      {{ end }}
  ]
}
{{ end }}
layouts/_default/list.json.json

πŸ™Œ Hands On

{
  "data": {{ block "response" .}}{{ end }}
}

Hugo to serve a static API (2/2)

{{ define "response" }}
{{ .Render "item" }}
{{ end }}
layouts/_default/single.json.json
{
  "kind": "{{ .Kind }}",
  "title": "{{ .Title }}",
  "permalink" : "{{ .Permalink }}index.json",
  "length": "{{ len .Content }}"
}
layouts/_default/item.json.json

πŸ™Œ Hands On

/ Forestry headless CMS /

Forestry CMS

πŸ™Œ Hands On

Go to Forestry.io and sign in using your Github account

  • Add a new site
  • Pick Hugo as static site generator (leave version as is)
  • Search for hugo-tutorial and choose master branch
  • In Settings > General > Deploy Admin

Deep-Dive (2)

/ πŸ™ŒΒ  HUGO & Preact / Jamstack with aWS Amplify /

HUGO & Javascript Frameworks ?

Image Processing | Asset minification | SASS POSTCSS

Hugo Pipes is Hugo’s asset processing set of functions.

Let's Practice

docker run --rm -it \
    -w /app \
    -v $(pwd):/app \
    lowess/preact-cli \
    create Lowess/preact-hugo hugo-js

Let's kick off this Hugo website and bisect its directory layout

docker run --rm \
    --entrypoint="" \
    -p 3000:3000 \
    -e HOST=0.0.0.0 \
    -w /app \
    -v $(pwd):/app \
    lowess/preact-cli npm run start

πŸ™Œ Hands On

JAMSTACK WITH AWS AMPLIFY - Overview

Hosting

Auth

Backend / API

  • S3
  • CloudFront
  • Lambda@Edge
  • R53
  • Cognito
  • Google Federation
  • Lambda
  • AppSync
  • Lambda

JAMSTACK - Hosting on cloudfront CDN

Hosting

S3 / CloudFront / Lambda@Edge / R53

Why Lambda@Edge ?

[...] CloudFront does allow you to specify a default root object (index.html), but it only works on the root of the website (such as http://www.example.com > http://www.example.com/index.html). It does not work on any subdirectory (such as http://www.example.com/about/). If you were to attempt to request this URL through CloudFront, CloudFront would do a S3 GetObject API call against a key that does not exist. [...]

CI / CD

# Lambda@edge - subdir-index.js
'use strict';

exports.handler = (event, context, callback) => {
    // Extract the request from the CloudFront event that is sent to Lambda@Edge
    var request = event.Records[0].cf.request;

    // Extract the URI from the request
    var olduri = request.uri;

    // Match any '/' that occurs at the end of a URI. Replace it with a default index
    var newuri = olduri.replace(/\/$/, '\/index.html');

    // Log the URI as received by CloudFront and the new URI to be used to fetch from origin
    console.log("Old URI: " + olduri + " New URI: " + newuri);

    // Replace the received URI with the URI that includes the index page
    request.uri = newuri;

    // Return to CloudFront
    return callback(null, request);
};

JAMSTACK - Authentication with Cognito

Authentication

Cognito / Google Federation / Lambda

OAuth Flow

PreSignUp Lambda

- Domain validation - (ensure email address is part of whitelisted domains)

OAuth Challenge

JAMSTACK - Authentication with Cognito

Authentication

Cognito / Google Federation / Lambda

# Authenticate User with Amplify Cognito Federation
useEffect(() => {
    Hub.listen("auth", ({ payload: { event, data } }) => {
      switch (event) {
        case "signIn":
          setUser({ user: data })
          break;
        case "signOut":
          setUser({ user: null })
          break;
      }
    })

    Auth.currentAuthenticatedUser()
      .then(user => {
        setUser({ user })
        context.login(user)
      })
      .catch(() => console.log("Not signed in"));
  }, []);
# Trigger Cognito OAuth Flow 
const SignIn = () => {
  return (
    <div class="flair-signin mx-auto card">
      <div class="card-body">
        <h1>Flair</h1>
        <h6 class="card-subtitle"><i>Dive into Verity with elegance...</i></h6>
        <button
           class="btn btn-danger w-100"
           onClick={() => Auth.federatedSignIn({provider: 'Google'})}
        >
          <FaGoogle /> Signin with Google
        </button>
        <hr />
        <button
          class="btn btn-default w-100"
           onClick={() => Auth.federatedSignIn()}
        >
          <FaAws /> Sigin with AWS Web UI
        </button>
      </div>
    </div>
  );
}

JAMSTACK - Backend / api with appsync

Backend / api

AppSync & Lambda Direct resolvers

AppSync VTL
With Direct Resolver

JAMSTACK - Backend / api with appsync

Backend / api

import { API } from '@aws-amplify/api';

const fetchFromAppSync = async ({graphQLQuery, graphQLVars}) => {
    // Run a GraphQL query on AWS AppSync
    const { data } = await API.graphql({
        query: graphQLQuery,
        authMode: 'AMAZON_COGNITO_USER_POOLS',
        variables: graphQLVars,
    });

    // GraphQL results come with the name of the query as a key { "graphQLQuery": {<data>} }
    const result = Object.values(data)[0];

    console.log(`[fetchFromAppSync] received ${JSON.stringify(result)}`);
}
type Query {
  getVerityPageByPageUrl(data_source: String, page_url: String!, env: String): VerityPage
  getVerityVideoByUuid(uuid: String!, env: String): VerityVideo
}

type VerityPage {
  page_url: String!
  ...
}
    
type VerityVideo {
  uuid: String!
  ...
}
(1) Export schema.json
(2) Run aws amplify codegen

JAMSTACK - Backend / api with appsync

Backend / api

AppSync / Lambda

import { API } from '@aws-amplify/api';

const fetchFromAppSync = async (
    {graphQLQuery, graphQLVars}
  ) => {
    const { data } = await API.graphql({
        query: graphQLQuery,
        authMode: 'AMAZON_COGNITO_USER_POOLS',
        variables: graphQLVars,
    });
}

graphQLQuery = getVerityPageByPageUrl

graphQLVars = { page_url: "http://example.com",

env: "prod" }

πŸ™Œ DEMO

JAMSTACK - Backend / api with appsync

Backend / api

AppSync / Lambda

def handler(event, context) -> Dict[Any, Any]:
    # Parse event payload
    arguments = event.get("arguments", {})
    graphql = event.get("info", {})
    query = graphql.get("fieldName", None)

    response = {}

    env = arguments.get("env", "prod")

    # Switch case routing based on GraphQL queries
    if query == "getVerityPageByPageUrl":
        page_response = dynamodb_to_object(
            get_page_by_url(page_url=arguments["page_url"], env=env)
        )

    elif query == "getVerityVideoByUuid":
        response = dynamodb_to_object(
            get_video_by_uuid(uuid=arguments["uuid"], env=env)
        )

    return response

πŸ™Œ DEMO

CHEATSHEET

Ops

Β TALKS

Knowledge worth sharing

By Florian