Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add go function invocation runtime as a package #1451

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
/func
/func_*
/cmd/func.yaml
/templates/go/cloudevents/go.sum
/templates/go/http/go.sum
/templates/typescript/cloudevents/build
/templates/typescript/http/build
/coverage.out
Expand Down
2 changes: 0 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,6 @@ bin/golangci-lint:

clean_templates:
# Removing temporary template files
@rm -f templates/go/cloudevents/go.sum
@rm -f templates/go/http/go.sum
@rm -rf templates/node/cloudevents/node_modules
@rm -rf templates/node/http/node_modules
@rm -rf templates/python/cloudevents/__pycache__
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ require (
knative.dev/serving v0.35.1-0.20221114131921-874ccebb8063
)

require github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
require (
github.com/gorilla/mux v1.8.0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/gorilla#gorilla-toolkit

not sure if we should depend on this package

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes I agree. For the time being, however, I think we can keep it as is until we come up with a suitable alternative.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@zroubalik also, note that I've moved this code over to https://github.com/knative-sandbox/func-go which will change this PR to just be modifications to the templates.

github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
)

require (
cloud.google.com/go/compute v1.10.0 // indirect
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
Expand Down
2 changes: 1 addition & 1 deletion pipelines/tekton/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ func validatePipeline(f fn.Function) error {
return ErrRuntimeRequired
}

if f.Runtime == "go" || f.Runtime == "rust" {
if f.Runtime == "rust" {
return ErrRuntimeNotSupported{f.Runtime}
}

Expand Down
4 changes: 2 additions & 2 deletions pipelines/tekton/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ func Test_validatePipeline(t *testing.T) {
wantErr: true,
},
{
name: "Unsupported runtime - Go - pack builder - without additional Buildpacks",
name: "Supported runtime - Go - pack builder - without additional Buildpacks",
function: fn.Function{Build: fn.BuildSpec{Builder: builders.Pack}, Runtime: "go"},
wantErr: true,
wantErr: false,
},
{
name: "Unsupported runtime - Go - pack builder - with additional Buildpacks",
Expand Down
34 changes: 34 additions & 0 deletions runtime/middleware_debug.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//go:build debug
// +build debug

package runtime

import (
"fmt"
"log"
"net/http"
"os"
"runtime/debug"
)

func recoverMiddleware(handler http.Handler) http.Handler {
logger := log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds)
f := func(rw http.ResponseWriter, req *http.Request) {
defer func() {
if r := recover(); r != nil {
recoverError := fmt.Errorf("user function error: %v", r)
stack := string(debug.Stack())
logger.Printf("%v\n%v\n", recoverError, stack)

rw.WriteHeader(http.StatusInternalServerError)

rw.Header().Add("Content-Type", "text/plain")
fmt.Fprintln(rw, recoverError)
fmt.Fprintln(rw, stack)
}
}()
handler.ServeHTTP(rw, req)
return
}
return http.HandlerFunc(f)
}
29 changes: 29 additions & 0 deletions runtime/middleware_release.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//go:build !debug
// +build !debug

package runtime

import (
"fmt"
"log"
"net/http"
"os"
"runtime/debug"
)

func recoverMiddleware(handler http.Handler) http.Handler {
logger := log.New(os.Stdout, "", log.LstdFlags|log.Lmicroseconds)
f := func(rw http.ResponseWriter, req *http.Request) {
defer func() {
if r := recover(); r != nil {
recoverError := fmt.Errorf("user function error: %v", r)
stack := string(debug.Stack())
logger.Printf("%v\n%v\n", recoverError, stack)

rw.WriteHeader(http.StatusInternalServerError)
}
}()
handler.ServeHTTP(rw, req)
}
return http.HandlerFunc(f)
}
161 changes: 161 additions & 0 deletions runtime/runtime.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package runtime

import (
"context"
"flag"
"fmt"
"net/http"
"os"
"os/signal"
"strconv"
"syscall"
"time"

cloudevents "github.com/cloudevents/sdk-go/v2"

"github.com/gorilla/mux"
)

var (
usage = "run\n\nRuns a CloudFunction CloudEvent handler."
verbose = flag.Bool("V", false, "Verbose logging [$VERBOSE]")
port = flag.Int("port", 8080, "Listen on all interfaces at the given port [$PORT]")
)

func Start(handler interface{}) {
flag.Usage = func() {
fmt.Fprintln(os.Stderr, usage)
flag.PrintDefaults()
}
parseEnv() // override static defaults with environment.
flag.Parse() // override env vars with flags.
if err := run(handler); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

}

// parseEnv parses environment variables, populating the destination flags
// prior to the builtin flag parsing. Invalid values exit 1.
func parseEnv() {
parseBool := func(key string, dest *bool) {
if val, ok := os.LookupEnv(key); ok {
b, err := strconv.ParseBool(val)
if err != nil {
fmt.Fprintf(os.Stderr, "%v is not a valid boolean\n", key)
os.Exit(1)
}
*dest = b
}
}
parseInt := func(key string, dest *int) {
if val, ok := os.LookupEnv(key); ok {
n, err := strconv.Atoi(val)
if err != nil {
fmt.Fprintf(os.Stderr, "%v is not a valid integer\n", key)
os.Exit(1)
}
*dest = n
}
}

parseBool("VERBOSE", verbose)
parseInt("PORT", port)
}

// run a cloudevents client in receive mode which invokes
// the user-defined function.Handler on receipt of an event.
func run(handler interface{}) error {
ctx, cancel := context.WithCancel(context.Background())

sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
cancel()
}()

// Use a gorilla mux for handling all HTTP requests
router := mux.NewRouter()

// Add handlers for readiness and liveness endpoints
router.HandleFunc("/health/{endpoint:readiness|liveness}", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "OK")
})

httpHandler := toHttpHandler(handler, ctx)

if httpHandler == nil {
if *verbose {
fmt.Printf("Initializing CloudEvent function\n")
}
protocol, err := cloudevents.NewHTTP(
cloudevents.WithPort(*port),
cloudevents.WithPath("/"),
)
if err != nil {
return err
}
eventHandler, err := cloudevents.NewHTTPReceiveHandler(ctx, protocol, handler)
if err != nil {
return err
}
router.Handle("/", eventHandler)
} else {
if *verbose {
fmt.Printf("Initializing HTTP function\n")
}
router.Handle("/", httpHandler)
}

httpServer := &http.Server{
Addr: fmt.Sprintf(":%d", *port),
Handler: router,
ReadTimeout: 1 * time.Minute,
WriteTimeout: 1 * time.Minute,
MaxHeaderBytes: 1 << 20,
}

listenAndServeErr := make(chan error, 1)
go func() {
if *verbose {
fmt.Printf("listening on http port %v\n", *port)
}
err := httpServer.ListenAndServe()
cancel()
listenAndServeErr <- err
}()

<-ctx.Done()
shutdownCtx, shutdownCancelFn := context.WithTimeout(context.Background(), time.Second*5)
defer shutdownCancelFn()
err := httpServer.Shutdown(shutdownCtx)
if err != nil {
fmt.Fprintf(os.Stderr, "error on server shutdown: %v\n", err)
}

err = <-listenAndServeErr
if http.ErrServerClosed == err {
return nil
}
return err
}

// if the handler signature is compatible with http handler the function returns an instance of `http.Handler`,
// otherwise nil is returned
func toHttpHandler(handler interface{}, ctx context.Context) http.Handler {

if f, ok := handler.(func(rw http.ResponseWriter, req *http.Request)); ok {
return recoverMiddleware(http.HandlerFunc(f))
}

if f, ok := handler.(func(ctx context.Context, rw http.ResponseWriter, req *http.Request)); ok {
ff := func(rw http.ResponseWriter, req *http.Request) {
f(ctx, rw, req)
}
return recoverMiddleware(http.HandlerFunc(ff))
}

return nil
}
11 changes: 11 additions & 0 deletions templates/go/cloudevents/faas/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
function "function"

"knative.dev/func/runtime"
)

func main() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add some comment here pointing users to handle.go. So they don't try to write the function code here.

runtime.Start(function.Handle)
}
21 changes: 19 additions & 2 deletions templates/go/cloudevents/go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
module function

go 1.14
go 1.18

require github.com/cloudevents/sdk-go/v2 v2.5.0
require (
github.com/cloudevents/sdk-go/v2 v2.12.0
knative.dev/func v0.35.1
)

require (
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
)

// TODO: Fix this chicken and egg problem.
replace knative.dev/func => github.com/lance/func v1.1.3-0.20221130200745-483914d520ca
32 changes: 32 additions & 0 deletions templates/go/cloudevents/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
github.com/cloudevents/sdk-go/v2 v2.12.0 h1:p1k+ysVOZtNiXfijnwB3WqZNA3y2cGOiKQygWkUHCEI=
github.com/cloudevents/sdk-go/v2 v2.12.0/go.mod h1:xDmKfzNjM8gBvjaF8ijFjM1VYOVUEeUfapHMUX1T5To=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/lance/func v1.1.3-0.20221130200745-483914d520ca h1:kgfYaTkb3Y0spZh1eQBryCt1ulkpRRDY5ArpyQvw7DQ=
github.com/lance/func v1.1.3-0.20221130200745-483914d520ca/go.mod h1:GFUD/8UjpoG3SKFDl89Lf62eNXcP8rafnklDZQEXSXI=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/atomic v1.10.0 h1:9qC72Qh0+3MqyJbAn8YU5xVq1frD8bn3JtD2oXtafVQ=
go.uber.org/atomic v1.10.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY=
go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
11 changes: 11 additions & 0 deletions templates/go/http/faas/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package main

import (
function "function"

"knative.dev/func/runtime"
)

func main() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should add some comment here pointing users to handle.go. So they don't try to write the function code here.

runtime.Start(function.Handle)
}
19 changes: 18 additions & 1 deletion templates/go/http/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,20 @@
module function

go 1.14
go 1.18

require knative.dev/func v0.35.1

require (
github.com/cloudevents/sdk-go/v2 v2.12.0 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/mux v1.8.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
go.uber.org/atomic v1.10.0 // indirect
go.uber.org/multierr v1.8.0 // indirect
go.uber.org/zap v1.23.0 // indirect
)

// TODO: Fix this chicken and egg problem.
replace knative.dev/func => github.com/lance/func v1.1.3-0.20221130200745-483914d520ca
Loading