Skip to content
Merged
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
24 changes: 24 additions & 0 deletions go/echo-cloudevents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Go Cloud Events Function

Welcome to your new Go Function! The boilerplate function code can be found in [`function.go`](function.go). This Function responds to [Cloud Events](https://cloudevents.io/).

## Development

Develop new features by adding a test to [`function_test.go`](function_test.go) for each feature, and confirm it works with `go test`.

Deploy your changes using `func deploy`. You can also invoke your function
directly by running it with `func run` and then using `curl`:

```console
curl -v -X POST -d '{"message": "hello"}' \
-H'Content-type: application/json' \
-H'Ce-id: 1' \
-H'Ce-source: cloud-event-example' \
-H'Ce-subject: Echo content' \
-H'Ce-type: MyEvent' \
-H'Ce-specversion: 1.0' \
http://localhost:8080/
```

For more, see [the complete documentation]('https://github.com/knative/func/tree/main/docs')

77 changes: 77 additions & 0 deletions go/echo-cloudevents/function.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// package function is an example of a Function implementation which
// responds to CloudEvents.
//
// This package name can be changed when using the "host" builder
// (as can the module in go.mod)
package function

import (
"context"
"fmt"

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

// MyFunction is your function's implementation.
// This structure name can be changed.
type MyFunction struct{}

// New constructs an instance of your function. It is called each time a
// new function service is created. This function must be named "New", accept
// no arguments and return an instance of a structure which exports one of the
// supported Handle signatures.
func New() *MyFunction {
return &MyFunction{}
}

// Handle a request using your function instance.
//
// One of the following method signatures needs to be implemented for your
// function to start:
//
// Handle()
// Handle() error
// Handle(context.Context)
// Handle(context.Context) error
// Handle(event.Event)
// Handle(event.Event) error
// Handle(context.Context, event.Event)
// Handle(context.Context, event.Event) error
// Handle(event.Event) *event.Event
// Handle(event.Event) (*event.Event, error)
// Handle(context.Context, event.Event) *event.Event
// Handle(context.Context, event.Event) (*event.Event, error)
//
// One of these function signatures must be implemented for your function
// to start.
func (f *MyFunction) Handle(ctx context.Context, e event.Event) (*event.Event, error) {
/*
* YOUR CODE HERE
*
* Try running `go test`. Add more tests as you code in `function_test.go`.
*/

fmt.Println("Received event")
fmt.Println(e) // echo to local output
return &e, nil // echo to caller
}

// TODO: Start
// TODO: Stop
// TODO: Alive
// TODO: Ready

// Handle is an optional method which can be used to implement simple
// functions with little or no state and minimal testing requirements. By
// instead choosing this package static function, one can forego the
// constructor and struct outlined above. The same method signatures are
// supported here as well, simply without the struct pointer receiver.
//
// func Handle(ctx context.Context, e event.Event) (*event.Event, error) {
//
// /* YOUR CODE HERE */
//
// fmt.Println("Received event")
// fmt.Println(e) // echo to local output
// return &e, nil // echo to caller
// }
32 changes: 32 additions & 0 deletions go/echo-cloudevents/function_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package function

import (
"context"
"testing"

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

// TestHandle ensures that Handle accepts a valid CloudEvent without error.
func TestHandle(t *testing.T) {
// Assemble
e := event.New()
e.SetID("id")
e.SetType("type")
e.SetSource("source")
e.SetData("text/plain", "data")

// Act
echo, err := New().Handle(context.Background(), e)
if err != nil {
t.Fatal(err)
}

// Assert
if echo == nil {
t.Errorf("received nil event") // fail on nil
}
if string(echo.Data()) != "data" {
t.Errorf("the received event expected data to be 'data', got '%s'", echo.Data())
}
}
11 changes: 11 additions & 0 deletions go/echo-cloudevents/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module function

go 1.21

require github.com/cloudevents/sdk-go/v2 v2.15.2

require (
github.com/json-iterator/go v1.1.10 // indirect
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 // indirect
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 // indirect
)
22 changes: 22 additions & 0 deletions go/echo-cloudevents/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
github.com/cloudevents/sdk-go/v2 v2.15.2 h1:54+I5xQEnI73RBhWHxbI1XJcqOFOVJN85vb41+8mHUc=
github.com/cloudevents/sdk-go/v2 v2.15.2/go.mod h1:lL7kSWAE/V8VI4Wh0jbL2v/jvqsm6tjmaQBSvxcv4uE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/json-iterator/go v1.1.10 h1:Kz6Cvnvv2wGdaG/V8yMvfkmNiXq9Ya2KUv4rouJJr68=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
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.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
5 changes: 5 additions & 0 deletions go/echo-cloudevents/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# optional. Invocation defines hints for how Functions created using this
# template can be invoked. These settings can be updated on the resultant
# Function as development progresses to ensure 'invoke' can always trigger the
# execution of a running Function instance for testing and development.
invoke: "cloudevent"
1 change: 1 addition & 0 deletions node/echo-cloudevents/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
133 changes: 133 additions & 0 deletions node/echo-cloudevents/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
# Node.js Cloud Events Function

Welcome to your new Node.js function project! The boilerplate function
code can be found in [`index.js`](./index.js). This function is meant
to respond to [Cloud Events](https://cloudevents.io/).

## Local execution

After executing `npm install`, you can run this function locally by executing
`npm run local`.

The runtime will expose three endpoints.

* `/` The endpoint for your function.
* `/health/readiness` The endpoint for a readiness health check
* `/health/liveness` The endpoint for a liveness health check

The health checks can be accessed in your browser at
[http://localhost:8080/health/readiness]() and
[http://localhost:8080/health/liveness](). You can use `curl` to `POST` an event
to the function endpoint:

```console
curl -X POST -d '{"name": "Tiger", "customerId": "0123456789"}' \
-H'Content-type: application/json' \
-H'Ce-id: 1' \
-H'Ce-source: cloud-event-example' \
-H'Ce-type: dev.knative.example' \
-H'Ce-specversion: 1.0' \
http://localhost:8080
```

The readiness and liveness endpoints use
[overload-protection](https://www.npmjs.com/package/overload-protection) and
will respond with `HTTP 503 Service Unavailable` with a `Client-Retry` header if
your function is determined to be overloaded, based on the memory usage and
event loop delay.

## The Function Interface

The `index.js` file may export a single function or a `Function`
object. The `Function` object allows developers to add lifecycle hooks for
initialization and shutdown, as well as providing a way to implement custom
health checks.

The `Function` interface is defined as:

```typescript
export interface Function {
// The initialization function, called before the server is started
// This function is optional and should be synchronous.
init?: () => any;

// The shutdown function, called after the server is stopped
// This function is optional and should be synchronous.
shutdown?: () => any;

// The liveness function, called to check if the server is alive
// This function is optional and should return 200/OK if the server is alive.
liveness?: HealthCheck;

// The readiness function, called to check if the server is ready to accept requests
// This function is optional and should return 200/OK if the server is ready.
readiness?: HealthCheck;

logLevel?: LogLevel;

// The function to handle HTTP requests
handle: CloudEventFunction | HTTPFunction;
}
```

## Handle Signature

CloudEvent functions are used in environments where the incoming HTTP request is a CloudEvent. The function signature is:

```typescript
interface CloudEventFunction {
(context: Context, event: CloudEvent): CloudEventFunctionReturn;
}
```

Where the return type is defined as:

```typescript
type CloudEventFunctionReturn = Promise<CloudEvent> | CloudEvent | HTTPFunctionReturn;
type HTTPFunctionReturn = Promise<StructuredReturn> | StructuredReturn | ResponseBody | void;
```

The function return type can be anything that a simple HTTP function can return or a CloudEvent. Whatever is returned, it will be sent back to the caller as a response.

Where the `StructuredReturn` is a JavaScript object with the following properties:

```typescript
interface StructuredReturn {
statusCode?: number;
headers?: Record<string, string>;
body?: ResponseBody;
}
```

If the function returns a `StructuredReturn` object, then the `statusCode` and `headers` properties are used to construct the HTTP response. If the `body` property is present, it is used as the response body. If the function returns `void` or `undefined`, then the response body is empty.

The `ResponseBody` is either a string, a JavaScript object, or a Buffer. JavaScript objects will be serialized as JSON. Buffers will be sent as binary data.

### Health Checks

The `Function` interface also allows for the addition of a `liveness` and `readiness` function. These functions are used to implement health checks for the function. The `liveness` function is called to check if the function is alive. The `readiness` function is called to check if the function is ready to accept requests. If either of these functions returns a non-200 status code, then the function is considered unhealthy.

A health check function is defined as:

```typescript
/**
* The HealthCheck interface describes a health check function,
* including the optional path to which it should be bound.
*/
export interface HealthCheck {
(request: Http2ServerRequest, reply: Http2ServerResponse): any;
path?: string;
}
```

By default, the health checks are bound to the `/health/liveness` and `/health/readiness` paths. You can override this by setting the `path` property on the `HealthCheck` object, or by setting the `LIVENESS_URL` and `READINESS_URL` environment variables.

## Testing

This function project includes a [unit test](./test/unit.js) and an
[integration test](./test/integration.js). All `.js` files in the test directory
are run.

```console
npm test
```
33 changes: 33 additions & 0 deletions node/echo-cloudevents/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
const { CloudEvent } = require('cloudevents');

/**
* Your CloudEvent handling function, invoked with each request.
* This example function logs its input, and responds with a CloudEvent
* which echoes the incoming event data
*
* It can be invoked with 'func invoke'
* It can be tested with 'npm test'
*
* @param {Context} context a context object.
* @param {object} context.body the request body if any
* @param {object} context.query the query string deserialzed as an object, if any
* @param {object} context.log logging object with methods for 'info', 'warn', 'error', etc.
* @param {object} context.headers the HTTP request headers
* @param {string} context.method the HTTP request method
* @param {string} context.httpVersion the HTTP protocol version
* See: https://github.com/knative/func/blob/main/docs/function-developers/nodejs.md#the-context-object
* @param {CloudEvent} event the CloudEvent
*/
const handle = async (context, event) => {
// YOUR CODE HERE
context.log.info("context", context);
context.log.info("event", event);

return new CloudEvent({
source: 'event.handler',
type: 'echo',
data: event.data
});
};

module.exports = { handle };
5 changes: 5 additions & 0 deletions node/echo-cloudevents/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# optional. Invocation defines hints for how Functions created using this
# template can be invoked. These settings can be updated on the resultant
# Function as development progresses to ensure 'invoke' can always trigger the
# execution of a running Function instance for testing and development.
invoke: "cloudevent"
Loading
Loading