├── 0-azure
├── DEPLOY.txt
├── azuredeploy.json
├── EMPTY.sh
├── DEPLOY.sh
└── README.md
├── 2-functions
├── .gitignore
├── go.mod
├── BUILD.sh
├── README.md
├── TimerTrigger
│ └── function.json
├── api
│ └── function.json
├── auth
│ └── function.json
├── healthz
│ └── function.json
├── HttpTrigger
│ └── function.json
├── host.json
├── main_test.go
├── host_advanced.json
├── main.go
└── DEPLOY.sh
├── 1-hugo
├── README.md
└── hugo.sh
├── 3-containers
├── README.md
├── Dockerfile
├── main.go
└── DEPLOY.sh
├── .gitignore
├── .github
└── workflows
│ ├── 1-hugo-deploy.yml
│ ├── 0-azure.yml
│ ├── 2-functions.yml
│ ├── 3-containers.yml
│ ├── 1-hugo-setup.yml
│ ├── 0-azure-empty.yml
│ └── 0-azure-storage.yml
├── LICENSE
├── GET-GOING-WITH-GITHUB-ACTIONS.md
├── README.md
├── GO-SERVERLESS-CONTAINERS-CLOUD.md
└── HUGO-GITHUB-PAGES-ACTIONS.md
/0-azure/DEPLOY.txt:
--------------------------------------------------------------------------------
1 | ping
2 |
--------------------------------------------------------------------------------
/2-functions/.gitignore:
--------------------------------------------------------------------------------
1 | bin/*
2 |
--------------------------------------------------------------------------------
/2-functions/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/the-gophers/hello-gopher
2 |
3 | go 1.13
4 |
--------------------------------------------------------------------------------
/1-hugo/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | See: [Tell your Story with Hugo, GitHub Pages and GitHub Actions Workflows](../HUGO-GITHUB-PAGES-ACTIONS.md)
4 |
5 |
--------------------------------------------------------------------------------
/2-functions/BUILD.sh:
--------------------------------------------------------------------------------
1 | mkdir -p bin/
2 |
3 | echo "GOOS=linux GOARCH=amd64 go build"
4 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o bin/hello-gopher .
5 |
--------------------------------------------------------------------------------
/2-functions/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | See: [Go for Serverless and Containers in the Cloud](../GO-SERVERLESS-CONTAINERS-CLOUD.md#2-serverless-go-functions)
4 |
5 |
--------------------------------------------------------------------------------
/3-containers/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | See: [Go for Serverless and Containers in the Cloud](../GO-SERVERLESS-CONTAINERS-CLOUD.md#3-containers-and-serverless-container-instances)
4 |
5 |
--------------------------------------------------------------------------------
/0-azure/azuredeploy.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3 | "contentVersion": "1.0.0.0",
4 | "resources": []
5 | }
6 |
7 |
--------------------------------------------------------------------------------
/2-functions/TimerTrigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "name": "myTimer",
5 | "type": "timerTrigger",
6 | "direction": "in",
7 | "runOnStartup": true,
8 | "schedule": "* * * * *"
9 | }
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/0-azure/EMPTY.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | # variables
5 | RESOURCE_GROUP='201100-gophercon'
6 |
7 | echo "emptying resource group: ${RESOURCE_GROUP}"
8 | az deployment group create --resource-group $RESOURCE_GROUP \
9 | --template-file azuredeploy.json \
10 | --mode Complete \
11 | --name empty
12 |
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Binaries for programs and plugins
2 | *.exe
3 | *.exe~
4 | *.dll
5 | *.so
6 | *.dylib
7 |
8 | # Test binary, built with `go test -c`
9 | *.test
10 |
11 | # Output of the go coverage tool, specifically when used with LiteIDE
12 | *.out
13 |
14 | # Dependency directories (remove the comment below to include it)
15 | # vendor/
16 | .idea
17 |
--------------------------------------------------------------------------------
/2-functions/api/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "httpTrigger",
5 | "authLevel": "anonymous",
6 | "direction": "in",
7 | "name": "req",
8 | "route": "api/{*restOfPath}"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "res"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/2-functions/auth/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "httpTrigger",
5 | "authLevel": "function",
6 | "direction": "in",
7 | "name": "req",
8 | "route": "auth/{*restOfPath}"
9 | },
10 | {
11 | "type": "http",
12 | "direction": "out",
13 | "name": "res"
14 | }
15 | ]
16 | }
17 |
--------------------------------------------------------------------------------
/2-functions/healthz/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "httpTrigger",
5 | "direction": "in",
6 | "name": "req",
7 | "methods": [
8 | "get"
9 | ],
10 | "authLevel": "anonymous"
11 | },
12 | {
13 | "type": "http",
14 | "direction": "out",
15 | "name": "res"
16 | }
17 | ]
18 | }
--------------------------------------------------------------------------------
/2-functions/HttpTrigger/function.json:
--------------------------------------------------------------------------------
1 | {
2 | "bindings": [
3 | {
4 | "type": "httpTrigger",
5 | "direction": "in",
6 | "name": "req",
7 | "methods": [
8 | "get"
9 | ],
10 | "authLevel": "anonymous"
11 | },
12 | {
13 | "type": "http",
14 | "direction": "out",
15 | "name": "res"
16 | }
17 | ]
18 | }
19 |
--------------------------------------------------------------------------------
/2-functions/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "customHandler": {
4 | "description": {
5 | "defaultExecutablePath": "bin/hello-gopher",
6 | "arguments": [],
7 | "workingDirectory": ""
8 | },
9 | "enableForwardingHttpRequest": true
10 | },
11 | "extensions": {
12 | "http": {
13 | "routePrefix": ""
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/3-containers/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM golang:rc-alpine as builder
2 | RUN apk add --no-cache ca-certificates
3 | WORKDIR /home
4 | COPY . /home/
5 | RUN set -x && env CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o hello-echo
6 |
7 | FROM scratch
8 | COPY --from=builder /etc/ssl/certs/ /etc/ssl/certs
9 | COPY --from=builder /home/hello-echo /usr/bin/hello-echo
10 | ENTRYPOINT [ "hello-echo" ]
11 |
--------------------------------------------------------------------------------
/2-functions/main_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "net/http"
5 | "net/http/httptest"
6 | "testing"
7 | )
8 |
9 | func TestHello(t *testing.T) {
10 | t.Run("api/hello", func(t *testing.T) {
11 | request, _ := http.NewRequest(http.MethodGet, "/api/hello", nil)
12 | response := httptest.NewRecorder()
13 |
14 | httpHello(response, request)
15 |
16 | got := response.Body.String()
17 | want := "Hello API!\n"
18 |
19 | if got != want {
20 | t.Errorf("got %q, want %q", got, want)
21 | }
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/2-functions/host_advanced.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "customHandler": {
4 | "description": {
5 | "defaultExecutablePath": "bin/hello-gopher",
6 | "arguments": [],
7 | "workingDirectory": ""
8 | },
9 | "enableForwardingHttpRequest": true
10 | },
11 | "extensions": {
12 | "http": {
13 | "routePrefix": ""
14 | }
15 | },
16 | "logging": {
17 | "logLevel": {
18 | "default": "Trace"
19 | },
20 | "applicationInsights": {
21 | "samplingExcludedTypes": "Request",
22 | "samplingSettings": {
23 | "isEnabled": true
24 | }
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/.github/workflows/1-hugo-deploy.yml:
--------------------------------------------------------------------------------
1 | name: 1-hugo-deploy
2 |
3 | on:
4 | push:
5 | branches:
6 | - gh-pages-content
7 |
8 | jobs:
9 | build-and-deploy:
10 | runs-on: ubuntu-latest
11 | steps:
12 | - uses: actions/checkout@v2
13 | - name: hugo.sh
14 | env:
15 | GITHUB_SHA: ${{ github.sha }}
16 | SITE_NAME: 1-hugo
17 | run: |
18 | git config --global user.name github-actions
19 | git config --global user.email github-actions@github.com
20 | cd /home/
21 | source $GITHUB_WORKSPACE/1-hugo/hugo.sh
22 | hugo-install-linux
23 | cd $GITHUB_WORKSPACE/
24 | hugo-github-deploy
25 |
26 |
--------------------------------------------------------------------------------
/0-azure/DEPLOY.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | # variables
5 | RESOURCE_GROUP='201100-gophercon'
6 | LOCATION='eastus'
7 | SUBSCRIPTION_ID=$(az account show | jq -r .id)
8 | SCOPE="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}"
9 | # RANDOM_STR='da39a3'
10 | RANDOM_STR=$(echo -n "$SCOPE" | shasum | head -c 6)
11 | CREATE_IF_EXISTS="false"
12 | # automatically set by actions workflow, but in case we're running locally
13 | # [[ -z "${GITHUB_SHA:-}" ]] && GITHUB_SHA='test'
14 | [[ -z "${GITHUB_SHA:-}" ]] && GITHUB_SHA=$(git rev-parse --short HEAD)
15 |
16 | az group show --name ${RESOURCE_GROUP}
17 |
18 | az resource list --resource-group ${RESOURCE_GROUP} | jq -c '.[]'
19 |
20 |
--------------------------------------------------------------------------------
/.github/workflows/0-azure.yml:
--------------------------------------------------------------------------------
1 | name: 0-azure
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - 0-azure/**
9 | workflow_dispatch:
10 | inputs:
11 | debug:
12 | description: 'debug'
13 | default: 'true'
14 | required: true
15 |
16 | jobs:
17 | deploy:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v2
21 | - uses: azure/login@v1
22 | with:
23 | creds: ${{ secrets.AZURE_CREDENTIALS }}
24 | - name: run DEPLOY.sh
25 | env:
26 | AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
27 | GITHUB_SHA: ${{ github.sha }}
28 | run: |
29 | cd $GITHUB_WORKSPACE/
30 | source 0-azure/DEPLOY.sh
31 |
32 |
--------------------------------------------------------------------------------
/.github/workflows/2-functions.yml:
--------------------------------------------------------------------------------
1 | name: 2-functions
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - 2-functions/**
9 | workflow_dispatch:
10 | inputs:
11 | debug:
12 | description: 'debug'
13 | default: 'true'
14 | required: true
15 |
16 | jobs:
17 | build-and-deploy:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v2
21 | - uses: azure/login@v1
22 | with:
23 | creds: ${{ secrets.AZURE_CREDENTIALS }}
24 | - name: run DEPLOY.sh
25 | env:
26 | AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
27 | GITHUB_SHA: ${{ github.sha }}
28 | DEBUG: ${{ github.event.inputs.debug }}
29 | run: |
30 | cd $GITHUB_WORKSPACE/2-functions/
31 | source DEPLOY.sh
32 |
33 |
--------------------------------------------------------------------------------
/.github/workflows/3-containers.yml:
--------------------------------------------------------------------------------
1 | name: 3-containers
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | paths:
8 | - 3-containers/**
9 | workflow_dispatch:
10 | inputs:
11 | debug:
12 | description: 'debug'
13 | default: 'true'
14 | required: true
15 |
16 | jobs:
17 | build-and-deploy:
18 | runs-on: ubuntu-latest
19 | steps:
20 | - uses: actions/checkout@v2
21 | - uses: azure/login@v1
22 | with:
23 | creds: ${{ secrets.AZURE_CREDENTIALS }}
24 | - name: run DEPLOY.sh
25 | env:
26 | AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
27 | GITHUB_SHA: ${{ github.sha }}
28 | DEBUG: ${{ github.event.inputs.debug }}
29 | run: |
30 | cd $GITHUB_WORKSPACE/3-containers/
31 | source DEPLOY.sh
32 |
33 |
--------------------------------------------------------------------------------
/.github/workflows/1-hugo-setup.yml:
--------------------------------------------------------------------------------
1 | name: 1-hugo-setup
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | site_title:
7 | description: 'Site Title'
8 | default: 'Hello, GopherCon!'
9 | required: true
10 |
11 | jobs:
12 | deploy:
13 | runs-on: ubuntu-latest
14 | steps:
15 | - uses: actions/checkout@v2
16 | - name: hugo.sh
17 | env:
18 | GITHUB_SHA: ${{ github.sha }}
19 | SITE_TITLE: ${{ github.event.inputs.site_title }}
20 | SITE_BASEURL: "/${{ github.event.repository.name }}/"
21 | SITE_NAME: 1-hugo
22 | run: |
23 | git config --global user.name github-actions
24 | git config --global user.email github-actions@github.com
25 | cd /home/
26 | source $GITHUB_WORKSPACE/1-hugo/hugo.sh
27 | hugo-install-linux
28 | cd $GITHUB_WORKSPACE/
29 | hugo-new-site
30 | hugo-github-init
31 | hugo-github-deploy
32 |
33 |
--------------------------------------------------------------------------------
/.github/workflows/0-azure-empty.yml:
--------------------------------------------------------------------------------
1 | name: 0-azure-empty
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | resource_group:
7 | description: 'Resource Group to empty.'
8 | default: '201100-gophercon'
9 | required: true
10 | debug:
11 | description: 'debug'
12 | default: 'true'
13 | required: true
14 |
15 | jobs:
16 | az-deployment:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v2
20 | - uses: azure/login@v1
21 | with:
22 | creds: ${{ secrets.AZURE_CREDENTIALS }}
23 | - name: az deployment group create
24 | env:
25 | RESOURCE_GROUP: ${{ github.event.inputs.resource_group }}
26 | AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
27 | GITHUB_SHA: ${{ github.sha }}
28 | run: |
29 | cd $GITHUB_WORKSPACE/
30 | echo "emptying resource group: ${RESOURCE_GROUP}"
31 | az deployment group create --resource-group $RESOURCE_GROUP \
32 | --template-file 0-azure/azuredeploy.json \
33 | --mode Complete \
34 | --name empty
35 |
36 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 the-gophers
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/3-containers/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "log"
6 | "net/http"
7 | "net/http/httputil"
8 | "os"
9 | )
10 |
11 | func main() {
12 | web()
13 | }
14 |
15 | func web() {
16 | http.HandleFunc("/", httpHello)
17 | http.HandleFunc("/echo", httpEcho)
18 | http.HandleFunc("/host", httpHost)
19 | listenPort := ":80"
20 | if val := os.Getenv("LISTEN_PORT"); val != "" {
21 | listenPort = ":" + val
22 | }
23 | fmt.Printf("Listening on %s\n", listenPort)
24 | err := http.ListenAndServe(listenPort, httpLog(http.DefaultServeMux))
25 | if err != nil {
26 | log.Fatal(err)
27 | }
28 | }
29 |
30 | func httpHello(w http.ResponseWriter, r *http.Request) {
31 | fmt.Fprintf(w, "Hello Gopher!\n")
32 | }
33 |
34 | func httpEcho(w http.ResponseWriter, r *http.Request) {
35 | b, err := httputil.DumpRequest(r, true)
36 | if err != nil {
37 | log.Printf("Error: %s\n", b)
38 | return
39 | }
40 | log.Printf("%s\n", b)
41 | fmt.Fprintf(w, "%s", b)
42 | }
43 |
44 | func httpHost(w http.ResponseWriter, r *http.Request) {
45 | if hostname, err := os.Hostname(); err == nil {
46 | fmt.Fprintf(w, "Hostname: %s\n", hostname)
47 | }
48 | }
49 |
50 | func httpLog(handler http.Handler) http.Handler {
51 | return http.HandlerFunc(
52 | func(w http.ResponseWriter, r *http.Request) {
53 | log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
54 | handler.ServeHTTP(w, r)
55 | })
56 | }
57 |
--------------------------------------------------------------------------------
/0-azure/README.md:
--------------------------------------------------------------------------------
1 | # Deploy to Azure with GitHub Actions
2 |
3 | You will need an Azure Subscription (e.g. [Free](https://aka.ms/azure-free-account) or [Student](https://aka.ms/azure-student-account)) to be able to authenticate your GitHub Actions against Azure.
4 |
5 | ## 1. Create Azure Service Principal
6 |
7 | Open your local [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest) (+[jq](https://stedolan.github.io/jq/download/)), the [Azure Cloud Shell (bash)](https://docs.microsoft.com/en-us/azure/cloud-shell/quickstart) or and run the following snippet:
8 |
9 | ```bash
10 | RESOURCE_GROUP='201100-gophercon'
11 | LOCATION='eastus'
12 | SUBSCRIPTION_ID=$(az account show | jq -r .id)
13 |
14 | az group create -n $RESOURCE_GROUP -l $LOCATION
15 |
16 | SP=$(az ad sp create-for-rbac --sdk-auth -n $RESOURCE_GROUP --role contributor \
17 | --scopes "/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}")
18 | echo $SP | jq -c
19 | ```
20 |
21 | ## 2. Create GitHub Actions Secret
22 |
23 | Copy the JSON output above and create a secret named `AZURE_CREDENTIALS` under `Settings > Secrets` in your GitHub repository's [Settings](../../../settings/secrets).
24 |
25 | ## 3. Modify [DEPLOY.sh](DEPLOY.sh) and trigger a build
26 |
27 | 1. Set the correct variables for `RESOURCE_GROUP`, etc, under `# variables` in [DEPLOY.sh](DEPLOY.sh).
28 | 1. Modify [DEPLOY.txt](DEPLOY.txt) to trigger a build.
29 |
30 |
--------------------------------------------------------------------------------
/GET-GOING-WITH-GITHUB-ACTIONS.md:
--------------------------------------------------------------------------------
1 | # Get Go-ing with GitHub Actions
2 |
3 | Welcome to "Get Go-ing with GitHub Actions" ([aka.ms/go-actions](https://aka.ms/go-actions)) delivered first at GopherCon 2020 (November 9th-13th) by Aaron Wislang ([@as\_w](https://twitter.com/as_w)) and David Justice ([@davidjustice](https://twitter.com/davidjustice)).
4 |
5 | We will have two in-person sessions on:
6 |
7 | - Monday November 9th from 12:00 PM-3:00 PM EST (3 Hours). See:
8 | - Tuesday, November from 10th 6:00 PM - 9:00 PM EST (3 Hours). See:
9 |
10 | We will continue to run our lab asynchronously on the GopherCon Discord channel throughout GopherCon via the \#microsoft (sponsors) and #github-actions (speakers) text and voice (workshop instructors) channels. You can also reach out to Aaron (Aaron W#0101) and/or David (devigned#6504), any time.
11 |
12 | You can also join us for our Microsoft AMA @ 12:30 PM-2:00 PM EST daily from Wednesday 11 to Friday 13th (see: https://www.gophercon.com/agenda ), followed by other sessions each day.
13 |
14 | ## Lab Instructions
15 | - We are now live! See: [README.md](README.md)
16 |
17 | ## Requirements
18 | - A GitHub Account, of any kind, which has not exceeded its monthly GitHub Actions quota.
19 | - An Azure Subscription (e.g. [Free](https://aka.ms/azure-free-account) or [Student](https://aka.ms/azure-student-account), or other), for the Actions that deploy Go Functions, Containers, or Storage, to the cloud.
20 |
21 |
--------------------------------------------------------------------------------
/3-containers/DEPLOY.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | # variables
5 | RESOURCE_GROUP='201100-gophercon'
6 | LOCATION='eastus'
7 | SUBSCRIPTION_ID=$(az account show | jq -r .id)
8 | SCOPE="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}"
9 | # RANDOM_STR='da39a3'
10 | RANDOM_STR=$(echo -n "$SCOPE" | shasum | head -c 6)
11 | CREATE_IF_EXISTS="false"
12 | # automatically set by actions workflow, but in case we're running locally
13 | # [[ -z "${GITHUB_SHA:-}" ]] && GITHUB_SHA='test'
14 | [[ -z "${GITHUB_SHA:-}" ]] && GITHUB_SHA=$(git rev-parse --short HEAD)
15 |
16 | REPOSITORY_NAME="hello-gopher"
17 | # create container registry
18 | REGISTRY_NAME="acr${RANDOM_STR}"
19 | az acr create -g $RESOURCE_GROUP -l $LOCATION --name $REGISTRY_NAME --sku Basic --admin-enabled true
20 | # build image
21 | CONTAINER_IMAGE=$REPOSITORY_NAME:$(date +%y%m%d)-${GITHUB_SHA}
22 | az acr build -r $REGISTRY_NAME -t $CONTAINER_IMAGE --file Dockerfile .
23 | # create container instance
24 | REGISTRY_PASSWORD=$(az acr credential show -n $REGISTRY_NAME | jq -r .passwords[0].value)
25 | CONTAINER_NAME="aci-${REPOSITORY_NAME}-${RANDOM_STR}"
26 | az container create --resource-group $RESOURCE_GROUP --location $LOCATION \
27 | --name $CONTAINER_NAME \
28 | --image "${REGISTRY_NAME}.azurecr.io/${CONTAINER_IMAGE}" \
29 | --registry-login-server "${REGISTRY_NAME}.azurecr.io" \
30 | --registry-username $REGISTRY_NAME \
31 | --registry-password $REGISTRY_PASSWORD \
32 | --cpu 1 \
33 | --memory 1 \
34 | --ports 80 \
35 | --environment-variables LISTEN_PORT=80 \
36 | --dns-name-label ${REPOSITORY_NAME}-${RANDOM_STR}
37 | FQDN=$(az container show -g $RESOURCE_GROUP --name $CONTAINER_NAME | jq -r .ipAddress.fqdn)
38 | echo "http://${FQDN}"
39 |
--------------------------------------------------------------------------------
/.github/workflows/0-azure-storage.yml:
--------------------------------------------------------------------------------
1 | name: 0-azure-storage
2 |
3 | on:
4 | workflow_dispatch:
5 | inputs:
6 | storage_container_url:
7 | description: 'Storage Container URL'
8 | default: 'https://storage_account_name.blob.core.windows.net/container_name/'
9 | required: true
10 | prefix:
11 | description: 'Prefix (no trailing slash)'
12 | default: 'latest'
13 | required: true
14 |
15 | jobs:
16 | azcopy:
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v2
20 | - name: azcopy install
21 | run: |
22 | echo "installing azcopy"
23 | sudo mkdir -p /home/azcopy/
24 | curl -L https://aka.ms/downloadazcopy-v10-linux | tar -zxf - --directory /home/azcopy/
25 | sudo mv $(find /home/azcopy/ -type f -name azcopy) /usr/bin/
26 | - name: azcopy sync
27 | env:
28 | SOURCE_DIRECTORY: .
29 | AZURE_STORAGE_CONTAINER_URL: ${{ github.event.inputs.storage_container_url }}
30 | AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }}
31 | GITHUB_SHA: ${{ github.sha }}
32 | PREFIX: ${{ github.event.inputs.prefix }}
33 | run: |
34 | # login to azcopy with AZURE_CREDENTIALS
35 | export AZCOPY_SPA_CLIENT_SECRET=$(echo "$AZURE_CREDENTIALS" | jq -r .clientSecret)
36 | CLIENT_ID=$(echo "$AZURE_CREDENTIALS" | jq -r .clientId)
37 | azcopy login --service-principal --application-id "${CLIENT_ID}"
38 |
39 | # set prefix
40 | cd $SOURCE_DIRECTORY
41 | [[ -z "${PREFIX:-}" ]] && PREFIX="${GITHUB_REF#refs/heads/}"
42 | [[ -z "${PREFIX:-}" ]] && PREFIX='latest'
43 |
44 | echo $PWD
45 |
46 | azcopy sync . "${AZURE_STORAGE_CONTAINER_URL}${PREFIX}" --delete-destination=true --exclude-path=.git
47 | echo "${AZURE_STORAGE_CONTAINER_URL}${PREFIX}"
48 | azcopy list "${AZURE_STORAGE_CONTAINER_URL}${PREFIX}"
49 |
50 |
--------------------------------------------------------------------------------
/2-functions/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "io/ioutil"
6 | "log"
7 | "net/http"
8 | "net/http/httputil"
9 | "os"
10 | )
11 |
12 | func main() {
13 | if err := run(); err != nil {
14 | log.Fatalf("%s\n", err)
15 | }
16 | }
17 |
18 | func run() error {
19 | http.HandleFunc("/", httpDefault)
20 | http.HandleFunc("/healthz", httpHealth)
21 | http.HandleFunc("/HttpTrigger", httpTrigger)
22 | http.HandleFunc("/TimerTrigger", httpTimerTrigger)
23 | http.HandleFunc("/api/hello", httpHello)
24 | http.HandleFunc("/api/", httpEcho)
25 | http.HandleFunc("/auth/", httpEcho)
26 |
27 | listenAddr := ":80"
28 | if val := os.Getenv("LISTEN_PORT"); val != "" {
29 | listenAddr = ":" + val
30 | }
31 | if val := os.Getenv("FUNCTIONS_HTTPWORKER_PORT"); val != "" {
32 | listenAddr = ":" + val
33 | }
34 | fmt.Printf("Listening on %s\n", listenAddr)
35 | return http.ListenAndServe(listenAddr, httpLog(http.DefaultServeMux))
36 | }
37 |
38 | func httpLog(handler http.Handler) http.Handler {
39 | return http.HandlerFunc(
40 | func(w http.ResponseWriter, r *http.Request) {
41 | log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
42 | handler.ServeHTTP(w, r)
43 | })
44 | }
45 |
46 | func httpDefault(w http.ResponseWriter, r *http.Request) {
47 | http.NotFound(w, r)
48 | }
49 |
50 | func httpHealth(w http.ResponseWriter, r *http.Request) {
51 | serverName := "hello-gopher"
52 | if val := os.Getenv("SERVER_NAME"); val != "" {
53 | serverName = val
54 | }
55 | fmt.Fprintf(w, serverName)
56 | }
57 |
58 | func httpHello(w http.ResponseWriter, r *http.Request) {
59 | fmt.Fprintf(w, "Hello API!\n")
60 | }
61 |
62 | func httpEcho(w http.ResponseWriter, r *http.Request) {
63 | b, err := httputil.DumpRequest(r, true)
64 | if err != nil {
65 | log.Printf("Error: %s\n", b)
66 | return
67 | }
68 | log.Printf("%s\n", b)
69 | fmt.Fprintf(w, "%s", b)
70 | }
71 |
72 | func httpTrigger(w http.ResponseWriter, r *http.Request) {
73 | entity := "Functions"
74 | if val := r.FormValue("name"); val != "" {
75 | entity = val
76 | }
77 | fmt.Fprintf(w, "Hello %s!\n", entity)
78 | }
79 |
80 | func httpTimerTrigger(w http.ResponseWriter, r *http.Request) {
81 | defer r.Body.Close()
82 | b, _ := ioutil.ReadAll(r.Body)
83 | log.Printf("TimerTrigger: %s", b)
84 | w.Header().Set("Content-Type", "application/json")
85 | fmt.Fprintf(w, "{}")
86 | }
87 |
--------------------------------------------------------------------------------
/2-functions/DEPLOY.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | set -euo pipefail
3 |
4 | # variables
5 | RESOURCE_GROUP='201100-gophercon'
6 | LOCATION='eastus'
7 | SUBSCRIPTION_ID=$(az account show | jq -r .id)
8 | SCOPE="/subscriptions/${SUBSCRIPTION_ID}/resourceGroups/${RESOURCE_GROUP}"
9 | # RANDOM_STR='2b7222'
10 | RANDOM_STR=$(echo -n "$SCOPE" | shasum | head -c 6)
11 | STORAGE_NAME="storage${RANDOM_STR}"
12 | FUNCTION_NAME="functions${RANDOM_STR}"
13 | CREATE_IF_EXISTS="false"
14 | # set by actions workflow
15 | # [[ -z "${GITHUB_SHA:-}" ]] && GITHUB_SHA='test'
16 | [[ -z "${GITHUB_SHA:-}" ]] && GITHUB_SHA=$(git rev-parse --short HEAD)
17 |
18 | TMP=$(az storage account list -g $RESOURCE_GROUP | jq '[.[].name | index("'$STORAGE_NAME'")] | length')
19 |
20 | if [[ "$TMP" == "0" || $CREATE_IF_EXISTS == "true" ]]; then
21 | echo "az storage account create..."
22 | az storage account create -g $RESOURCE_GROUP -l $LOCATION -n $STORAGE_NAME \
23 | --kind StorageV2 \
24 | --sku Standard_LRS \
25 | > /dev/null
26 | else
27 | echo "storage exists..."
28 | fi
29 |
30 | TMP=$(az functionapp list -g $RESOURCE_GROUP | jq '[.[].name | index("'$FUNCTION_NAME'")] | length')
31 |
32 | if [[ "$TMP" == "0" || $CREATE_IF_EXISTS == "true" ]]; then
33 | echo "az functionapp create..."
34 | az functionapp create -g $RESOURCE_GROUP -s $STORAGE_NAME -n $FUNCTION_NAME \
35 | --consumption-plan-location $LOCATION \
36 | --os-type Linux \
37 | --runtime custom \
38 | --functions-version 3 \
39 | > /dev/null
40 | else
41 | echo "functionapp exists..."
42 | fi
43 |
44 | echo "az functionapp appsettings (SERVER_NAME)..."
45 | az functionapp config appsettings set -g $RESOURCE_GROUP -n $FUNCTION_NAME --settings \
46 | "SERVER_NAME=hello-gopher-${GITHUB_SHA}" \
47 | > /dev/null
48 |
49 | # echo "build binary (docker)"
50 | # docker run --rm -v ${PWD}:/pwd/ -w /pwd/ -i golang:1.15.3 bash BUILD.sh
51 | echo "build binary"
52 | source BUILD.sh
53 |
54 | echo "deploy function..."
55 |
56 | mkdir -p _/
57 | FILE_NAME='_/deploy.zip'
58 | zip -r $FILE_NAME .
59 |
60 | echo "az functionapp deployment..."
61 | az functionapp deployment source config-zip \
62 | -g $RESOURCE_GROUP -n $FUNCTION_NAME \
63 | --src $FILE_NAME
64 |
65 | rm $FILE_NAME
66 |
67 | echo "curl https://${FUNCTION_NAME}.azurewebsites.net/healthz"
68 | curl -s -w '%{stderr}\nresponse_code: %{response_code}\ntime_starttransfer: %{time_starttransfer}\n' "https://${FUNCTION_NAME}.azurewebsites.net/healthz"
69 |
70 |
--------------------------------------------------------------------------------
/1-hugo/hugo.sh:
--------------------------------------------------------------------------------
1 | [[ -z "${SITE_NAME:-}" ]] && SITE_NAME='1-hugo'
2 | [[ -z "${SITE_BRANCH:-}" ]] && SITE_BRANCH='gh-pages-content'
3 | [[ -z "${SITE_TITLE:-}" ]] && SITE_TITLE='Hello, Hugo!'
4 | [[ -z "${SITE_BASEURL:-}" ]] && SITE_BASEURL='/'
5 |
6 | function hugo-install-linux {
7 | echo "installing hugo"
8 | sudo mkdir -p hugo/
9 | VERSION="0.76.5"
10 | curl -L https://github.com/gohugoio/hugo/releases/download/v${VERSION}/hugo_${VERSION}_Linux-64bit.tar.gz | sudo tar -zxf - --directory hugo/
11 | sudo mv hugo/hugo /usr/bin/
12 | sudo rm -rf hugo/
13 | hugo version
14 | }
15 |
16 | function hugo-new-site {
17 | hugo new site ${SITE_NAME} --force
18 | cd ${SITE_NAME}
19 | # git init
20 | git checkout -b ${SITE_BRANCH}
21 | git clone https://github.com/yihui/hugo-xmin.git themes/hugo-xmin/
22 | rm -rf themes/hugo-xmin/.git/
23 | git add themes/hugo-xmin/
24 | git commit -m "hugo: new site and add themes/hugo-xmin/"
25 |
26 | CONFIG=$(cat < config.toml
49 |
50 | hugo new posts/my-first-post.md
51 | git add .
52 | git commit -m "hugo: add config.toml and first post"
53 |
54 | git push -u origin ${SITE_BRANCH}
55 | cd ../
56 | }
57 |
58 | function hugo-github-init {
59 | cd ${SITE_NAME}/
60 | git checkout --orphan gh-pages
61 | git reset --hard
62 | git commit --allow-empty -m "Initializing gh-pages branch"
63 | git push origin gh-pages
64 | git checkout ${SITE_BRANCH}
65 | git worktree add -B gh-pages public origin/gh-pages
66 | cd ../
67 | }
68 |
69 | function hugo-github-deploy {
70 | cd ${SITE_NAME}/
71 | if ! test -d public/
72 | then
73 | echo "public/ does not exist. running git worktree add..."
74 | git fetch origin
75 | git worktree add -B gh-pages public origin/gh-pages
76 | fi
77 |
78 | rm -rf public/*
79 | hugo
80 |
81 | cd public
82 | git add --all
83 | git commit -m "publish"
84 | cd ../
85 | git push origin gh-pages
86 | cd ../
87 | }
88 |
89 | function hugo-reset {
90 | git worktree remove hello-gophercon/public
91 | rm -rf hello-gophercon
92 | }
93 |
94 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Get Go-ing with GitHub Actions
2 | Welcome to "Get Go-ing with GitHub Actions" ([aka.ms/go-actions](https://aka.ms/go-actions)) at GopherCon 2020 (November 9th-13th) by Aaron Wislang ([@as\_w](https://twitter.com/as_w)) and David Justice ([@davidjustice](https://twitter.com/davidjustice)).
3 |
4 | We will have two in-person sessions on:
5 |
6 | - Monday November 9th from 12:00 PM-3:00 PM EST (3 Hours). See:
7 | - Tuesday, November from 10th 6:00 PM - 9:00 PM EST (3 Hours). See:
8 |
9 | We will continue to run our lab asynchronously on the GopherCon Discord () throughout GopherCon via the \#microsoft (sponsors) and #github-actions (speakers) text and voice (workshop instructors) channels. You can also reach out to Aaron (Aaron W#0101) and/or David (devigned#6504), any time.
10 |
11 | You can also join us for our Microsoft AMA @ 12:30 PM-2:00 PM EST daily from Wednesday 11 to Friday 13th (see: https://www.gophercon.com/agenda ), followed by other sessions each day.
12 |
13 | ## Micro-labs
14 | Micro-labs are self-contained learning streams which can be done in any order. The content discussed within
15 | each of the micro-labs is not intended to build upon previous micro-labs.
16 |
17 | ### [Tell your Story with Hugo, GitHub Pages and GitHub Actions Workflows [the-gophers/go-actions]](HUGO-GITHUB-PAGES-ACTIONS.md)
18 | In this micro-lab we will help you get started telling your story to the world by setting up a Hugo site using
19 | GitHub Actions and GitHub Pages.
20 |
21 | ### [Go for Serverless and Containers in the Cloud [the-gophers/go-actions]](GO-SERVERLESS-CONTAINERS-CLOUD.md)
22 | It's time to use GitHub Actions to take your Go to the Cloud with Serverless Functions, Containers and more.
23 |
24 | ### [Building, Testing and Releasing for Multiple Platforms [the-gophers/matrix-builds]](https://github.com/the-gophers/matrix-builds)
25 | Explore the use of matrix builds and Go's ability to easily target multiple arch / platforms. In this example you will learn about resources for verifying your software across multiple platforms and architectures concurrently, how to properly inject version information into Go binaries via a release workflow, and how to create an automated release with artifacts spanning multiple platforms and architectures.
26 |
27 | ### [GitHub Actions as a Function!? [the-gophers/tweet-dispatcher]](https://github.com/the-gophers/tweet-dispatcher)
28 | Did you know you can manually trigger GitHub Action workflows? You can and some folks use this in some really
29 | creative ways. Together, we'll walk through setting one up and doing something kind of silly with it. You
30 | will be encouraged to put your own spin on `workflow_dispatch` actions.
31 |
32 | ### [Building Your Own GitHub Action [the-gophers/go-action]](https://github.com/the-gophers/go-action)
33 | Join us as we build our first GitHub Action using Go. Learn the basics of creating a GitHub Action, and
34 | how you can leverage your existing Go skills to build your own build automation.
35 |
36 | ### [Developing Kubernetes Applications [the-gophers/kind-knative]](https://github.com/the-gophers/kind-knative)
37 | Let's build a containerized application and publish it to K8s. In this micro-lab, we'll use GitHub Actions
38 | to build, test and publish our containerized application to K8s.
39 |
40 | ## Requirements
41 | - A GitHub Account, of any kind, which has not exceeded its monthly GitHub Actions quota.
42 | - An Azure Subscription (e.g. [Free](https://aka.ms/azure-free-account) or [Student](https://aka.ms/azure-student-account), or other), for the Actions that deploy Go Functions, Containers, or Storage, to the cloud.
43 |
44 | ## Code of Conduct
45 |
46 | We follow the GopherCon Code of Conduct which can be found [here](https://www.gophercon.com/page/1475132/code-of-conduct).
47 |
48 |
--------------------------------------------------------------------------------
/GO-SERVERLESS-CONTAINERS-CLOUD.md:
--------------------------------------------------------------------------------
1 | # Go for Serverless and Containers in the Cloud
2 |
3 | ## Getting Started
4 | This is a GitHub template repo, so when you click "Use this template", it will create a new copy of this
5 | template in your org or personal repo of choice. Once you have created a repo from this template, you
6 | should be able to clone and navigate to the root of the repository.
7 |
8 | ### What's in Here?
9 |
10 | ### 0. Azure
11 |
12 | ```shell script
13 | .
14 | ├── .github
15 | │ └── workflows
16 | │ ├── 0-azure.yml
17 | │ ├── 0-azure-empty.yml
18 | │ └── 0-azure-storage.yml
19 | └── 0-azure
20 | ├── README.md
21 | ├── azuredeploy.json
22 | └── DEPLOY.sh
23 | ```
24 |
25 | #### [README.md](./README.md)
26 |
27 | Instructions on how to create an Azure Service Principal and create a GitHub Actions Secret (`AZURE_CREDENTIALS`) that will authenticate your GitHub Actions workflow to the Cloud.
28 |
29 | **This step is a requirement for `2. Serverless Go Functions` and `3. Containers and Serverless Container Instances`**
30 |
31 | #### [.github/workflows/0-azure.yml](./.github/workflows/0-azure.yml)
32 |
33 | Our first Azure Actions workflow, triggered on `push` and `workflow_dispatch` events, that will use the `azure/login@v1` action to authenticate using the `AZURE_CREDENTIALS` GitHub secret, and run `0-azure/DEPLOY.sh` which runs two Azure CLI commands, `az group show` and `az resource list`.
34 |
35 | #### [.github/workflows/0-azure-empty.yml](./.github/workflows/0-azure-empty.yml)
36 |
37 | A manually triggered (`workflow_dispatch`) workflow that will use `az deployment group create` to deploy an empty Azure Resource Manager (ARM) template, `azuredeploy.json`, that will remove all the Azure Resources in our Resource Group.
38 |
39 | #### [.github/workflows/0-azure-storage.yml](./.github/workflows/0-azure-storage.yml)
40 |
41 | A stand-alone action (currently triggered on `workflow_dispatch`) that will install our Go CLI for Azure Storage, `azcopy`, authenticate against Azure using our `Service Principal` from our `AZURE_CREDENTIALS` GitHub Secret, and use the `azcopy sync` command to sync the contents of our local repository to an Azure Storage Container.
42 |
43 | Note: you will need to tweak this workflow according to how you would like to use it.
44 |
45 | ### 2. Serverless Go Functions
46 |
47 | ```shell script
48 | .
49 | ├── .github
50 | │ └── workflows
51 | │ └── 2-functions.yml
52 | └── 2-functions
53 | ├── README.md
54 | ├── ...
55 | ├── BUILD.sh
56 | └── DEPLOY.sh
57 | ├── main.go
58 | ├── host.json
59 | ├── healthz
60 | │ └── function.json
61 | └── TimerTrigger
62 | └── function.json
63 | ```
64 |
65 | #### [README.md](./README.md)
66 |
67 | Links to this README.md.
68 |
69 | #### [.github/workflows/2-functions.yml](./.github/workflows/2-functions.yml)
70 |
71 | Similar to `0-azure.yml`, this workflow runs our `2-functions/DEPLOY.sh`. It is triggered on `push` event, which is filtered to the path `2-functions/**`.
72 |
73 | #### [2-functions/DEPLOY.sh](./2-functions/DEPLOY.sh)
74 |
75 | A bash script that uses the [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/what-is-azure-cli) (`az`) to deploy a Serverless Go Function via [Azure Functions custom handlers (preview)](https://docs.microsoft.com/en-us/azure/azure-functions/functions-custom-handlers), that brings Go support to Functions.
76 |
77 | - Create an [Azure Storage account](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-cli) (`az storage account create`)
78 | - Create an [Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/functions-overview) App with [Azure Functions custom handlers](https://docs.microsoft.com/en-us/azure/azure-functions/functions-custom-handlers) (`az functionapp create`)
79 | - Set an environment variable, `SERVER_NAME`, for our application (`az functionapp config appsettings`) that includes the `GITHUB_SHA` variable in the format `hello-gopher-${GITHUB_SHA}"`
80 | - Build our Go binary via `BUILD.sh`, using the Go version installed by default within GitHub Actions. Note: Commented out is the option to use `docker run` and `BUILD.sh` to build our binary using the `golang:1.15.3` containers.
81 | - `zip` our binary to `deploy.zip` and deploy it to Azure Functions (`az functionapp deployment`)
82 | - Confirm our function is up and running by running `curl` against our `https://${FUNCTION_NAME}.azurewebsites.net/api/healthz`, which outputs the name of the function, including the `GITHUB_SHA` above.
83 |
84 | #### [2-functions/BUILD.sh](./2-functions/BUILD.sh)
85 |
86 | Builds our Go binary as above. Optionally used inside a docker container should we choose to build our Go application inside of Docker.
87 |
88 | #### [2-functions/main.go](./2-functions/main.go)
89 |
90 | The entrypoint for our Go Functions application. A simple HTTP web server.
91 |
92 | #### [2-functions/healthz/function.json](./2-functions/healthz/function.json)
93 |
94 | The function definition for our `/api/healthz` endpoint which is triggered via an `HTTP Trigger`.
95 |
96 | #### [2-functions/TimerTrigger/function.json](./2-functions/TimerTrigger/function.json)
97 |
98 | The function definition for our `TimerTrigger` function, which is triggered via a cron-style `Timer trigger`.
99 |
100 | ### 3. Containers and Serverless Container Instances
101 |
102 | ```shell script
103 | .
104 | ├── .github
105 | │ └── workflows
106 | │ └── 3-containers.yml
107 | └── 3-containers
108 | ├── README.md
109 | ├── Dockerfile
110 | ├── main.go
111 | └── DEPLOY.sh
112 | ```
113 |
114 | #### [README.md](./README.md)
115 |
116 | Links to this README.md
117 |
118 | #### [.github/workflows/3-containers.yml](./.github/workflows/3-containers.yml)
119 |
120 | Similar to `0-azure.yml`, this workflow runs our `3-containers/DEPLOY.sh`. It is triggered on `push` event, which is filtered to the path `3-containers/**`.
121 |
122 | #### [3-containers/DEPLOY.sh](./3-containers/DEPLOY.sh)
123 |
124 | A bash script that uses the Azure CLI (`az`) to:
125 |
126 | - Create an [Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-get-started-portal) (`az acr create`)
127 | - Build our docker container, which builds our Go HTTP application via multi-stage build, inside the registry using [Azure Container Registry Tasks](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-quickstart-task-cli) (`az acr build`)
128 | - Get the credentials for our private Container Registry (`az acr get-credentials`)
129 | - Deploy our container as an [Azure Container Instance](https://docs.microsoft.com/en-us/azure/container-instances/container-instances-quickstart) (`az container create`), listening on Port 80
130 |
131 | #### [3-containers/Dockerfile](./3-containers/Dockerfile)
132 |
133 | A simple Dockerfile that uses `golang:rc-alpine` image to perform a multi-stage build of our `hello-echo` echo server, using a `scratch` as the output image.
134 |
135 | #### [3-containers/main.go](./3-containers/main.go)
136 |
137 | A simple Go echo-server that listens on 3 endpoints, `/`, `/echo` and `/host`, with the ability to override the default port (80) via the `LISTEN_PORT` environment variable.
138 |
139 | The `httpLog` function logs incoming requests to standard error.
140 |
--------------------------------------------------------------------------------
/HUGO-GITHUB-PAGES-ACTIONS.md:
--------------------------------------------------------------------------------
1 | # Tell your Story with Hugo, GitHub Pages and GitHub Actions Workflows
2 |
3 | ## What is Hugo?
4 |
5 | [Hugo](https://gohugo.io/) is one of the most popular static site generators. It is open source, written in Go, and blazingly fast.
6 |
7 | Hugo enables us to write our content in [markdown](https://daringfireball.net/projects/markdown/syntax) in the editor of our choice, create themes using Go's [html/template](https://golang.org/pkg/html/template/) and [text/template](https://golang.org/pkg/text/template/).
8 |
9 | Hugo is often used for blogs, but also works extremely well for everything from a simple landing page, resume, or product site, to complex documentation learning resources or presentations, and has well maintained themes for all of these and more.
10 |
11 | ## What is GitHub Pages?
12 |
13 | [GitHub Pages](https://pages.github.com/) provides free static site hosting for you, your organization, and your projects, hosted directly from your GitHub repository. Just edit, push, and your changes are live.
14 |
15 | By default you will receive a URL in the format `username.github.io` or `organization.github.io`, where you can host a site directly at the root, or host your project at username.github.io/repository. We will be using the project option by default as we experiment. GitHub Pages supports the [Jekyll](https://jekyllrb.com/) static site generator natively, but since it deploys static HTML pages and assets, we can build [Hugo](https://gohugo.io/) static sites with [GitHub Actions](https://github.com/features/actions).
16 |
17 | It is worth mentioning that [Azure Static Web Apps (Preview)](https://docs.microsoft.com/en-ca/azure/static-web-apps/overview) is another feature-rich option that uses GitHub Actions to build and deploy your static web app, and has [native support for Hugo](https://docs.microsoft.com/en-us/azure/static-web-apps/publish-hugo), amongst other popular static site and front-end frameworks, and has support for APIs, routing, authentication, authorization, pre-production environments for Pull Requests, and more. However, our focus today is to explore GitHub Actions via Hugo, as a Go CLI tool, and how we can automate and create a GitHub Actions workflow from scratch, without any local or external dependencies, or needing to leave our GitHub repository.
18 |
19 | ## Introduction
20 |
21 | If you are new to Hugo, you may want to follow these steps locally. You can follow Hugo's [Quick Start](https://gohugo.io/getting-started/quick-start/) to install Hugo, choose a [Hugo Theme](https://themes.gohugo.io/), and test locally. This includes the [hugo server](https://gohugo.io/getting-started/quick-start/) command which lets you preview locally, including [draft, future, or expired](https://gohugo.io/getting-started/usage/#draft-future-and-expired-content) content, and a [LiveReload](https://gohugo.io/getting-started/usage/#livereload) feature that enable you to see your edits in realtime.
22 |
23 | For the purposes of this lab, and for use inside GitHub Actions, we have tweaked and wrapped the Quick Start and [Host on GitHub](https://gohugo.io/hosting-and-deployment/hosting-on-github/) steps into bash functions in [hugo.sh](1-hugo/hugo.sh). We will step through [hugo.sh](1-hugo/hugo.sh), but you can use it locally, or go through the same steps manually, if you prefer.
24 |
25 | We also include our first (optional) GitHub Action (`1-hugo-setup`) that can bootstrap our first site, the `gh-pages` and `gh-pages-content` branches, along with a clean easy to modify theme, [hugo xmin](https://themes.gohugo.io/hugo-xmin/), without any local tools or need to leave GitHub.
26 |
27 | ## Let's setup our first Hugo site with Actions
28 |
29 | 1. Open the [the-gophers/go-actions](https://github.com/the-gophers/go-actions) and click the "Use this template" button.
30 | > [Template repositories](https://github.blog/2019-06-06-generate-new-repositories-with-repository-templates/) are a useful way to make any project project or sample simple to share and be used as a starting point by others. GitHub Actions workflows are included whenever a repo is used as a template repo, or forked.
31 | 1. Select your own account and create a new repository. Leaving the name `go-actions` is a good option.
32 | 1. Check the `Include all branches` box before clicking the `Create repository from template` button.
33 | 1. Click the `Actions` tab on your newly created repo.
34 | 1. Look for the `1-hugo-setup` action on the left hand side. You will see it says "This workflow has a workflow_dispatch event trigger." and you can click `Run workflow`, leave the `Branch: main` by default, and click `Run workflow`. Here you can also supply additional [inputs](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/metadata-syntax-for-github-actions#inputs) to a `workflow_dispatch` action. We've left an example input called `debug` with a default value of `true`.
35 | > Note: the `Branch` option will decide both which version of an Actions workflow, and the branch that is checked out when the action runs. In order for a workflow to appear here it must also be present in the default branch of the repo.
36 | 1. Click on the `Actions` tab again, and you will see the action is now running. Click on the action, and then click the job name, `deploy`, where you will see live logs of the build.
37 | 1. In less than a minute, the `1-hugo-setup` workflow will use the `hugo.sh` script to bootstrap a static site in a new `gh-pages-content` branch inside the `current` repo, including new site, the [hugo xmin](https://themes.gohugo.io/hugo-xmin/) theme which we have chosen for you, configuration via [config.toml](https://gohugo.io/getting-started/configuration/#example-configuration), and a new post under `content/posts` with `draft: true` set in its [front matter](https://gohugo.io/content-management/front-matter/#front-matter-variables).
38 | 1. Your `1-hugo/` folder in the `gh-pages-content` branch of your repository should now look like this:
39 | ```
40 | .
41 | |-- README.md
42 | |-- archetypes
43 | | `-- default.md
44 | |-- config.toml
45 | |-- content
46 | | `-- posts
47 | | `-- my-first-post.md
48 | |-- hugo.sh
49 | |-- themes
50 | `-- hugo-xmin
51 | ```
52 |
53 | So how did all this happen? In this repository we have 2 workflows and 1 shell script for this lab:
54 |
55 | ```shell script
56 | .
57 | ├── .github
58 | │ └── workflows
59 | │ ├── 1-hugo-setup.yml
60 | │ └── 1-hugo-deploy.yml
61 | └── 1-hugo
62 | ├── README.md
63 | └── hugo.sh
64 | ```
65 |
66 | #### [1-hugo.sh](./1-hugo.sh)
67 |
68 | Let's begin with `1-hugo.sh`, which contains a handful of bash functions:
69 |
70 | - `hugo-install-linux` installs Hugo for linux by downloading and extracting the Go binary directly from [gohugoio/hugo's Releases](https://github.com/gohugoio/hugo/releases) page.
71 | - `hugo-new-site` runs `hugo new site` and creates a new site, which is checked into the git repository on the `gh-pages-content` branch. We are creating this by convention so that your site content is separate from the rest of the sample repo. Note, that because this folder already existed, and had `hugo.sh` in it, we had to use the `--force` flag for `hugo new site`. You can delete the `gh-pages-content` branch at any time and re-run the `1-hugo-setup` action to experiment further.
72 | - `hugo-github-init` creates the `gh-pages` branch required to deploy to GitHub actions. Your remote must be called `origin`, as this is hard-coded in `hugo.sh`.
73 | - `hugo-github-deploy` builds your site, and uses the [git worktree](https://gohugo.io/hosting-and-deployment/hosting-on-github/#build-and-deployment) command to ensure the `public/` folder our content is generated to is part of the `gh-pages` branch. We are taking this extra step to keep your site and its markdown-formatted content separate from the HTML files and other static assets that will be generated on every change.
74 | - `hugo-reset` is not used during setup or deployment, but will remove the [git-worktree](https://git-scm.com/docs/git-worktree), that points to the `gh-pages` branch, if needed.
75 |
76 | Now let's publish your first piece of content.
77 |
78 | 8. Find the `gh-pages-content` branch and navigate to the same `1-hugo/` folder where your site is now located. Find `content/posts/` and open `my-first-post.md`. Remove the line that says `draft: true` and commit that change.
79 | 9. Your second GitHub Actions workflow, `1-hugo-deploy`, will kick off immediately. Click the `Actions` tab to have a look at the output.
80 | 10. Click on the `Settings` tab of your GitHub repository, and scroll down to `GitHub Pages`. You should see a message that says: "Your site is published at ". Here you will see other options such as custom domains and to use a branch other than `gh-pages`. GitHub Pages are always public, but they can be generated from a repository that remains private. If for any reason your site hasn't already been generated, select the `gh-pages` branch here, or make any commit to the pre-existing `gh-pages` branch (e.g. add a README.md).
81 |
82 | Congratulations! You've used your first [GitHub Actions workflow](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions) stored in a YAML file under [.github/workflows/1-hugo-setup.yml](.github/workflows/1-hugo-setup.yml) [manually triggered by a `workflow_dispatch` event](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#manual-events). Your second [GitHub Actions workflow](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions) stored in a YAML file under [.github/workflows/1-hugo-deploy.yml](.github/workflows/1-hugo-deploy.yml), was triggered by the [push event](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#example-using-multiple-events-with-activity-types-or-configuration) which is one of 27 webhook events, the cron-like [schedule](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#scheduled-events) event, and 2 manual ([workflow_dispatch](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#workflow_dispatch), [repository_dispatch](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#repository_dispatch)) events that can trigger Actions workflows.
83 |
84 | ## The anatomy of an Actions workflow
85 |
86 | Both `1-hugo-setup` and `1-hugo-deploy` use a single Action called [Checkout V2](https://github.com/marketplace/actions/checkout), which lives inside the public [github.com/actions/checkout](https://github.com/actions/checkout#checkout-v2) repository. The `actions/checkout@v2` Action is included by default with most workflows (if you click `Actions > New workflow > Simple workflow > Setup this workflow`, for example), and checks-out your repository under `$GITHUB_WORKSPACE`, one of a number of [default environment variables](https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#default-environment-variables) that GitHub Actions sets.
87 |
88 | The V2 of this action enables your scripts to run authenticated git commands by persisting an automatic [GITHUB_TOKEN](https://docs.github.com/en/free-pro-team@latest/actions/reference/authentication-in-a-workflow#about-the-github_token-secret) GitHub Actions [Secret](https://docs.github.com/en/free-pro-team@latest/actions/reference/encrypted-secrets) that GitHub creates as as part of every workflow to your local git config.
89 |
90 | GitHub Actions workflows are defined in YAML. Let's look at the syntax for the simplest workflow we have created.
91 |
92 | > Note: Editing YAML by hand can be a hassle for many developers, but the GitHub web interface has its own [YAML workflow editor](https://github.blog/2019-10-01-new-workflow-editor-for-github-actions/) with auto-completion, linting and even help with cron expressions.
93 |
94 | Both workflows begin with the [name](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#name), and the [on](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#on) section where we define the [events](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows) that trigger them. In `1-hugo-setup.yml`, [workflow_dispatch](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows#workflow_dispatch) takes [inputs](https://docs.github.com/en/free-pro-team@latest/actions/creating-actions/metadata-syntax-for-github-actions#inputs) that are passed via the `github.event.inputs` payload on the [github](https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#github-context) context that contains other useful values such as `github.sha` and `github.event.respository.name` which we use to set a `SITE_BASEURL` [environment variable](https://docs.github.com/en/free-pro-team@latest/actions/reference/environment-variables#about-environment-variables) under `env` and later use to set [baseURL in our config.toml](https://gohugo.io/getting-started/configuration/#all-configuration-settings) using [context expression syntax](https://docs.github.com/en/free-pro-team@latest/actions/reference/context-and-expression-syntax-for-github-actions#contexts).
95 |
96 | We define a [job](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobs) named `deploy` with a [step](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idsteps) named `hugo.sh` that [runs-on](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idruns-on) `ubuntu-latest`. The linux `ubuntu-latest` runner defaults to `ubuntu-18.04`, but `ubuntu-16.04` and `ubuntu-20.04` are also options alongside Windows Server 2019 (`windows-latest`/`windows-2019`), macOS Big Sur (`macos-11.0`), macOS Catalina (`macos-latest`/`macos-10.15`), or free [self-hosted](https://docs.github.com/en/free-pro-team@latest/actions/hosting-your-own-runners/about-self-hosted-runners) (`self-hosted`) runners.
97 |
98 | Finally, we use our step's [run](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstepsrun) command (which defaults to bash) to run a multi-line script. In order to commit to our repo, we need to set our `user.name` and `user.email` via `git config`, and we then `source` our `hugo.sh`, before running our bash functions. `source` lets us use the environment variables we've set without exporting them.
99 |
100 | #### [.workflows/github/1-hugo-setup.sh](./.workflows/github/1-hugo-setup.sh)
101 |
102 | ```yaml
103 | name: 1-hugo-setup
104 |
105 | on:
106 | workflow_dispatch:
107 | inputs:
108 | site_title:
109 | description: 'Site Title'
110 | default: 'Hello, GopherCon!'
111 | required: true
112 | jobs:
113 | deploy:
114 | runs-on: ubuntu-latest
115 | steps:
116 | - uses: actions/checkout@v2
117 | - name: hugo.sh
118 | env:
119 | GITHUB_SHA: ${{ github.sha }}
120 | SITE_TITLE: ${{ github.event.inputs.site_title }}
121 | SITE_BASEURL: "/${{ github.event.repository.name }}/"
122 | SITE_NAME: 1-hugo
123 | run: |
124 | git config --global user.name github-actions
125 | git config --global user.email github-actions@github.com
126 | cd /home/
127 | source $GITHUB_WORKSPACE/1-hugo/hugo.sh
128 | hugo-install-linux
129 | cd $GITHUB_WORKSPACE/
130 | hugo-new-site
131 | hugo-github-init
132 | hugo-github-deploy
133 | ```
134 |
135 | Our `1-hugo-deploy` action is very similar to the above, with the exception of the [push](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestbranchestags) trigger which we use to filter on a branch:
136 |
137 | #### [.workflows/github/1-hugo-deploy.sh](./.workflows/github/1-hugo-deploy.sh)
138 |
139 | ```yaml
140 | on:
141 | push:
142 | branches:
143 | - gh-pages-content
144 | ```
145 |
146 |
147 | ## Summary
148 |
149 | In this brief workflow we've explored GitHub Actions workflows through a quite straightforward workflow that installs and runs a Go CLI tool and using bash scripts as part of our workflow.
150 |
151 | Many of the steps we have may have their own, dedicated, Actions in the GitHub Marketplace.
152 |
153 | For example, there are official actions for [Go](https://github.com/marketplace/actions/setup-go-environment) which provide more advanced functionality than running `go build` using the version that is pre-installed on every runner. The community has created many [Hugo](https://github.com/marketplace?type=actions&query=hugo) actions you may find useful.
154 |
155 | It can often be helpful to strike a balance between your existing tools and workflows, particularly those you use locally, and what gets run in the cloud. This is especially true when debugging. The debug loop is much tighter when you can hack on a bash script locally, and then push it to GitHub Actions knowing it will run the same as it does locally.
156 |
157 | Finally, Go CLI tools are ideal for GitHub Actions workflows. Go binaries are very quick to install (Hugo takes single-digit seconds), and while Actions workflows also support both `docker` and `docker-compose` that can be great for complex build environments, Go binaries are often a solid alternative to packaging an environment as a container.
158 |
159 | We will continue to explore similar workflows in our next labs covering Go in Serverless Functions and Containers in the Cloud.
160 |
161 | ## Ideas
162 |
163 | - Hack on your site, configure, and modify your existing theme, or [choose a new one](https://themes.gohugo.io).
164 | - Is it a landing page? A resume? A blog? Documentation for your GitHub project?
165 | - Could you integrate [wjdp/htmltest](https://github.com/wjdp/htmltest) into your workflow to check for broken links? You could do this via shell scripting, or perhaps an existing Action in the Marketplace.
166 | - Consider how you might integrate more dynamic content into your site, or implement more advanced functionality such as publishing posts on a schedule.
167 |
--------------------------------------------------------------------------------