├── 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 | --------------------------------------------------------------------------------