├── template ├── golang-http │ ├── .dockerignore │ ├── .gitignore │ ├── go.mod │ ├── function │ │ ├── go.mod │ │ ├── go.sum │ │ └── handler.go │ ├── template.yml │ ├── go.sum │ ├── Dockerfile │ ├── main.go │ └── modules-cleanup.sh ├── golang-middleware │ ├── .gitignore │ ├── go.mod │ ├── function │ │ ├── go.mod │ │ └── handler.go │ ├── go.work │ ├── template.yml │ ├── Dockerfile │ └── main.go └── golang-middleware-inproc │ ├── .gitignore │ ├── function │ ├── go.mod │ └── handler.go │ ├── go.work │ ├── template.yml │ ├── go.mod │ ├── Dockerfile │ ├── main.go │ └── go.sum ├── .gitignore ├── .DEREK.yml ├── .github ├── workflows │ └── build.yaml ├── PULL_REQUEST_TEMPLATE.md ├── ISSUE_TEMPLATE.md └── ISSUE_TEMPLATE │ └── issue.md ├── LICENSE ├── verify.sh └── README.md /template/golang-http/.dockerignore: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /template/golang-http/.gitignore: -------------------------------------------------------------------------------- 1 | /handler 2 | -------------------------------------------------------------------------------- /template/golang-middleware/.gitignore: -------------------------------------------------------------------------------- 1 | /handler 2 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/.gitignore: -------------------------------------------------------------------------------- 1 | /handler 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | test 3 | test.yml 4 | .DS_STORE 5 | .vscode 6 | -------------------------------------------------------------------------------- /template/golang-middleware/go.mod: -------------------------------------------------------------------------------- 1 | module handler 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /.DEREK.yml: -------------------------------------------------------------------------------- 1 | redirect: https://raw.githubusercontent.com/openfaas/faas/master/.DEREK.yml 2 | -------------------------------------------------------------------------------- /template/golang-middleware/function/go.mod: -------------------------------------------------------------------------------- 1 | module handler/function 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /template/golang-middleware/go.work: -------------------------------------------------------------------------------- 1 | go 1.24.0 2 | 3 | use ( 4 | . 5 | ./function 6 | ) 7 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/function/go.mod: -------------------------------------------------------------------------------- 1 | module handler/function 2 | 3 | go 1.24.0 4 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/go.work: -------------------------------------------------------------------------------- 1 | go 1.25.0 2 | 3 | use ( 4 | . 5 | ./function 6 | ) 7 | -------------------------------------------------------------------------------- /template/golang-http/go.mod: -------------------------------------------------------------------------------- 1 | module handler 2 | 3 | go 1.24.0 4 | 5 | require github.com/openfaas/templates-sdk/go-http v0.0.0-20220408082716-5981c545cb03 6 | -------------------------------------------------------------------------------- /template/golang-http/function/go.mod: -------------------------------------------------------------------------------- 1 | module handler/function 2 | 3 | go 1.24.0 4 | 5 | require github.com/openfaas/templates-sdk/go-http v0.0.0-20220408082716-5981c545cb03 6 | -------------------------------------------------------------------------------- /template/golang-http/template.yml: -------------------------------------------------------------------------------- 1 | language: golang-http 2 | fprocess: ./handler 3 | welcome_message: | 4 | This template is now deprecated, use the golang-middleware 5 | template instead. 6 | 7 | https://docs.openfaas.com/languages/go/ -------------------------------------------------------------------------------- /template/golang-http/go.sum: -------------------------------------------------------------------------------- 1 | github.com/openfaas/templates-sdk/go-http v0.0.0-20220408082716-5981c545cb03 h1:wMIW4ddCuogcuXcFO77BPSMI33s3QTXqLTOHY6mLqFw= 2 | github.com/openfaas/templates-sdk/go-http v0.0.0-20220408082716-5981c545cb03/go.mod h1:2vlqdjIdqUjZphguuCAjoMz6QRPm2O8UT0TaAjd39S8= 3 | -------------------------------------------------------------------------------- /template/golang-http/function/go.sum: -------------------------------------------------------------------------------- 1 | github.com/openfaas/templates-sdk/go-http v0.0.0-20220408082716-5981c545cb03 h1:wMIW4ddCuogcuXcFO77BPSMI33s3QTXqLTOHY6mLqFw= 2 | github.com/openfaas/templates-sdk/go-http v0.0.0-20220408082716-5981c545cb03/go.mod h1:2vlqdjIdqUjZphguuCAjoMz6QRPm2O8UT0TaAjd39S8= 3 | -------------------------------------------------------------------------------- /template/golang-middleware/function/handler.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | func Handle(w http.ResponseWriter, r *http.Request) { 10 | var input []byte 11 | 12 | if r.Body != nil { 13 | defer r.Body.Close() 14 | 15 | body, _ := io.ReadAll(r.Body) 16 | 17 | input = body 18 | } 19 | 20 | w.WriteHeader(http.StatusOK) 21 | w.Write([]byte(fmt.Sprintf("Body: %s", string(input)))) 22 | } 23 | -------------------------------------------------------------------------------- /template/golang-http/function/handler.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | handler "github.com/openfaas/templates-sdk/go-http" 8 | ) 9 | 10 | // Handle a function invocation 11 | func Handle(req handler.Request) (handler.Response, error) { 12 | var err error 13 | 14 | message := fmt.Sprintf("Body: %s", string(req.Body)) 15 | 16 | return handler.Response{ 17 | Body: []byte(message), 18 | StatusCode: http.StatusOK, 19 | }, err 20 | } 21 | -------------------------------------------------------------------------------- /template/golang-middleware/template.yml: -------------------------------------------------------------------------------- 1 | language: golang-middleware 2 | fprocess: ./handler 3 | welcome_message: | 4 | You have created a new function which uses Go 1.25 and Alpine 5 | Linux as its base image. The go.mod version is set to 1.24.0 6 | for compatibility. 7 | 8 | To disable the go module, for private vendor code, please use 9 | "--build-arg GO111MODULE=off" with faas-cli build or configure this 10 | via your stack.yml file. 11 | 12 | Learn more: https://docs.openfaas.com/languages/go/ 13 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | types: 9 | - opened 10 | - synchronize 11 | - reopened 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v4 18 | with: 19 | fetch-depth: 1 20 | - name: Setup faas-cli 21 | run: curl -sSL https://cli.openfaas.com | sh 22 | - name: Verify all templates 23 | run: bash verify.sh 24 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | 4 | ## How Has This Been Tested? 5 | 6 | 7 | 8 | 9 | 10 | ## How are existing users impacted? What migration steps/scripts do we need? 11 | 12 | 13 | ## Checklist: 14 | 15 | I have: 16 | 17 | - [ ] updated the documentation and/or roadmap (if required) 18 | - [ ] read the [CONTRIBUTION](https://github.com/openfaas/faas/blob/master/CONTRIBUTING.md) guide 19 | - [ ] signed-off my commits with `git commit -s` 20 | - [ ] added unit tests 21 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/function/handler.go: -------------------------------------------------------------------------------- 1 | package function 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log/slog" 7 | "os" 8 | 9 | "net/http" 10 | "time" 11 | ) 12 | 13 | func Handle(w http.ResponseWriter, r *http.Request) { 14 | var input []byte 15 | 16 | log := slog.New(slog.NewTextHandler(os.Stdout, nil)). 17 | With("X-Call-Id", r.Header.Get("X-Call-Id")) 18 | 19 | log.Info("received request") 20 | 21 | if r.Body != nil { 22 | defer r.Body.Close() 23 | 24 | body, _ := io.ReadAll(r.Body) 25 | 26 | input = body 27 | } 28 | 29 | log.Info("Sleeping for 1 milliseconds") 30 | 31 | time.Sleep(time.Millisecond * 1) 32 | 33 | log.Info("Sleep done") 34 | 35 | w.WriteHeader(http.StatusOK) 36 | w.Write([]byte(fmt.Sprintf("Body: %s", string(input)))) 37 | } 38 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/template.yml: -------------------------------------------------------------------------------- 1 | language: golang-middleware-inproc 2 | fprocess: ./handler 3 | welcome_message: | 4 | This function consumes the watchdog via its Go SDK rather than 5 | running two separate processes. It is for testing and will 6 | likely be merged into the normal "golang-middleware" template 7 | in the future and be deleted itself. Pin to a specific 8 | SHA if you do want use this template. 9 | 10 | You have created a new function which uses Go 1.25 and Alpine 11 | Linux as its base image. The go.mod version is set to 1.24.0 12 | for compatibility. 13 | 14 | To disable the go module, for private vendor code, please use 15 | "--build-arg GO111MODULE=off" with faas-cli build or configure this 16 | via your stack.yml file. 17 | 18 | Learn more: https://docs.openfaas.com/languages/go/ 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 OpenFaaS Ltd 4 | Copyright (c) 2017 Alex Ellis 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/go.mod: -------------------------------------------------------------------------------- 1 | module handler 2 | 3 | go 1.24.0 4 | 5 | require ( 6 | github.com/google/uuid v1.6.0 7 | github.com/openfaas/of-watchdog v0.0.0-20251114100857-67a6309db21b 8 | ) 9 | 10 | require ( 11 | github.com/beorn7/perks v1.0.1 // indirect 12 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 13 | github.com/docker/go-units v0.5.0 // indirect 14 | github.com/golang-jwt/jwt/v5 v5.3.0 // indirect 15 | github.com/kylelemons/godebug v1.1.0 // indirect 16 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect 17 | github.com/openfaas/faas-middleware v1.2.5 // indirect 18 | github.com/openfaas/faas-provider v0.25.8 // indirect 19 | github.com/prometheus/client_golang v1.23.0 // indirect 20 | github.com/prometheus/client_model v0.6.2 // indirect 21 | github.com/prometheus/common v0.65.0 // indirect 22 | github.com/prometheus/procfs v0.17.0 // indirect 23 | github.com/rakutentech/jwk-go v1.2.0 // indirect 24 | golang.org/x/crypto v0.41.0 // indirect 25 | golang.org/x/sys v0.35.0 // indirect 26 | google.golang.org/protobuf v1.36.7 // indirect 27 | ) 28 | 29 | // replace github.com/openfaas/of-watchdog => ../../../of-watchdog 30 | -------------------------------------------------------------------------------- /verify.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | CLI="faas-cli" 5 | 6 | build_template() { 7 | template=$1 8 | 9 | rm -rf stack.yaml || : 10 | echo Building $template 11 | func_name=$template-ci 12 | $CLI new $func_name --lang $template 2>/dev/null 1>&2 13 | $CLI build 14 | } 15 | 16 | verify_and_clean() { 17 | image=$1 18 | tag_name=latest 19 | 20 | echo Verifying $template 21 | container=$(docker run -d -p 8080:8080 $func_name:$tag_name) 22 | sleep 5 # wait for slower templates to start 23 | output=$(curl -s -d "testing" http://127.0.0.1:8080) 24 | 25 | echo $image output: $output 26 | success=false 27 | if [ ! -z "$output" ]; then # output was not empty = good template 28 | success=true 29 | fi 30 | 31 | echo Cleaning $image 32 | docker rm $container -f 2>/dev/null 1>&2 33 | docker rmi $func_name:$tag_name 2>/dev/null 1>&2 34 | 35 | if [ "$success" = false ]; then 36 | echo $image template failed validation 37 | exit 1 38 | else 39 | echo $image template validation successful 40 | fi 41 | } 42 | 43 | if ! [ -x "$(command -v faas-cli)" ]; then 44 | HERE=$(pwd) 45 | cd /tmp/ 46 | curl -sSL https://cli.openfaas.com | sh 47 | CLI="/tmp/faas-cli" 48 | 49 | cd $HERE 50 | fi 51 | 52 | cli_version=$($CLI version --short-version) 53 | 54 | echo Validating templates with faas-cli $cli_version 55 | 56 | cd ./template 57 | 58 | # verify each of the templates 59 | for dir in ./*/; do 60 | dirname=${dir%*/} 61 | template=${dirname##*/} 62 | 63 | pushd ../ 2>/dev/null 1>&2 64 | 65 | build_template $template 66 | verify_and_clean $template 67 | rm -rf stack.yaml *-ci 68 | 69 | popd 2>/dev/null 1>&2 70 | done 71 | 72 | # remove the generated files and folders if successful 73 | cd ../ 74 | rm -rf *-ci stack.yaml 75 | 76 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.25-alpine AS build 2 | 3 | ARG TARGETPLATFORM 4 | ARG BUILDPLATFORM 5 | ARG TARGETOS 6 | ARG TARGETARCH 7 | 8 | RUN apk --no-cache add git 9 | 10 | RUN mkdir -p /go/src/handler 11 | WORKDIR /go/src/handler 12 | COPY . . 13 | 14 | ARG GO111MODULE="on" 15 | ARG GOPROXY="" 16 | ARG GOFLAGS="" 17 | ARG CGO_ENABLED=0 18 | ENV CGO_ENABLED=${CGO_ENABLED} 19 | 20 | # Run a gofmt and exclude all vendored code. 21 | RUN test -z "$(gofmt -l $(find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./function/vendor/*"))" || { echo "Run \"gofmt -s -w\" on your Golang code"; exit 1; } 22 | 23 | WORKDIR /go/src/handler/function 24 | RUN mkdir -p /go/src/handler/function/static 25 | 26 | RUN --mount=type=cache,target=/root/.cache/go-build \ 27 | --mount=type=cache,target=/go/pkg/mod \ 28 | GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test ./... -cover 29 | 30 | WORKDIR /go/src/handler 31 | RUN --mount=type=cache,target=/root/.cache/go-build \ 32 | --mount=type=cache,target=/go/pkg/mod \ 33 | GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ 34 | go build --ldflags "-s -w" -o handler . 35 | 36 | FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.22.1 AS ship 37 | 38 | # Add non root user and certs 39 | RUN apk --no-cache add ca-certificates \ 40 | && addgroup -S app && adduser -S -g app app 41 | 42 | # Split instructions so that buildkit can run & cache 43 | # the previous command ahead of time. 44 | RUN mkdir -p /home/app \ 45 | && chown app /home/app 46 | 47 | WORKDIR /home/app 48 | 49 | COPY --from=build --chown=app /go/src/handler/handler . 50 | COPY --from=build --chown=app /go/src/handler/function/static static 51 | 52 | USER app 53 | 54 | ENV fprocess="" 55 | ENV mode="inproc" 56 | ENV upstream_url="" 57 | ENV prefix_logs="false" 58 | 59 | CMD ["./handler"] 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## My actions before raising this issue 4 | - [ ] Followed the [troubleshooting guide](https://docs.openfaas.com/deployment/troubleshooting/) 5 | - [ ] Read/searched [the docs](https://docs.openfaas.com/) 6 | - [ ] Searched [past issues](/issues) 7 | 8 | ## Why do you need this? 9 | 10 | 11 | 12 | ## Expected Behaviour 13 | 14 | 15 | 16 | 17 | ## Current Behaviour 18 | 19 | 20 | 21 | 22 | ## List All Possible Solutions and Workarounds 23 | 24 | 25 | 26 | 27 | ## Steps to Reproduce (for bugs) 28 | 29 | 30 | 1. 31 | 2. 32 | 3. 33 | 4. 34 | 35 | 36 | ## Your Environment 37 | 38 | * FaaS-CLI version ( Full output from: `faas-cli version` ): 39 | 40 | * Docker version `docker version` (e.g. Docker 17.0.05 ): 41 | 42 | * Are you using Docker Swarm or Kubernetes (FaaS-netes)? 43 | 44 | * Operating System and version (e.g. Linux, Windows, MacOS): 45 | 46 | * Code example or link to GitHub repo or gist to reproduce problem: 47 | 48 | * Other diagnostic information / logs from [troubleshooting guide](https://docs.openfaas.com/deployment/troubleshooting) 49 | 50 | ## Next steps 51 | 52 | You may [join Slack](https://docs.openfaas.com/community) for community support. 53 | -------------------------------------------------------------------------------- /template/golang-middleware/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${TARGETPLATFORM:-linux/amd64} ghcr.io/openfaas/of-watchdog:0.10.12 AS watchdog 2 | FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.25-alpine AS build 3 | 4 | # Multi-arch build args 5 | ARG TARGETPLATFORM 6 | ARG BUILDPLATFORM 7 | ARG TARGETOS 8 | ARG TARGETARCH 9 | 10 | RUN apk --no-cache add git 11 | 12 | COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog 13 | RUN chmod +x /usr/bin/fwatchdog 14 | 15 | RUN mkdir -p /go/src/handler 16 | WORKDIR /go/src/handler 17 | COPY . . 18 | 19 | ARG GO111MODULE="on" 20 | ARG GOPROXY="" 21 | ARG GOFLAGS="" 22 | ARG CGO_ENABLED=0 23 | ENV CGO_ENABLED=${CGO_ENABLED} 24 | 25 | # Run a gofmt and exclude all vendored code. 26 | RUN test -z "$(gofmt -l $(find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./function/vendor/*"))" || { echo "Run \"gofmt -s -w\" on your Golang code"; exit 1; } 27 | 28 | WORKDIR /go/src/handler/function 29 | RUN mkdir -p /go/src/handler/function/static 30 | 31 | RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test ./... -cover 32 | 33 | WORKDIR /go/src/handler 34 | RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ 35 | go build --ldflags "-s -w" -o handler . 36 | 37 | FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.22.2 AS ship 38 | 39 | # Add non root user and certs 40 | RUN apk --no-cache add ca-certificates \ 41 | && addgroup -S app && adduser -S -g app app 42 | 43 | # Split instructions so that buildkit can run & cache 44 | # the previous command ahead of time. 45 | RUN mkdir -p /home/app \ 46 | && chown app /home/app 47 | 48 | WORKDIR /home/app 49 | 50 | COPY --from=build --chown=app /go/src/handler/handler . 51 | COPY --from=build --chown=app /usr/bin/fwatchdog . 52 | COPY --from=build --chown=app /go/src/handler/function/static static 53 | 54 | USER app 55 | 56 | ENV fprocess="./handler" 57 | ENV mode="http" 58 | ENV upstream_url="http://127.0.0.1:8082" 59 | ENV prefix_logs="false" 60 | 61 | CMD ["./fwatchdog"] 62 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue.md: -------------------------------------------------------------------------------- 1 | ## Due diligence 2 | 3 | 4 | ## My actions before raising this issue 5 | Before you ask for help or support, make sure that you've [consulted the manual for faasd](https://openfaas.gumroad.com/l/serverless-for-everyone-else). We can't answer questions that are already covered by the manual. 6 | 7 | 8 | 9 | ## Why do you need this? 10 | 11 | 12 | ## Who is this for? 13 | 14 | What company is this for? Are you listed in the [ADOPTERS.md](https://github.com/openfaas/faas/blob/master/ADOPTERS.md) file? 15 | 16 | 17 | ## Expected Behaviour 18 | 19 | 20 | 21 | 22 | ## Current Behaviour 23 | 24 | 25 | 26 | 27 | ## List All Possible Solutions and Workarounds 28 | 29 | 30 | 31 | 32 | ## Which Solution Do You Recommend? 33 | 34 | 35 | 36 | ## Steps to Reproduce (for bugs) 37 | 38 | 39 | 1. 40 | 2. 41 | 3. 42 | 4. 43 | 44 | ## Your Environment 45 | 46 | * OS and architecture: 47 | 48 | * Versions: 49 | 50 | ```sh 51 | go version 52 | 53 | containerd -version 54 | 55 | uname -a 56 | 57 | cat /etc/os-release 58 | 59 | faasd version 60 | ``` 61 | -------------------------------------------------------------------------------- /template/golang-http/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM --platform=${TARGETPLATFORM:-linux/amd64} ghcr.io/openfaas/of-watchdog:0.10.12 AS watchdog 2 | FROM --platform=${BUILDPLATFORM:-linux/amd64} golang:1.25-alpine AS build 3 | 4 | ARG TARGETPLATFORM 5 | ARG BUILDPLATFORM 6 | ARG TARGETOS 7 | ARG TARGETARCH 8 | 9 | RUN apk --no-cache add git 10 | 11 | COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog 12 | RUN chmod +x /usr/bin/fwatchdog 13 | 14 | RUN mkdir -p /go/src/handler 15 | WORKDIR /go/src/handler 16 | COPY . . 17 | 18 | ARG GO111MODULE="on" 19 | ARG GOPROXY="" 20 | ARG GOFLAGS="" 21 | ARG CGO_ENABLED=0 22 | ENV CGO_ENABLED=${CGO_ENABLED} 23 | 24 | # Run a gofmt and exclude all vendored code. 25 | RUN test -z "$(gofmt -l $(find . -type f -name '*.go' -not -path "./vendor/*" -not -path "./function/vendor/*"))" || { echo "Run \"gofmt -s -w\" on your Golang code"; exit 1; } 26 | 27 | RUN sh ./modules-cleanup.sh 28 | 29 | WORKDIR /go/src/handler/function 30 | 31 | RUN mkdir -p /go/src/handler/function/static 32 | 33 | RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOFLAGS=${GOFLAGS} go test ./... -cover 34 | 35 | WORKDIR /go/src/handler 36 | RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} GOFLAGS=${GOFLAGS} \ 37 | go build --ldflags "-s -w" -o handler . 38 | 39 | FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.22.2 AS ship 40 | # Add non root user and certs 41 | 42 | RUN apk --no-cache add ca-certificates \ 43 | && addgroup -S app && adduser -S -g app app 44 | 45 | # Split instructions so that buildkit can run & cache 46 | # the previous command ahead of time. 47 | RUN mkdir -p /home/app \ 48 | && chown app /home/app 49 | 50 | WORKDIR /home/app 51 | 52 | COPY --from=build --chown=app /go/src/handler/handler . 53 | COPY --from=build --chown=app /usr/bin/fwatchdog . 54 | COPY --from=build --chown=app /go/src/handler/function/static static 55 | 56 | USER app 57 | 58 | ENV fprocess="./handler" 59 | ENV mode="http" 60 | ENV upstream_url="http://127.0.0.1:8082" 61 | ENV prefix_logs="false" 62 | 63 | CMD ["./fwatchdog"] 64 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "handler/function" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "syscall" 11 | "time" 12 | 13 | "github.com/google/uuid" 14 | "github.com/openfaas/of-watchdog/config" 15 | 16 | "github.com/openfaas/of-watchdog/pkg" 17 | ) 18 | 19 | const defaultTimeout = 10 * time.Second 20 | 21 | func main() { 22 | 23 | os.Setenv("mode", "inproc") 24 | 25 | if _, ok := os.LookupEnv("exec_timeout"); !ok { 26 | os.Setenv("exec_timeout", defaultTimeout.String()) 27 | } 28 | 29 | extendedTimeout := time.Duration(defaultTimeout + time.Millisecond*100) 30 | 31 | if _, ok := os.LookupEnv("read_timeout"); !ok { 32 | os.Setenv("read_timeout", extendedTimeout.String()) 33 | } 34 | if _, ok := os.LookupEnv("write_timeout"); !ok { 35 | os.Setenv("write_timeout", extendedTimeout.String()) 36 | } 37 | 38 | if v, ok := os.LookupEnv("healthcheck_interval"); !ok { 39 | os.Setenv("healthcheck_interval", os.Getenv("write_timeout")) 40 | } else { 41 | interval, _ := time.ParseDuration(v) 42 | if interval <= time.Millisecond*0 { 43 | os.Setenv("healthcheck_interval", "1ms") 44 | } 45 | } 46 | 47 | cfg, err := config.New(os.Environ()) 48 | if err != nil { 49 | log.Fatalf("failed to parse watchdog config: %v", err) 50 | } 51 | 52 | log.Printf("Watchdog config: %+v\n", cfg) 53 | 54 | h := func(w http.ResponseWriter, r *http.Request) { 55 | if _, ok := r.Header["X-Call-Id"]; !ok { 56 | r.Header.Set("X-Call-Id", uuid.New().String()) 57 | } 58 | function.Handle(w, r) 59 | } 60 | 61 | cfg.SetHandler(h) 62 | cfg.OperationalMode = config.ModeInproc 63 | 64 | watchdog := pkg.NewWatchdog(cfg) 65 | 66 | sigs := make(chan os.Signal, 1) 67 | signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) 68 | ctx, cancel := context.WithCancel(context.Background()) 69 | defer cancel() 70 | 71 | go func() { 72 | s := <-sigs 73 | log.Printf("Signal received: %s", s.String()) 74 | cancel() 75 | }() 76 | 77 | if err := watchdog.Start(ctx); err != nil { 78 | log.Fatalf("failed to start watchdog: %v", err) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /template/golang-middleware/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "log" 7 | "net/http" 8 | "os" 9 | "os/signal" 10 | "strconv" 11 | "sync/atomic" 12 | "syscall" 13 | "time" 14 | 15 | "handler/function" 16 | ) 17 | 18 | var ( 19 | acceptingConnections int32 20 | ) 21 | 22 | const defaultTimeout = 10 * time.Second 23 | 24 | func main() { 25 | readTimeout := parseIntOrDurationValue(os.Getenv("read_timeout"), defaultTimeout) 26 | writeTimeout := parseIntOrDurationValue(os.Getenv("write_timeout"), defaultTimeout) 27 | healthInterval := parseIntOrDurationValue(os.Getenv("healthcheck_interval"), writeTimeout) 28 | 29 | s := &http.Server{ 30 | Addr: fmt.Sprintf(":%d", 8082), 31 | ReadTimeout: readTimeout, 32 | WriteTimeout: writeTimeout, 33 | MaxHeaderBytes: 1 << 20, // Max header of 1MB 34 | } 35 | 36 | http.HandleFunc("/", function.Handle) 37 | 38 | listenUntilShutdown(s, healthInterval, writeTimeout) 39 | } 40 | 41 | func listenUntilShutdown(s *http.Server, shutdownTimeout time.Duration, writeTimeout time.Duration) { 42 | idleConnsClosed := make(chan struct{}) 43 | go func() { 44 | sig := make(chan os.Signal, 1) 45 | signal.Notify(sig, syscall.SIGTERM) 46 | 47 | <-sig 48 | 49 | log.Printf("[entrypoint] SIGTERM: no connections in: %s", shutdownTimeout.String()) 50 | <-time.Tick(shutdownTimeout) 51 | 52 | ctx, cancel := context.WithTimeout(context.Background(), writeTimeout) 53 | defer cancel() 54 | 55 | if err := s.Shutdown(ctx); err != nil { 56 | log.Printf("[entrypoint] Error in Shutdown: %v", err) 57 | } 58 | 59 | log.Printf("[entrypoint] Exiting.") 60 | 61 | close(idleConnsClosed) 62 | }() 63 | 64 | // Run the HTTP server in a separate go-routine. 65 | go func() { 66 | if err := s.ListenAndServe(); err != http.ErrServerClosed { 67 | log.Printf("[entrypoint] Error ListenAndServe: %v", err) 68 | close(idleConnsClosed) 69 | } 70 | }() 71 | 72 | atomic.StoreInt32(&acceptingConnections, 1) 73 | 74 | <-idleConnsClosed 75 | } 76 | 77 | func parseIntOrDurationValue(val string, fallback time.Duration) time.Duration { 78 | if len(val) > 0 { 79 | parsedVal, parseErr := strconv.Atoi(val) 80 | if parseErr == nil && parsedVal >= 0 { 81 | return time.Duration(parsedVal) * time.Second 82 | } 83 | } 84 | 85 | duration, durationErr := time.ParseDuration(val) 86 | if durationErr != nil { 87 | return fallback 88 | } 89 | return duration 90 | } 91 | -------------------------------------------------------------------------------- /template/golang-http/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "io" 7 | "log" 8 | "net/http" 9 | "os" 10 | "os/signal" 11 | "strconv" 12 | "sync/atomic" 13 | "syscall" 14 | "time" 15 | 16 | "handler/function" 17 | 18 | handler "github.com/openfaas/templates-sdk/go-http" 19 | ) 20 | 21 | var ( 22 | acceptingConnections int32 23 | ) 24 | 25 | const defaultTimeout = 10 * time.Second 26 | 27 | func main() { 28 | readTimeout := parseIntOrDurationValue(os.Getenv("read_timeout"), defaultTimeout) 29 | writeTimeout := parseIntOrDurationValue(os.Getenv("write_timeout"), defaultTimeout) 30 | healthInterval := parseIntOrDurationValue(os.Getenv("healthcheck_interval"), writeTimeout) 31 | 32 | s := &http.Server{ 33 | Addr: fmt.Sprintf(":%d", 8082), 34 | ReadTimeout: readTimeout, 35 | WriteTimeout: writeTimeout, 36 | MaxHeaderBytes: 1 << 20, // Max header of 1MB 37 | } 38 | 39 | http.HandleFunc("/", makeRequestHandler()) 40 | listenUntilShutdown(s, healthInterval, writeTimeout) 41 | } 42 | 43 | func listenUntilShutdown(s *http.Server, shutdownTimeout time.Duration, writeTimeout time.Duration) { 44 | idleConnsClosed := make(chan struct{}) 45 | go func() { 46 | sig := make(chan os.Signal, 1) 47 | signal.Notify(sig, syscall.SIGTERM) 48 | 49 | <-sig 50 | 51 | log.Printf("[entrypoint] SIGTERM: no connections in: %s", shutdownTimeout.String()) 52 | <-time.Tick(shutdownTimeout) 53 | 54 | ctx, cancel := context.WithTimeout(context.Background(), writeTimeout) 55 | defer cancel() 56 | 57 | if err := s.Shutdown(ctx); err != nil { 58 | log.Printf("[entrypoint] Error in Shutdown: %v", err) 59 | } 60 | 61 | log.Printf("[entrypoint] Exiting.") 62 | 63 | close(idleConnsClosed) 64 | }() 65 | 66 | // Run the HTTP server in a separate go-routine. 67 | go func() { 68 | if err := s.ListenAndServe(); err != http.ErrServerClosed { 69 | log.Printf("[entrypoint] Error ListenAndServe: %v", err) 70 | close(idleConnsClosed) 71 | } 72 | }() 73 | 74 | atomic.StoreInt32(&acceptingConnections, 1) 75 | 76 | <-idleConnsClosed 77 | } 78 | 79 | func makeRequestHandler() func(http.ResponseWriter, *http.Request) { 80 | return func(w http.ResponseWriter, r *http.Request) { 81 | var input []byte 82 | 83 | if r.Body != nil { 84 | defer r.Body.Close() 85 | 86 | bodyBytes, bodyErr := io.ReadAll(r.Body) 87 | 88 | if bodyErr != nil { 89 | log.Printf("Error reading body from request.") 90 | } 91 | 92 | input = bodyBytes 93 | } 94 | 95 | req := handler.Request{ 96 | Body: input, 97 | Header: r.Header, 98 | Method: r.Method, 99 | QueryString: r.URL.RawQuery, 100 | } 101 | req.WithContext(r.Context()) 102 | 103 | result, resultErr := function.Handle(req) 104 | 105 | if result.Header != nil { 106 | for k, v := range result.Header { 107 | w.Header()[k] = v 108 | } 109 | } 110 | 111 | if resultErr != nil { 112 | log.Print(resultErr) 113 | w.WriteHeader(http.StatusInternalServerError) 114 | } else { 115 | if result.StatusCode == 0 { 116 | w.WriteHeader(http.StatusOK) 117 | } else { 118 | w.WriteHeader(result.StatusCode) 119 | } 120 | } 121 | 122 | w.Write(result.Body) 123 | } 124 | } 125 | 126 | func parseIntOrDurationValue(val string, fallback time.Duration) time.Duration { 127 | if len(val) > 0 { 128 | parsedVal, parseErr := strconv.Atoi(val) 129 | if parseErr == nil && parsedVal >= 0 { 130 | return time.Duration(parsedVal) * time.Second 131 | } 132 | } 133 | 134 | duration, durationErr := time.ParseDuration(val) 135 | if durationErr != nil { 136 | return fallback 137 | } 138 | return duration 139 | } 140 | -------------------------------------------------------------------------------- /template/golang-http/modules-cleanup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | set -e 4 | 5 | GO111MODULE=$(go env GO111MODULE) 6 | 7 | [ -z "$DEBUG" ] && DEBUG=0 8 | 9 | # move_vendor will copy the function's vendor folder, 10 | # if it exists. 11 | move_vendor() { 12 | if [ ! -d ./function/vendor ]; then 13 | echo "vendor not found" 14 | return 15 | fi 16 | 17 | echo "moving function vendor" 18 | mv -f ./function/vendor . 19 | } 20 | 21 | 22 | # cleanup_gomod will move the function's go module 23 | cleanup_gomod() { 24 | 25 | # Nothing to do when modules is explicitly off 26 | # the z prefix protects against any SH wonkiness 27 | # see https://stackoverflow.com/a/18264223 28 | if [ "z$GO111MODULE" = "zoff" ]; then 29 | echo "modules disabled, skipping go.mod cleanup" 30 | return; 31 | fi 32 | 33 | if [ ! -f ./function/go.mod ]; then 34 | echo "module not initialized, skipping go.mod cleanup" 35 | return; 36 | fi 37 | 38 | echo "cleaning up go.mod" 39 | 40 | # Copy the user's go.mod 41 | mv -f ./function/go.mod . 42 | mv -f ./function/go.sum . 43 | 44 | # Clean up the go.mod 45 | 46 | # Cleanup any sub-module replacements. 47 | # This requires modifying any replace that points to "./*", 48 | # the user has will use this to reference sub-modules instead 49 | # of sub-packages, which we cleanup below. 50 | echo "cleanup local replace statements" 51 | # 1. Replace references to the local folder with `./function` 52 | sed -i 's/=> \.\//=> \.\/function\//' go.mod 53 | 54 | 55 | # Remove any references to the handler/function module. 56 | # It is ok to just remove it because we will replace it later. 57 | # 58 | # Note that these references may or may not exist. We expect the 59 | # go.mod to have a replace statement _if_ developer has subpackages 60 | # in their handler. In this case they will need a this replace statement 61 | # 62 | # replace handler/function => ./ 63 | # 64 | # `go mod` will then add a line that looks like 65 | # 66 | # handler/function v0.0.0-00010101000000-000000000000 67 | # 68 | # both of these lines need to be replaced, this grep selects everything 69 | # _except_ those offending lines. 70 | grep -v "\shandler/function" go.mod > gomod2; mv gomod2 go.mod 71 | 72 | # Now update the go.mod 73 | # 74 | # 1. use replace so that imports of handler/function use the local code 75 | # 2. we need to rename the module to handler because our main.go assumes 76 | # this is the package name 77 | go mod edit \ 78 | -replace=handler/function=./function \ 79 | -module handler 80 | 81 | 82 | 83 | if [ "$DEBUG" -eq 1 ]; then 84 | cat go.mod 85 | echo "" 86 | fi 87 | } 88 | 89 | 90 | # cleanup_vendor_modulestxt will cleanup the modules.txt file in the vendor folder 91 | # this file is needed when modules are enabled and it must be in sync with the 92 | # go.mod. To function correctly we need to modify the references to handler/function, 93 | # if they exist. 94 | cleanup_vendor_modulestxt() { 95 | if [ ! -d ./vendor ]; then 96 | echo "no vendor found, skipping modules.txt cleanup" 97 | return 98 | fi 99 | 100 | # Nothing to do when modules is explicitly off 101 | # the z prefix protects against any SH wonkiness 102 | # see https://stackoverflow.com/a/18264223 103 | if [ "z$GO111MODULE" = "zoff" ]; then 104 | echo "modules disabled, skipping modules.txt cleanup" 105 | return; 106 | fi 107 | 108 | echo "cleanup vendor/modules.txt" 109 | 110 | # just in case 111 | touch "./vendor/modules.txt" 112 | 113 | # when vendored, we need to do similar edits to the vendor/modules.txt 114 | # as we did to the go.mod 115 | 116 | # 1. we need to replace any possible copy of the handler code 117 | rm -rf vendor/handler && \ 118 | 119 | # 2. in modules.txt, we remove existing references to the handler/function 120 | # we reconstruct these in the last step 121 | grep -v "\shandler/function" ./vendor/modules.txt> modulestext; mv modulestext ./vendor/modules.txt 122 | 123 | # 3. Handle any other local replacements. 124 | # any replace that points to `./**` needs to be udpat echo "cleanup local replace statements" 125 | sed -i 's/=> \.\//=> \.\/function\//' ./vendor/modules.txt 126 | 127 | # 4. To make the modules.txt consistent with the new go.mod, 128 | # we add the mising replace to the vendor/modules.txt 129 | echo "## explicit" >> ./vendor/modules.txt 130 | echo "# handler/function => ./function" >> ./vendor/modules.txt 131 | 132 | if [ "$DEBUG" -eq 1 ]; then 133 | cat ./vendor/modules.txt; 134 | echo "" 135 | fi 136 | } 137 | 138 | # has_local_replacement checks if the file contains local go module replacement 139 | has_local_replacement() { 140 | return "$(grep -E -c '=> \./\S+' "$1")" 141 | } 142 | 143 | 144 | ################ 145 | # main 146 | ################ 147 | move_vendor 148 | 149 | cleanup_gomod 150 | 151 | cleanup_vendor_modulestxt 152 | -------------------------------------------------------------------------------- /template/golang-middleware-inproc/go.sum: -------------------------------------------------------------------------------- 1 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 2 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 3 | github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= 4 | github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= 8 | github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= 9 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= 10 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= 11 | github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= 12 | github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= 13 | github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= 14 | github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= 15 | github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= 16 | github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= 17 | github.com/google/pprof v0.0.0-20250125003558-7fdb3d7e6fa0 h1:my2ucqBZmv+cWHIhZNSIYKzgN8EBGyHdC7zD5sASRAg= 18 | github.com/google/pprof v0.0.0-20250125003558-7fdb3d7e6fa0/go.mod h1:vavhavw2zAxS5dIdcRluK6cSGGPlZynqzFM8NdvU144= 19 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= 20 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 21 | github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= 22 | github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= 23 | github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= 24 | github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= 25 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= 26 | github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= 27 | github.com/onsi/ginkgo/v2 v2.22.2 h1:/3X8Panh8/WwhU/3Ssa6rCKqPLuAkVY2I0RoyDLySlU= 28 | github.com/onsi/ginkgo/v2 v2.22.2/go.mod h1:oeMosUL+8LtarXBHu/c0bx2D/K9zyQ6uX3cTyztHwsk= 29 | github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= 30 | github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= 31 | github.com/openfaas/faas-middleware v1.2.5 h1:RXPsuXw1PsxPsQ0MJz0ud3mU6X+Rg8v50ZAPxapVJ1g= 32 | github.com/openfaas/faas-middleware v1.2.5/go.mod h1:YuGnJ7wVW/dJEzZCltHYKGGHwfelQMPWt205m6PNfVE= 33 | github.com/openfaas/faas-provider v0.25.8 h1:3W1ZyhUvpqTOiNQoV7jzdZhDJeJJlJelosAtjFzMNyw= 34 | github.com/openfaas/faas-provider v0.25.8/go.mod h1:rMXbj+AYVpn82UoHIOgWHiDeV118t0bSxyoC9d00jpc= 35 | github.com/openfaas/of-watchdog v0.0.0-20251111152958-6696d30d7c8a h1:RInh0UKCMMA8QG40kcDNs4TQHOQcwR3xtcWtO21RsTo= 36 | github.com/openfaas/of-watchdog v0.0.0-20251111152958-6696d30d7c8a/go.mod h1:HjFTzt+rGkPtXEagz6qk3yTuqjJj52UPWeeSF02fosw= 37 | github.com/openfaas/of-watchdog v0.0.0-20251114100857-67a6309db21b h1:xrTwtQQBowj2SAfIHxZjXWddYPlU9Yb9g52XwrJ70s4= 38 | github.com/openfaas/of-watchdog v0.0.0-20251114100857-67a6309db21b/go.mod h1:HjFTzt+rGkPtXEagz6qk3yTuqjJj52UPWeeSF02fosw= 39 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 40 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 41 | github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= 42 | github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= 43 | github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= 44 | github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= 45 | github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= 46 | github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= 47 | github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= 48 | github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= 49 | github.com/rakutentech/jwk-go v1.2.0 h1:vNJwedPkRR+32V5WGNj0JP4COes93BGERvzQLBjLy4c= 50 | github.com/rakutentech/jwk-go v1.2.0/go.mod h1:pI0bYVntqaJ27RCpaC75MTUacheW0Rk4+8XzWWe1OWM= 51 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 52 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= 53 | go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= 54 | go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= 55 | golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= 56 | golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= 57 | golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= 58 | golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= 59 | golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= 60 | golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= 61 | golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= 62 | golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= 63 | golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE= 64 | golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588= 65 | google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= 66 | google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 67 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 68 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OpenFaaS Golang HTTP templates 2 | 3 | This repository contains two Golang templates for OpenFaaS which give additional control over the HTTP request and response. They will both handle higher throughput than the classic watchdog due to the process being kept warm. 4 | 5 | Our recommended template for Go developers is golang-middleware. 6 | 7 | You'll find a chapter dedicated to writing functions with Go in [Everyday Golang by Alex Ellis](https://store.openfaas.com/l/everyday-golang) 8 | 9 | Using the templates: 10 | 11 | ```bash 12 | faas-cli template store pull golang-http 13 | faas-cli template store pull golang-middleware 14 | ``` 15 | 16 | Or: 17 | 18 | ```bash 19 | $ faas template pull https://github.com/openfaas/golang-http-template 20 | $ faas new --list 21 | 22 | Languages available as templates: 23 | - golang-http 24 | - golang-middleware 25 | ``` 26 | 27 | The two templates are very similar: 28 | 29 | * `golang-middleware` implements a `http.HandleFunc` from Go's stdlib. 30 | * `golang-http` uses a structured request/response object 31 | 32 | ## Dependencies 33 | 34 | You can manage dependencies in one of the following ways: 35 | 36 | - To use Go modules without vendoring, the default already is set `GO111MODULE=on` but you also can make that explicit by adding `--build-arg GO111MODULE=on` to `faas-cli up`, you can also use `--build-arg GOPROXY=https://` if you want to use your own mirror for the modules 37 | - You can also Go modules with vendoring, run `go mod vendor` in your function folder and add `--build-arg GO111MODULE=off --build-arg GOFLAGS='-mod=vendor'` to `faas-cli up` 38 | - If you have a private module dependency, we recommend using the vendoring technique from above. 39 | 40 | ### SSH authentication for private Git repositories and modules 41 | 42 | If you do not wish to, or cannot use vendoring for some reason, then we provide an alternative set of templates for OpenFaaS Pro customers: 43 | 44 | * [OpenFaaS Pro templates for Go](https://github.com/openfaas/pro-templates) 45 | 46 | ## 1.0 golang-middleware (recommended template) 47 | 48 | This is one of the fastest templates available for Go available. Its signature is a [http.HandlerFunc](https://golang.org/pkg/net/http/#HandlerFunc), instead of a traditional request and response that you may expect from a function. 49 | 50 | The user has complete control over the HTTP request and response. 51 | 52 | ### Get the template 53 | 54 | ``` 55 | $ faas template store pull golang-middleware 56 | 57 | # Or 58 | 59 | $ faas template pull https://github.com/openfaas/golang-http-template 60 | $ faas new --lang golang-middleware 61 | ``` 62 | 63 | ### Example usage 64 | 65 | Example writing a JSON response: 66 | 67 | ```go 68 | package function 69 | 70 | import ( 71 | "encoding/json" 72 | "fmt" 73 | "io" 74 | "net/http" 75 | "os" 76 | ) 77 | 78 | func Handle(w http.ResponseWriter, r *http.Request) { 79 | var input []byte 80 | 81 | if r.Body != nil { 82 | defer r.Body.Close() 83 | 84 | // read request payload 85 | reqBody, err := io.ReadAll(r.Body) 86 | 87 | if err != nil { 88 | http.Error(w, err.Error(), http.StatusInternalServerError) 89 | return 90 | 91 | input = reqBody 92 | } 93 | } 94 | 95 | // log to stdout 96 | fmt.Printf("request body: %s", string(input)) 97 | 98 | response := struct { 99 | Payload string `json:"payload"` 100 | Headers map[string][]string `json:"headers"` 101 | Environment []string `json:"environment"` 102 | }{ 103 | Payload: string(input), 104 | Headers: r.Header, 105 | Environment: os.Environ(), 106 | } 107 | 108 | resBody, err := json.Marshal(response) 109 | if err != nil { 110 | http.Error(w, err.Error(), http.StatusInternalServerError) 111 | return 112 | } 113 | 114 | // write result 115 | w.WriteHeader(http.StatusOK) 116 | w.Write(resBody) 117 | } 118 | ``` 119 | 120 | Example persistent database connection pool between function calls: 121 | 122 | ```go 123 | package function 124 | 125 | import ( 126 | "database/sql" 127 | "fmt" 128 | "io" 129 | "net/http" 130 | "strings" 131 | _ "github.com/go-sql-driver/mysql" 132 | ) 133 | 134 | // db pool shared between function calls 135 | var db *sql.DB 136 | 137 | func init() { 138 | var err error 139 | db, err = sql.Open("mysql", "user:password@/dbname") 140 | if err != nil { 141 | panic(err.Error()) 142 | } 143 | 144 | err = db.Ping() 145 | if err != nil { 146 | panic(err.Error()) 147 | } 148 | } 149 | 150 | func Handle(w http.ResponseWriter, r *http.Request) { 151 | var query string 152 | ctx := r.Context() 153 | 154 | if r.Body != nil { 155 | defer r.Body.Close() 156 | 157 | // read request payload 158 | body, err := io.ReadAll(r.Body) 159 | 160 | if err != nil { 161 | http.Error(w, err.Error(), http.StatusInternalServerError) 162 | return 163 | } 164 | 165 | query = string(body) 166 | } 167 | 168 | // log to stdout 169 | fmt.Printf("Executing query: %s", query) 170 | 171 | rows, err := db.QueryContext(ctx, query) 172 | if err != nil { 173 | http.Error(w, err.Error(), http.StatusInternalServerError) 174 | return 175 | } 176 | defer rows.Close() 177 | 178 | ids := make([]string, 0) 179 | for rows.Next() { 180 | if e := ctx.Err(); e != nil { 181 | http.Error(w, e, http.StatusBadRequest) 182 | return 183 | } 184 | var id int 185 | if err := rows.Scan(&id); err != nil { 186 | http.Error(w, err.Error(), http.StatusInternalServerError) 187 | return 188 | } 189 | ids = append(ids, string(id)) 190 | } 191 | if err := rows.Err(); err != nil { 192 | http.Error(w, err.Error(), http.StatusInternalServerError) 193 | return 194 | } 195 | 196 | result := fmt.Sprintf("ids %s", strings.Join(ids, ", ")) 197 | 198 | // write result 199 | w.WriteHeader(http.StatusOK) 200 | w.Write([]byte(result)) 201 | } 202 | ``` 203 | 204 | Example retrieving request query strings 205 | 206 | ```go 207 | package function 208 | import ( 209 | "fmt" 210 | "net/http" 211 | ) 212 | func Handle(w http.ResponseWriter, r *http.Request) { 213 | // Parses RawQuery and returns the corresponding 214 | // values as a map[string][]string 215 | // for more info https://golang.org/pkg/net/url/#URL.Query 216 | query := r.URL.Query() 217 | w.WriteHeader(http.StatusOK) 218 | w.Write([]byte(fmt.Sprintf("id: %s", query.Get("id")))) 219 | } 220 | ``` 221 | 222 | ### Adding static files to your image 223 | 224 | If a folder named `static` is found in the root of your function's source code, **it will be copied** into the final image published for your function. 225 | 226 | To read this back at runtime, you can do the following: 227 | 228 | ```go 229 | package function 230 | 231 | import ( 232 | "net/http" 233 | "os" 234 | ) 235 | 236 | func Handle(w http.ResponseWriter, r *http.Request) { 237 | 238 | data, err := os.ReadFile("./static/file.txt") 239 | 240 | if err != nil { 241 | http.Error(w, err.Error(), http.StatusInternalServerError) 242 | } 243 | 244 | w.Write(data) 245 | } 246 | ``` 247 | 248 | ## 2.0 golang-http 249 | 250 | This template provides additional context and control over the HTTP response from your function. 251 | 252 | ### Status of the template 253 | 254 | Like the `golang-middleware` template, this template is highly performant and suitable for production. 255 | 256 | ### Get the template 257 | 258 | ```sh 259 | $ faas template store pull golang-http 260 | 261 | # Or 262 | $ faas template pull https://github.com/openfaas/golang-http-template 263 | $ faas new --lang golang-http 264 | ``` 265 | 266 | ### Example usage 267 | 268 | Example writing a successful message: 269 | 270 | ```go 271 | package function 272 | 273 | import ( 274 | "fmt" 275 | "net/http" 276 | 277 | handler "github.com/openfaas/templates-sdk/go-http" 278 | ) 279 | 280 | // Handle a function invocation 281 | func Handle(req handler.Request) (handler.Response, error) { 282 | var err error 283 | 284 | message := fmt.Sprintf("Hello world, input was: %s", string(req.Body)) 285 | 286 | return handler.Response{ 287 | Body: []byte(message), 288 | }, err 289 | } 290 | ``` 291 | 292 | Example writing a custom status code 293 | 294 | ```go 295 | package function 296 | 297 | import ( 298 | "fmt" 299 | "net/http" 300 | 301 | handler "github.com/openfaas/templates-sdk/go-http" 302 | ) 303 | 304 | // Handle a function invocation 305 | func Handle(req handler.Request) (handler.Response, error) { 306 | var err error 307 | 308 | return handler.Response{ 309 | Body: []byte("Your workload was accepted"), 310 | StatusCode: http.StatusAccepted, 311 | }, err 312 | } 313 | ``` 314 | 315 | Example writing an error / failure. 316 | 317 | ```go 318 | package function 319 | 320 | import ( 321 | "fmt" 322 | "net/http" 323 | 324 | handler "github.com/openfaas/templates-sdk/go-http" 325 | ) 326 | 327 | // Handle a function invocation 328 | func Handle(req handler.Request) (handler.Response, error) { 329 | var err error 330 | 331 | return handler.Response{ 332 | Body: []byte("the input was invalid") 333 | }, fmt.Errorf("invalid input") 334 | } 335 | ``` 336 | 337 | The error will be logged to `stderr` and the `body` will be written to the client along with a HTTP 500 status code. 338 | 339 | Example reading a header. 340 | 341 | ```go 342 | package function 343 | 344 | import ( 345 | "log" 346 | 347 | handler "github.com/openfaas/templates-sdk/go-http" 348 | ) 349 | 350 | // Handle a function invocation 351 | func Handle(req handler.Request) (handler.Response, error) { 352 | var err error 353 | 354 | log.Println(req.Header) // Check function logs for the request headers 355 | 356 | return handler.Response{ 357 | Body: []byte("This is the response"), 358 | Header: map[string][]string{ 359 | "X-Served-By": []string{"My Awesome Function"}, 360 | }, 361 | }, err 362 | } 363 | ``` 364 | 365 | Example responding to an aborted request. 366 | 367 | The `Request` object provides access to the request context. This allows you to check if the request has been cancelled by using the context's done channel `req.Context().Done()` or the context's error `req.Context().Err()` 368 | 369 | ```go 370 | package function 371 | 372 | import ( 373 | "fmt" 374 | "net/http" 375 | 376 | handler "github.com/openfaas/templates-sdk/go-http" 377 | ) 378 | 379 | // Handle a function invocation 380 | func Handle(req handler.Request) (handler.Response, error) { 381 | var err error 382 | 383 | for i := 0; i < 10000; i++ { 384 | if req.Context().Err() != nil { 385 | return handler.Response{}, fmt.Errorf("request cancelled") 386 | } 387 | fmt.Printf("count %d\n", i) 388 | } 389 | 390 | message := fmt.Sprintf("Hello world, input was: %s", string(req.Body)) 391 | return handler.Response{ 392 | Body: []byte(message), 393 | StatusCode: http.StatusOK, 394 | }, err 395 | } 396 | ``` 397 | 398 | This context can also be passed to other methods so that they can respond to the cancellation as well, for example [`db.ExecContext(req.Context())`](https://golang.org/pkg/database/sql/#DB.ExecContext) 399 | 400 | 401 | #### Advanced usage 402 | 403 | ##### Sub-packages 404 | 405 | It is often natural to organize your code into sub-packages, for example you may have a function with the following structure 406 | 407 | ``` 408 | ./echo 409 | ├── go.mod 410 | ├── go.sum 411 | ├── handler.go 412 | └── pkg 413 | └── version 414 | └── version.go 415 | ``` 416 | 417 | Now if you want to reference the`version` sub-package, import it as 418 | 419 | ```go 420 | import "handler/function/pkg/version" 421 | ``` 422 | 423 | This works like any local Go project. 424 | 425 | ##### Go sub-modules 426 | 427 | Sub-modules (meaning sub-folders with a `go.mod`) are not supported. 428 | 429 | --------------------------------------------------------------------------------