OpenShift Serverless Functions is a Technology Preview feature only. Technology Preview features are not supported with Red Hat production service level agreements (SLAs) and might not be functionally complete. Red Hat does not recommend using them in production. These features provide early access to upcoming product features, enabling customers to test functionality and provide feedback during the development process.

For more information about the support scope of Red Hat Technology Preview features, see https://access.redhat.com/support/offerings/techpreview/.

After you have created a Golang function project, you can modify the template files provided to add business logic to your function.

Prerequisites

Golang function template structure

When you create a Golang function using the kn CLI, the project directory looks like a typical Go project, with the exception of an additional func.yaml configuration file.

Golang functions have few restrictions. The only requirements are that your project must be defined in a function module, and must export the function Handle().

Both http and event trigger functions have the same template structure:

Template structure
fn
├── README.md
├── func.yaml (1)
├── go.mod (2)
├── go.sum
├── handle.go
└── handle_test.go
1 The func.yaml configuration file is used to determine the image name and registry.
2 You can add any required dependencies to the go.mod file, which can include additional local Golang files. When the project is built for deployment, these dependencies are included in the resulting runtime container image.
Example of adding dependencies
$ go get gopkg.in/yaml.v2@v2.4.0

About invoking Golang functions

Golang functions are invoked by using different methods, depending on whether they are triggered by an HTTP request or a CloudEvent.

Functions triggered by an HTTP request

When an incoming HTTP request is received, your function is invoked with a standard Golang Context as the first parameter, followed by two more parameters:

You can use standard Golang techniques to access the request, and set a proper HTTP response of your function.

Example HTTP response
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) {
  // Read body
  body, err := ioutil.ReadAll(req.Body)
  defer req.Body.Close()
  if err != nil {
	http.Error(res, err.Error(), 500)
	return
  }
  // Process body and function logic
  // ...
}

Functions triggered by a cloud event

When an incoming cloud event is received, the event is invoked by the CloudEvents Golang SDK and the Event type as a parameter.

You can leverage the Golang Context as an optional parameter in the function contract, as shown in the list of supported function signatures:

Supported function signatures
Handle()
Handle() error
Handle(context.Context)
Handle(context.Context) error
Handle(cloudevents.Event)
Handle(cloudevents.Event) error
Handle(context.Context, cloudevents.Event)
Handle(context.Context, cloudevents.Event) error
Handle(cloudevents.Event) *cloudevents.Event
Handle(cloudevents.Event) (*cloudevents.Event, error)
Handle(context.Context, cloudevents.Event) *cloudevents.Event
Handle(context.Context, cloudevents.Event) (*cloudevents.Event, error)

CloudEvent trigger example

A cloud event is received which contains a JSON string in the data property:

{
  "customerId": "0123456",
  "productId": "6543210"
}

To access this data, a structure must be defined which maps properties in the cloud event data, and retrieves the data from the incoming event. The following example uses the Purchase structure:

type Purchase struct {
  CustomerId string `json:"customerId"`
  ProductId  string `json:"productId"`
}
func Handle(ctx context.Context, event cloudevents.Event) (err error) {

  purchase := &Purchase{}
  if err = event.DataAs(purchase); err != nil {
	fmt.Fprintf(os.Stderr, "failed to parse incoming CloudEvent %s\n", err)
	return
  }
  // ...
}

Alternatively, a Golang encoding/json package could be used to access the cloud event directly as JSON in the form of a bytes array:

func Handle(ctx context.Context, event cloudevents.Event) {
  bytes, err := json.Marshal(event)
  // ...
}

Golang function return values

HTTP triggered functions can set the response directly by using the Golang http.ResponseWriter.

Example HTTP response
func Handle(ctx context.Context, res http.ResponseWriter, req *http.Request) {
  // Set response
  res.Header().Add("Content-Type", "text/plain")
  res.Header().Add("Content-Length", "3")
  res.WriteHeader(200)
  _, err := fmt.Fprintf(res, "OK\n")
  if err != nil {
	fmt.Fprintf(os.Stderr, "error or response write: %v", err)
  }
}

Functions triggered by a cloud event might return nothing, error, or CloudEvent in order to push events into the Knative Eventing system. In this case, you must set a unique ID, proper Source, and a Type for the cloud event. The data can be populated from a defined structure, or from a map.

Example CloudEvent response
func Handle(ctx context.Context, event cloudevents.Event) (resp *cloudevents.Event, err error) {
  // ...
  response := cloudevents.NewEvent()
  response.SetID("example-uuid-32943bac6fea")
  response.SetSource("purchase/getter")
  response.SetType("purchase")
  // Set the data from Purchase type
  response.SetData(cloudevents.ApplicationJSON, Purchase{
	CustomerId: custId,
	ProductId:  prodId,
  })
  // OR set the data directly from map
  response.SetData(cloudevents.ApplicationJSON, map[string]string{"customerId": custId, "productId": prodId})
  // Validate the response
  resp = &response
  if err = resp.Validate(); err != nil {
	fmt.Printf("invalid event created. %v", err)
  }
  return
}

Testing Golang functions

Golang functions can be tested locally on your computer. In the default project that is created when you create a function using kn func create, there is a handle_test.go file which contains some basic tests. These tests can be extended as needed.

Procedure
  • Run the tests:

    $ go test

Next steps