Skip to content

Commit 798fc07

Browse files
authored
feat: declarative function api (#48)
* init declarative functions Signed-off-by: Lize Cai <[email protected]> * add tests for declarative functions Signed-off-by: Lize Cai <[email protected]> * update unit tests Signed-off-by: Lize Cai <[email protected]> * update e2e files for declarative functions Signed-off-by: Lize Cai <[email protected]> * add default http pattern for existing single function register method Signed-off-by: Lize Cai <[email protected]> * fix declarative tests, update CMD in dockerfile Signed-off-by: Lize Cai <[email protected]>
1 parent f1e311b commit 798fc07

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1670
-29
lines changed

.github/workflows/main.yml

+10
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ on:
88
- 'context/**'
99
- 'plugin/**'
1010
- 'framework/**'
11+
- 'functions/**'
12+
- 'internal/**'
1113
- 'runtime/**'
1214
- 'test/**'
1315
- 'go.mod'
@@ -45,6 +47,14 @@ jobs:
4547
e2e: "test/sync-http/e2e.yaml"
4648
- name: Sync Cloudevent e2e test
4749
e2e: "test/sync-cloudevent/e2e.yaml"
50+
- name: Declarative Sync HTTP e2e test
51+
e2e: "test/declarative/sync-http/e2e.yaml"
52+
- name: Declarative Sync Cloudevent e2e test
53+
e2e: "test/declarative/sync-cloudevent/e2e.yaml"
54+
- name: Declarative multiple Sync HTTP e2e test
55+
e2e: "test/declarative/sync-http-multiple/e2e.yaml"
56+
- name: Declarative multiple Sync Cloudevent e2e test
57+
e2e: "test/declarative/sync-cloudevent-multiple/e2e.yaml"
4858
steps:
4959
- uses: actions/checkout@v2
5060

.github/workflows/plugin_test.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ on:
88
- 'context/**'
99
- 'plugin/**'
1010
- 'framework/**'
11+
- 'functions/**'
12+
- 'internal/**'
1113
- 'runtime/**'
1214
- 'test/**'
1315
- 'go.mod'

.gitignore

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
# IDE
22
.idea/
3-
.vscode/
3+
.vscode/
4+
5+
bin/

context/context.go

+5
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ const (
4848
Success = 200
4949
InternalError = 500
5050
defaultPort = "8080"
51+
defaultHttpPattern = "/"
5152
daprSidecarGRPCPort = "50001"
5253
TracingProviderSkywalking = "skywalking"
5354
TracingProviderOpentelemetry = "opentelemetry"
@@ -796,6 +797,10 @@ func parseContext() (*FunctionContext, error) {
796797
}
797798
}
798799

800+
if ctx.HttpPattern == "" {
801+
ctx.HttpPattern = defaultHttpPattern
802+
}
803+
799804
// When using self-hosted mode, configure the client port via env,
800805
// refer to https://docs.dapr.io/reference/environment/
801806
port := os.Getenv("DAPR_GRPC_PORT")

framework/framework.go

+73-5
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"context"
55
"errors"
66
"net/http"
7+
"os"
78

89
cloudevents "github.com/cloudevents/sdk-go/v2"
910
"k8s.io/klog/v2"
1011

1112
ofctx "github.com/OpenFunction/functions-framework-go/context"
13+
"github.com/OpenFunction/functions-framework-go/internal/functions"
14+
"github.com/OpenFunction/functions-framework-go/internal/registry"
1215
"github.com/OpenFunction/functions-framework-go/plugin"
1316
plgExample "github.com/OpenFunction/functions-framework-go/plugin/plugin-example"
1417
"github.com/OpenFunction/functions-framework-go/plugin/skywalking"
@@ -23,6 +26,7 @@ type functionsFrameworkImpl struct {
2326
postPlugins []plugin.Plugin
2427
pluginMap map[string]plugin.Plugin
2528
runtime runtime.Interface
29+
registry *registry.Registry
2630
}
2731

2832
// Framework is the interface for the function conversion.
@@ -36,6 +40,9 @@ type Framework interface {
3640
func NewFramework() (*functionsFrameworkImpl, error) {
3741
fwk := &functionsFrameworkImpl{}
3842

43+
// Set the function registry
44+
fwk.registry = registry.Default()
45+
3946
// Parse OpenFunction FunctionContext
4047
if ctx, err := ofctx.GetRuntimeContext(); err != nil {
4148
klog.Errorf("failed to parse OpenFunction FunctionContext: %v\n", err)
@@ -59,17 +66,29 @@ func NewFramework() (*functionsFrameworkImpl, error) {
5966

6067
func (fwk *functionsFrameworkImpl) Register(ctx context.Context, fn interface{}) error {
6168
if fnHTTP, ok := fn.(func(http.ResponseWriter, *http.Request)); ok {
62-
if err := fwk.runtime.RegisterHTTPFunction(fwk.funcContext, fwk.prePlugins, fwk.postPlugins, fnHTTP); err != nil {
69+
rf, err := functions.New(functions.WithFunctionName(fwk.funcContext.GetName()), functions.WithHTTP(fnHTTP), functions.WithFunctionPath(fwk.funcContext.GetHttpPattern()))
70+
if err != nil {
71+
klog.Errorf("failed to register function: %v", err)
72+
}
73+
if err := fwk.runtime.RegisterHTTPFunction(fwk.funcContext, fwk.prePlugins, fwk.postPlugins, rf); err != nil {
6374
klog.Errorf("failed to register function: %v", err)
6475
return err
6576
}
6677
} else if fnOpenFunction, ok := fn.(func(ofctx.Context, []byte) (ofctx.Out, error)); ok {
67-
if err := fwk.runtime.RegisterOpenFunction(fwk.funcContext, fwk.prePlugins, fwk.postPlugins, fnOpenFunction); err != nil {
78+
rf, err := functions.New(functions.WithFunctionName(fwk.funcContext.GetName()), functions.WithOpenFunction(fnOpenFunction), functions.WithFunctionPath(fwk.funcContext.GetHttpPattern()))
79+
if err != nil {
80+
klog.Errorf("failed to register function: %v", err)
81+
}
82+
if err := fwk.runtime.RegisterOpenFunction(fwk.funcContext, fwk.prePlugins, fwk.postPlugins, rf); err != nil {
6883
klog.Errorf("failed to register function: %v", err)
6984
return err
7085
}
7186
} else if fnCloudEvent, ok := fn.(func(context.Context, cloudevents.Event) error); ok {
72-
if err := fwk.runtime.RegisterCloudEventFunction(ctx, fwk.funcContext, fwk.prePlugins, fwk.postPlugins, fnCloudEvent); err != nil {
87+
rf, err := functions.New(functions.WithFunctionName(fwk.funcContext.GetName()), functions.WithCloudEvent(fnCloudEvent), functions.WithFunctionPath(fwk.funcContext.GetHttpPattern()))
88+
if err != nil {
89+
klog.Errorf("failed to register function: %v", err)
90+
}
91+
if err := fwk.runtime.RegisterCloudEventFunction(ctx, fwk.funcContext, fwk.prePlugins, fwk.postPlugins, rf); err != nil {
7392
klog.Errorf("failed to register function: %v", err)
7493
return err
7594
}
@@ -82,6 +101,56 @@ func (fwk *functionsFrameworkImpl) Register(ctx context.Context, fn interface{})
82101
}
83102

84103
func (fwk *functionsFrameworkImpl) Start(ctx context.Context) error {
104+
105+
target := os.Getenv("FUNCTION_TARGET")
106+
107+
// if FUNCTION_TARGET is provided
108+
if len(target) > 0 {
109+
if fn, ok := fwk.registry.GetRegisteredFunction(target); ok {
110+
klog.Infof("registering function: %s on path: %s", target, fn.GetPath())
111+
switch fn.GetFunctionType() {
112+
case functions.HTTPType:
113+
fwk.Register(ctx, fn.GetHTTPFunction())
114+
case functions.CloudEventType:
115+
fwk.Register(ctx, fn.GetCloudEventFunction())
116+
case functions.OpenFunctionType:
117+
fwk.Register(ctx, fn.GetOpenFunctionFunction())
118+
}
119+
} else {
120+
klog.Errorf("function not found: %s", target)
121+
}
122+
} else {
123+
// if FUNCTION_TARGET is not provided but user uses declarative function, by default all registered functions will be deployed.
124+
funcNames := fwk.registry.GetFunctionNames()
125+
if len(funcNames) > 1 && fwk.funcContext.GetRuntime() == ofctx.Async {
126+
return errors.New("only one function is allowed in async runtime")
127+
} else if len(funcNames) > 0 {
128+
klog.Info("no 'FUNCTION_TARGET' is provided, register all the functions in the registry")
129+
for _, name := range funcNames {
130+
if rf, ok := fwk.registry.GetRegisteredFunction(name); ok {
131+
klog.Infof("registering function: %s on path: %s", rf.GetName(), rf.GetPath())
132+
switch rf.GetFunctionType() {
133+
case functions.HTTPType:
134+
if err := fwk.runtime.RegisterHTTPFunction(fwk.funcContext, fwk.prePlugins, fwk.postPlugins, rf); err != nil {
135+
klog.Errorf("failed to register function: %v", err)
136+
return err
137+
}
138+
case functions.CloudEventType:
139+
if err := fwk.runtime.RegisterCloudEventFunction(ctx, fwk.funcContext, fwk.prePlugins, fwk.postPlugins, rf); err != nil {
140+
klog.Errorf("failed to register function: %v", err)
141+
return err
142+
}
143+
case functions.OpenFunctionType:
144+
if err := fwk.runtime.RegisterOpenFunction(fwk.funcContext, fwk.prePlugins, fwk.postPlugins, rf); err != nil {
145+
klog.Errorf("failed to register function: %v", err)
146+
return err
147+
}
148+
}
149+
}
150+
}
151+
}
152+
}
153+
85154
err := fwk.runtime.Start(ctx)
86155
if err != nil {
87156
klog.Error("failed to start runtime service")
@@ -136,13 +205,12 @@ func createRuntime(fwk *functionsFrameworkImpl) error {
136205
rt := fwk.funcContext.GetRuntime()
137206
port := fwk.funcContext.GetPort()
138207
pattern := fwk.funcContext.GetHttpPattern()
139-
140208
switch rt {
141209
case ofctx.Knative:
142210
fwk.runtime = knative.NewKnativeRuntime(port, pattern)
143211
return nil
144212
case ofctx.Async:
145-
fwk.runtime, err = async.NewAsyncRuntime(port)
213+
fwk.runtime, err = async.NewAsyncRuntime(port, pattern)
146214
if err != nil {
147215
return err
148216
}

functions/options.go

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package functions
2+
3+
import (
4+
"github.com/OpenFunction/functions-framework-go/internal/functions"
5+
)
6+
7+
type FunctionOption = functions.FunctionOption
8+
9+
var (
10+
WithFunctionPath = functions.WithFunctionPath
11+
)

functions/registers.go

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// Package functions provides a way to declaratively register functions
2+
// that can be used to handle incoming requests.
3+
package functions
4+
5+
import (
6+
"context"
7+
"log"
8+
"net/http"
9+
10+
ofctx "github.com/OpenFunction/functions-framework-go/context"
11+
"github.com/OpenFunction/functions-framework-go/internal/registry"
12+
cloudevents "github.com/cloudevents/sdk-go/v2"
13+
)
14+
15+
// HTTP registers an HTTP function that becomes the function handler served
16+
// at "/" when environment variable `FUNCTION_TARGET=name`
17+
func HTTP(name string, fn func(http.ResponseWriter, *http.Request), options ...FunctionOption) {
18+
if err := registry.Default().RegisterHTTP(name, fn, options...); err != nil {
19+
log.Fatalf("failure to register function: %s", err)
20+
}
21+
}
22+
23+
// CloudEvent registers a CloudEvent function that becomes the function handler
24+
// served at "/" when environment variable `FUNCTION_TARGET=name`
25+
func CloudEvent(name string, fn func(context.Context, cloudevents.Event) error, options ...FunctionOption) {
26+
if err := registry.Default().RegisterCloudEvent(name, fn, options...); err != nil {
27+
log.Fatalf("failure to register function: %s", err)
28+
}
29+
}
30+
31+
// OpenFunction registers a OpenFunction function that becomes the function handler
32+
// served at "/" when environment variable `FUNCTION_TARGET=name`
33+
func OpenFunction(name string, fn func(ofctx.Context, []byte) (ofctx.Out, error), options ...FunctionOption) {
34+
if err := registry.Default().RegisterOpenFunction(name, fn, options...); err != nil {
35+
log.Fatalf("failure to register function: %s", err)
36+
}
37+
}

0 commit comments

Comments
 (0)