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.
Inspired by https://github.com/netlify-templates/victor-hugo
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 (ReInvent 2017) is
- A Public API
- With a GraphQL layer (retrieve only what you need)
- ...with ability to check authenticated users from Cognito
- A Public API
- Since August 2020 - Appsync offers 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