diff --git a/README.md b/README.md index 7b40e7a..292ab39 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ The two templates are equivalent with `golang-http` using a structured request/r You can manage dependencies in one of the following ways: * To use Go modules without vendoring, add `--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 +* You can also Go modules with vendoring, run `go mod vendor` in your function folder and add `--build-arg GO111MODULE=on` to `faas-cli up` * For traditional vendoring with `dep` give no argument, or add `--build-arg GO111MODULE=off` to `faas-cli up` ## 1.0 golang-http @@ -361,7 +362,37 @@ func Handle(w http.ResponseWriter, r *http.Request) { } ``` -#### Advanced usage - Go sub-modules via `GO_REPLACE.txt` +#### Advanced usage + +##### Sub-packages + +It is often natural to organize your code into sub-packages, for example you may have a function with the following structure + +``` +./echo +├── go.mod +├── go.sum +├── handler.go +└── pkg + └── version + └── version.go +``` + +First update your go.mod file to replace `handler/function` with your local folder + +```go +go mod edit -replace=handler/function=./ +``` + +Now if you want to reference the`version` sub-package, import it as + +```go +import "handler/function/pkg/version" +``` + +This replacement is handled gracefully by the template at build time and your local development environment will now recognize the sub-package. + +##### Go sub-modules For this example you will need to be using Go 1.13 or newer and Go modules, enable this via `faas-cli build --build-arg GO111MODULE=on`. @@ -384,16 +415,13 @@ func Echo(w http.ResponseWriter, r *http.Request) { } ``` -To include a relative module such as this new `handlers` package, you should create a `GO_REPLACE.txt` file as follows: +To include a relative module such as this new `handlers` package, you should update your `go.mod` file as follows: ``` -replace github.com/alexellis/vault/purchase/handlers => ./function/handlers +go mod edit -replace=github.com/alexellis/vault/purchase/handlers=./handlers ``` -How did we get to that? Let's say your GOPATH for your GitHub repo is: `github.com/alexellis/vault/` and your OpenFaaS function is `purchase` generated by `faas-cli new purchase --lang golang-middleware`. - -Your relative GOPATH is: `github.com/alexellis/vault/purchase`, so add a redirect as per below to redirect the "handlers" package to where it exists in the build container. - +At build time, this relative path will be handled correctly inside the template. Now if you want to reference the handlers package from within your `handler.go` write the following: diff --git a/template/golang-http/Dockerfile b/template/golang-http/Dockerfile index d882607..80b0579 100644 --- a/template/golang-http/Dockerfile +++ b/template/golang-http/Dockerfile @@ -8,41 +8,49 @@ ARG TARGETARCH RUN apk --no-cache add git -ENV CGO_ENABLED=0 - COPY --from=watchdog /fwatchdog /usr/bin/fwatchdog RUN chmod +x /usr/bin/fwatchdog +ENV CGO_ENABLED=0 + RUN mkdir -p /go/src/handler WORKDIR /go/src/handler COPY . . +ARG GO111MODULE="off" +ARG GOPROXY="" +ARG GOFLAGS="" +ARG DEBUG=0 + +# Lift the vendor and go.mod to the main package, cleanup any relative references +RUN sh modules-cleanup.sh + # Run a gofmt and exclude all vendored code. 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; } -ARG GO111MODULE="off" -ARG GOPROXY="" +WORKDIR /go/src/handler/function + +RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test ./... -cover + +WORKDIR /go/src/handler RUN CGO_ENABLED=${CGO_ENABLED} GOOS=${TARGETOS} GOARCH=${TARGETARCH} \ go build --ldflags "-s -w" -a -installsuffix cgo -o handler . -RUN GOOS=${TARGETOS} GOARCH=${TARGETARCH} go test handler/function/... -cover FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.13 # Add non root user and certs RUN apk --no-cache add ca-certificates \ && addgroup -S app && adduser -S -g app app -# Split instructions so that buildkit can run & cache +# Split instructions so that buildkit can run & cache # the previous command ahead of time. RUN mkdir -p /home/app \ && chown app /home/app WORKDIR /home/app -COPY --from=build /go/src/handler/handler . -COPY --from=build /usr/bin/fwatchdog . -COPY --from=build /go/src/handler/function/ . - -RUN chown -R app /home/app +COPY --from=build --chown=app /go/src/handler/handler . +COPY --from=build --chown=app /usr/bin/fwatchdog . +COPY --from=build --chown=app /go/src/handler/function/ . USER app diff --git a/template/golang-http/modules-cleanup.sh b/template/golang-http/modules-cleanup.sh new file mode 100644 index 0000000..fcc5354 --- /dev/null +++ b/template/golang-http/modules-cleanup.sh @@ -0,0 +1,149 @@ +#!/usr/bin/env sh + +set -e + +GO111MODULE=$(go env GO111MODULE) + +# move_vendor will copy the function's vendor folder, +# if it exists. +move_vendor() { + if [ ! -d ./function/vendor ]; then + echo "vendor not found" + return + fi + + echo "moving function vendor" + mv -f ./function/vendor . +} + + +# cleanup_gomod will move the function's go module +cleanup_gomod() { + + # Nothing to do when modules is explicitly off + # the z prefix protects against any SH wonkiness + # see https://stackoverflow.com/a/18264223 + if [ "z$GO111MODULE" = "zoff" ]; then + echo "modules disabled, skipping go.mod cleanup" + return; + fi + + if [ ! -f ./function/go.mod ]; then + echo "module not initialized, skipping go.mod cleanup" + return; + fi + + echo "cleaning up go.mod" + + # Copy the user's go.mod + mv -f ./function/go.mod . + mv -f ./function/go.sum . + + # Clean up the go.mod + + # Cleanup any sub-module replacements. + # This requires modifying any replace that points to "./*", + # the user has will use this to reference sub-modules instead + # of sub-packages, which we cleanup below. + echo "cleanup local replace statements" + # 1. Replace references to the local folder with `./function` + sed -i 's/=> \.\//=> \.\/function\//' go.mod + + + # Remove any references to the handler/function module. + # It is ok to just remove it because we will replace it later. + # + # Note that these references may or may not exist. We expect the + # go.mod to have a replace statement _if_ developer has subpackages + # in their handler. In this case they will need a this replace statement + # + # replace handler/function => ./ + # + # `go mod` will then add a line that looks like + # + # handler/function v0.0.0-00010101000000-000000000000 + # + # both of these lines need to be replaced, this grep selects everything + # _except_ those offending lines. + grep -v "\shandler/function" go.mod > gomod2; mv gomod2 go.mod + + # Now update the go.mod + # + # 1. use replace so that imports of handler/function use the local code + # 2. we need to rename the module to handler because our main.go assumes + # this is the package name + go mod edit \ + -replace=handler/function=./function \ + -module handler + + + + if [ "$DEBUG" -eq 1 ]; then + cat go.mod + echo "" + fi +} + + +# cleanup_vendor_modulestxt will cleanup the modules.txt file in the vendor folder +# this file is needed when modules are enabled and it must be in sync with the +# go.mod. To function correctly we need to modify the references to handler/function, +# if they exist. +cleanup_vendor_modulestxt() { + if [ ! -d ./vendor ]; then + echo "no vendor found, skipping modules.txt cleanup" + return + fi + + # Nothing to do when modules is explicitly off + # the z prefix protects against any SH wonkiness + # see https://stackoverflow.com/a/18264223 + if [ "z$GO111MODULE" = "zoff" ]; then + echo "modules disabled, skipping modules.txt cleanup" + return; + fi + + echo "cleanup vendor/modules.txt" + + # just in case + touch "./vendor/modules.txt" + + # when vendored, we need to do similar edits to the vendor/modules.txt + # as we did to the go.mod + + # 1. we need to replace any possible copy of the handler code + rm -rf vendor/handler && \ + + # 2. in modules.txt, we remove existing references to the handler/function + # we reconstruct these in the last step + grep -v "\shandler/function" ./vendor/modules.txt> modulestext; mv modulestext ./vendor/modules.txt + + # 3. Handle any other local replacements. + # any replace that points to `./**` needs to be udpat echo "cleanup local replace statements" + sed -i 's/=> \.\//=> \.\/function\//' ./vendor/modules.txt + + # 4. To make the modules.txt consistent with the new go.mod, + # we add the mising replace to the vendor/modules.txt + echo "## explicit" >> ./vendor/modules.txt + echo "# handler/function => ./function" >> ./vendor/modules.txt + + if [ "$DEBUG" -eq 1 ]; then + cat ./vendor/modules.txt; + echo "" + fi +} + +# has_local_replacement checks if the file contains local go module replacement +has_local_replacement() { + return "$(grep -E -c '=> \./\S+' "$1")" +} + + +################ +# main +################ +move_vendor + +cleanup_gomod + +cleanup_vendor_modulestxt \ No newline at end of file diff --git a/template/golang-http/vendor/github.com/openfaas/templates-sdk/LICENSE b/template/golang-http/vendor/github.com/openfaas/templates-sdk/LICENSE deleted file mode 100644 index 7ecb7d7..0000000 --- a/template/golang-http/vendor/github.com/openfaas/templates-sdk/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2020 OpenFaaS - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/template/golang-http/vendor/github.com/openfaas/templates-sdk/go-http/README.md b/template/golang-http/vendor/github.com/openfaas/templates-sdk/go-http/README.md deleted file mode 100644 index a4a5be7..0000000 --- a/template/golang-http/vendor/github.com/openfaas/templates-sdk/go-http/README.md +++ /dev/null @@ -1,41 +0,0 @@ -# go-http SDK - -An SDK for building OpenFaaS functions in Go - -## Installing - -Use `go get` to retrieve the SDK to add it to your `GOPATH` workspace, or -project's Go module dependencies. - - go get github.com/openfaas/templates-sdk/go-http - -To update the SDK use `go get -u` to retrieve the latest version of the SDK. - - go get -u github.com/openfaas/templates-sdk/go-http - - -## Features - -### Handler definition - -```go -type FunctionHandler interface { - Handle(req Request) (Response, error) -} -``` - -`FunctionHandler` interface is used by [golang-http](https://github.com/openfaas-incubator/golang-http-template/tree/master/template/golang-http) template to define a functions handler - -### Secrets -For the time being please use the secrets function from `github.com/openfaas/openfaas-cloud/sdk` - -See: https://github.com/openfaas/openfaas-cloud/blob/master/sdk/secrets.go - -Usage: - -```go -secret, err := sdk.ReadSecret("MY_SECRET") -if err != nil { - return fmt.Errorf("error reading secret. %v", err) -} -``` diff --git a/template/golang-http/vendor/github.com/openfaas/templates-sdk/go-http/handler.go b/template/golang-http/vendor/github.com/openfaas/templates-sdk/go-http/handler.go deleted file mode 100644 index 4f5d376..0000000 --- a/template/golang-http/vendor/github.com/openfaas/templates-sdk/go-http/handler.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Alex Ellis 2018. All rights reserved. -// Licensed under the MIT license. See LICENSE file in the project root for full license information. -package handler - -import ( - "context" - "net/http" -) - -// Response of function call -type Response struct { - - // Body the body will be written back - Body []byte - - // StatusCode needs to be populated with value such as http.StatusOK - StatusCode int - - // Header is optional and contains any additional headers the function response should set - Header http.Header -} - -// Request of function call -type Request struct { - Body []byte - Header http.Header - QueryString string - Method string - Host string - ctx context.Context -} - -// Context is set for optional cancellation inflight requests. -func (r *Request) Context() context.Context { - return r.ctx -} - -// WithContext overides the context for the Request struct -func (r *Request) WithContext(ctx context.Context) { - // AE: Not keen on panic mid-flow in user-code, however stdlib also appears to do - // this. https://golang.org/src/net/http/request.go - // This is not setting a precedent for broader use of "panic" to handle errors. - if ctx == nil { - panic("nil context") - } - r.ctx = ctx -} - -// FunctionHandler used for a serverless Go method invocation -type FunctionHandler interface { - Handle(req Request) (Response, error) -} - -func init() { - -} diff --git a/template/golang-http/vendor/modules.txt b/template/golang-http/vendor/modules.txt deleted file mode 100644 index 08e7770..0000000 --- a/template/golang-http/vendor/modules.txt +++ /dev/null @@ -1,2 +0,0 @@ -# github.com/openfaas/templates-sdk v0.0.0-20200723092016-0ebf61253625 -github.com/openfaas/templates-sdk/go-http diff --git a/template/golang-middleware/Dockerfile b/template/golang-middleware/Dockerfile index ffaa3c9..91007f4 100644 --- a/template/golang-middleware/Dockerfile +++ b/template/golang-middleware/Dockerfile @@ -17,15 +17,16 @@ RUN mkdir -p /go/src/handler WORKDIR /go/src/handler COPY . . -# Add user overrides to the root go.mod, which is the only place "replace" can be used -RUN cat function/GO_REPLACE.txt >> ./go.mod || exit 0 - -# Run a gofmt and exclude all vendored code. -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; } - ARG GO111MODULE="off" ARG GOPROXY="" ARG GOFLAGS="" +ARG DEBUG=0 + +# Lift the vendor and go.mod to the main package, cleanup any relative references +RUN sh modules-cleanup.sh + +# Run a gofmt and exclude all vendored code. +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; } WORKDIR /go/src/handler/function @@ -39,7 +40,7 @@ FROM --platform=${TARGETPLATFORM:-linux/amd64} alpine:3.13 # Add non root user and certs RUN apk --no-cache add ca-certificates \ && addgroup -S app && adduser -S -g app app -# Split instructions so that buildkit can run & cache +# Split instructions so that buildkit can run & cache # the previous command ahead of time. RUN mkdir -p /home/app \ && chown app /home/app diff --git a/template/golang-middleware/modules-cleanup.sh b/template/golang-middleware/modules-cleanup.sh new file mode 100644 index 0000000..ab5d458 --- /dev/null +++ b/template/golang-middleware/modules-cleanup.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env sh + +set -e + +GO111MODULE=$(go env GO111MODULE) + +# move_vendor will copy the function's vendor folder, +# if it exists. +move_vendor() { + if [ ! -d ./function/vendor ]; then + echo "vendor not found" + return + fi + + echo "moving function vendor" + mv -f ./function/vendor . +} + + +# cleanup_gomod will move the function's go module +cleanup_gomod() { + + # Nothing to do when modules is explicitly off + # the z prefix protects against any SH wonkiness + # see https://stackoverflow.com/a/18264223 + if [ "z$GO111MODULE" = "zoff" ]; then + echo "modules disabled, skipping go.mod cleanup" + return; + fi + + if [ ! -f ./function/go.mod ]; then + echo "module not initialized, skipping go.mod cleanup" + return; + fi + + echo "cleaning up go.mod" + + # Copy the user's go.mod + mv -f ./function/go.mod . + mv -f ./function/go.sum . + + # Clean up the go.mod + + # Cleanup any sub-module replacements. + # This requires modifying any replace that points to "./*", + # the user has will use this to reference sub-modules instead + # of sub-packages, which we cleanup below. + echo "cleanup local replace statements" + # 1. Replace references to the local folder with `./function` + sed -i 's/=> \.\//=> \.\/function\//' go.mod + + + # Remove any references to the handler/function module. + # It is ok to just remove it because we will replace it later. + # + # Note that these references may or may not exist. We expect the + # go.mod to have a replace statement _if_ developer has subpackages + # in their handler. In this case they will need a this replace statement + # + # replace handler/function => ./ + # + # `go mod` will then add a line that looks like + # + # handler/function v0.0.0-00010101000000-000000000000 + # + # both of these lines need to be replaced, this grep selects everything + # _except_ those offending lines. + grep -v "\shandler/function" go.mod > gomod2; mv gomod2 go.mod + + # Now update the go.mod + # + # 1. use replace so that imports of handler/function use the local code + # 2. we need to rename the module to handler because our main.go assumes + # this is the package name + go mod edit \ + -replace=handler/function=./function \ + -module handler + + + + if [ "$DEBUG" -eq 1 ]; then + cat go.mod + echo "" + fi +} + + +# cleanup_vendor_modulestxt will cleanup the modules.txt file in the vendor folder +# this file is needed when modules are enabled and it must be in sync with the +# go.mod. To function correctly we need to modify the references to handler/function, +# if they exist. +cleanup_vendor_modulestxt() { + if [ ! -d ./vendor ]; then + echo "no vendor found, skipping modules.txt cleanup" + return + fi + + # Nothing to do when modules is explicitly off + # the z prefix protects against any SH wonkiness + # see https://stackoverflow.com/a/18264223 + if [ "z$GO111MODULE" = "zoff" ]; then + echo "modules disabled, skipping modules.txt cleanup" + return; + fi + + if [ -f ./function/go.mod ]; then + echo "module not initialized, skipping go.mod cleanup" + return; + fi + + echo "cleanup vendor/modules.txt" + + # just in case + touch "./vendor/modules.txt" + + # when vendored, we need to do similar edits to the vendor/modules.txt + # as we did to the go.mod + + # 1. we need to replace any possible copy of the handler code + rm -rf vendor/handler && \ + + # 2. in modules.txt, we remove existing references to the handler/function + # we reconstruct these in the last step + grep -v "\shandler/function" ./vendor/modules.txt> modulestext; mv modulestext ./vendor/modules.txt + + # 3. Handle any other local replacements. + # any replace that points to `./**` needs to be udpat echo "cleanup local replace statements" + sed -i 's/=> \.\//=> \.\/function\//' ./vendor/modules.txt + + # 4. To make the modules.txt consistent with the new go.mod, + # we add the mising replace to the vendor/modules.txt + echo "## explicit" >> ./vendor/modules.txt + echo "# handler/function => ./function" >> ./vendor/modules.txt + + if [ "$DEBUG" -eq 1 ]; then + cat ./vendor/modules.txt; + echo "" + fi +} + +# has_local_replacement checks if the file contains local go module replacement +has_local_replacement() { + return "$(grep -E -c '=> \./\S+' "$1")" +} + + +################ +# main +################ +move_vendor + +cleanup_gomod + +cleanup_vendor_modulestxt \ No newline at end of file