# Technical Documentation: serverless/gateway

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/serverless/gateway)

### Overview (`README.md`)

**📦 Archived - This repository is archived and preserved for reference only. No updates, issues, or pull requests will be accepted. If you have questions, please reach out to our support team.**

---



[We're Hiring!](https://serverless.com/company/jobs/) • [Website](https://serverless.com/event-gateway/) • [Slack](https://join.slack.com/t/serverless-contrib/shared_invite/enQtMzgxMTkxMzIzNTU3LTY0OGZlYWI2OTI4YTliMWQ0YWNlZGZjMDhkNDAyZGQyZDYwMzYwMTlmNmVmMzMzNmI4YzAyNjg0ZjZkYTdmMzU) • [Newsletter](http://eepurl.com/b8dv4P) • [Forum](http://forum.serverless.com) • [Meetups](https://github.com/serverless/meetups) • [Twitter](https://twitter.com/goserverless)

The Event Gateway combines both API Gateway and Pub/Sub functionality into a single event-driven experience. It's
dataflow for event-driven, serverless architectures. It routes Events (data) to Functions (serverless compute).
Everything it cares about is an event! Even calling a function. It makes it easy to share events across different
systems, teams and organizations!

Use the Event Gateway right now, by running the **[Event Gateway Getting Started Application](https://github.com/serverless/event-gateway-getting-started)** with the _[Serverless Framework](https://serverless.com/framework/)_.

Features:

* **Platform agnostic** - All your cloud services are now compatible with one another: share cross-cloud functions and events with AWS Lambda, Microsoft Azure, IBM Cloud and Google Cloud Platform.
* **Send events from any cloud** - Data streams in your application become events. Centralize events from any cloud provider to get a bird’s eye view of all the data flowing through your cloud.
* **React to cross-cloud events** - You aren’t locked in to events and functions being on the same provider: Any event, on any cloud, can trigger any function. Set events and functions up like dominoes and watch them fall.
* **First-class support for CloudEvents** - Emit and react to events in [CloudEvents](https://cloudevents.io/) standard.
* **Expose events to your team** - Share events and functions to other parts of the application. Your teammates can find them and utilize them in their own services.
* **Extendable through middleware** - Perform data transforms, authorizations, serializations, and other custom computes straight from the Event Gateway.

The Event Gateway is a L7 proxy and realtime dataflow engine, intended for use with Functions-as-a-Service on AWS,
Azure, Google & IBM.

_The project is under heavy development. The APIs will continue to [change](#versioning) until we release a 1.0.0 version. It's not
yet ready for production applications._

[](https://travis-ci.org/serverless/event-gateway)



## Contents

1.  [Quick Start](#quick-start)
1.  [Running the Event Gateway](#running-the-event-gateway)
1.  [Motivation](#motivation)
1.  [Components](#components)
    1.  [Event Registry](#event-registry)
    1.  [Function Discovery](#function-discovery)
    1.  [Subscriptions](#subscriptions)
    1.  [Spaces](#spaces)
1.  [CloudEvents Support](#cloudevents-support)
1.  [SDKs](#sdks)
1.  [Versioning](#versioning)
1.  [FAQ](#faq)
1.  [Background](#background)
1.  [Community](#community)

## Reference

1.  [API](./docs/api.md)
1.  [Event Types](./docs/event-types.md)
1.  [Subscription Types](./docs/subscription-types.md)
1.  [Architecture](./docs/architecture.md)
1.  [Clustering](./docs/clustering.md)
1.  [System Events and Plugin System](./docs/system-events-and-plugin-system.md)
1.  [System Metrics](./docs/metrics.md)
1.  [Reliability Guarantees](./docs/reliability-guarantees.md)

## Quick Start

### Getting Started

Looking for an example to get started? The easiest way to use the Event Gateway is with the [`serverless-event-gateway-plugin`](https://github.com/serverless/serverless-event-gateway-plugin) with the Serverless Framework. Check out the [**Getting Started Example**](https://github.com/serverless/event-gateway-getting-started) to deploy your first service to the Event Gateway.

## Running the Event Gateway

### Hosted version

If you don't want to run the Event Gateway yourself, you can use the hosted version provided by the Serverless team. [Sign up here](https://dashboard.serverless.com)!

### via Docker

There is an [official Docker image](https://hub.docker.com/r/serverless/event-gateway/).

```bash
docker run -p 4000:4000 -p 4001:4001 serverless/event-gateway --dev
```

### Binary

On macOS or Linux run the following to download the binary:

```
curl -sfL https://raw.githubusercontent.com/serverless/event-gateway/master/install.sh | sh
```

On Windows download [binary](https://github.com/serverless/event-gateway/releases).

Then run the binary in development mode with:

```bash
$ event-gateway --dev
```

### Kubernetes

The repo contains `helm` [charts](contrib/helm/README.md) for a quick deploy to an existing cluster using native nginx Ingress. To deploy
a development cluster you can follow the [minikube](contrib/helm/MINIKUBE.md) instructions.

---

If you want more detailed information on running and developing with the Event Gateway,
please check [Running Locally](./docs/running-locally.md) and [Developing](./docs/developing.md) guides.

## Motivation

* It is cumbersome to plug things into each other. This should be easy! Why do I need to set up a queue system to
  keep track of new user registrations or failed logins?
* Introspection is terrible. There is no performant way to emit logs and metrics from a function. How do I know
  a new piece of code is actually working? How do I feed metrics to my existing monitoring system? How do I
  plug this function into to my existing analytics system?
* Using new functions is risky without the ability to incrementally deploy them.
* The AWS API Gateway is frequently cited as a performance and cost-prohibitive factor for using AWS Lambda.

## Components

### Event Registry

Event Registry is a single source of truth about events occuring in the space. Every event emitted to a space has to have event
type registered beforehand. Event Registry also provides a way to authorize incoming events. Please check
[Event Types](./docs/event-types.md) reference for more information.

### Function Discovery

Discover and call serverless functions from anything that can reach the Event Gateway. Function Discovery supports the
following function types:

* FaaS functions (AWS Lambda, Google Cloud Functions, Azure Functions, IBM Cloud Functions)
* Connectors (AWS Kinesis, AWS Kinesis Firehose, AWS SQS)
* HTTP endpoints/Webhook (e.g. POST http://example.com/function)

Function Discovery stores information about functions allowing the Event Gateway to call them as a reaction to received
event.

#### Example: Register An AWS Lambda Function

<details open>
<summary>curl example</summary>

```bash
curl --request POST \
  --url http://localhost:4001/v1/spaces/default/functions \
  --header 'content-type: application/json' \
  --data '{
    "functionId": "hello",
    "type": "awslambda",
    "provider":{
      "arn": "arn:aws:lambda:us-east-1:377024778620:function:bluegreen-dev-hello",
      "region": "us-east-1"
    }
}'
```
</details>

<details>
<summary>Node.js SDK example</summary>

```javascript
const eventGateway = new EventGateway({ url: 'http://localhost' })
eventGateway.registerFunction({
  functionId: 'sendEmail',
  type: 'awslambda',
  provider: {
    arn: 'xxx',
    region: 'us-west-2'
  }
})
```
</details>

### Subscriptions

Lightweight pub/sub system. Allows functions to asynchronously receive custom events. Instead of rewriting your
functions every time you want to send data to another place, this can be handled entirely in configuration
using the Event Gateway. This completely decouples functions from one another, reducing communication costs across
teams, eliminates effort spent redeploying functions, and allows you to easily share events across functions,
HTTP services, even different cloud providers. Functions may be registered as subscribers to a custom event.
When an event occurs, all subscribers are called asynchronously with the event as its argument.

Creating a subscription requires providing ID of registered function, an event type, an HTTP method (`POST` by default),
and a path (`/` by default). The method and path properties defines HTTP endpoint which Events API will be listening on.

Event Gateway supports two subscription types: `async` and `sync`. Please check [Subscription Types](./docs/subscription-types.md)
reference for more information.

#### Example: Subscribe to an Event

<details open>
<summary>curl example</summary>

```bash
curl --request POST \
  --url http://localhost:4001/v1/spaces/default/subscriptions \
  --header 'content-type: application/json' \
  --data '{
    "type": "async",
    "eventType": "user.created",
    "functionId": "sendEmail",
    "path": "/myteam"
  }'
```
</details>
<details>
<summary>Node.js SDK example</summary>

```javascript
const eventGateway = new EventGateway({ url: 'http://localhost' })
eventGateway.subscribe({
  type: 'async',
  eventType: 'user.created',
  functionId: 'sendEmail',
  path: '/myteam'
})
```
</details>

`sendEmail` function will be invoked for every `user.created` event to `<Events API>/myteam` endpoint.

#### Example: Emit an Event

<details open>
<summary>curl example</summary>

```bash
curl --request POST \
  --url http://localhost:4000/ \
  --header 'content-type: application/json' \
  --data '{
    "eventType": "myapp.user.created",
    "eventID": "66dfc31d-6844-42fd-b1a7-a489a49f65f3",
    "cloudEventsVersion": "0.1",
    "source": "/myapp/services/users",
    "eventTime": "1990-12-31T23:59:60Z",
    "data": { "userID": "123" },
    "contentType": "application/json"
  }'
```

</details>
<details>
<summary>Node.js SDK example</summary>

```javascript
const eventGateway = new EventGateway({ url: 'http://localhost' })
eventGateway.emit({
  "eventType": "myapp.user.created",
  "eventID": "66dfc31d-6844-42fd-b1a7-a489a49f65f3",
  "cloudEventsVersion": "0.1",
  "source": "/myapp/services/users",
  "eventTime": "1990-12-31T23:59:60Z",
  "data": { "userID": "123" },
  "contentType": "application/json"
})
```
</details>

#### Example: Subscribe to an `http.request` Event

Not all data are events that's why Event Gateway has a special, built-in [`http.request` event type](./docs/api.md#http-request-event) that enables subscribing to
raw HTTP requests.

<details open>
<summary>curl example</summary>

```bash
curl --request POST \
  --url http://localhost:4001/v1/spaces/default/subscriptions \
  --header 'content-type: application/json' \
  --data '{
    "type": "sync",
    "eventType": "http.request",
    "functionId": "listUsers",
    "method": "GET",
    "path": "/users"
  }'
```

</details>
<details>
<summary>Node.js SDK example</summary>

```javascript
const eventGateway = new EventGateway({ url: 'http://localhost' })
eventGateway.subscribe({
  type: 'sync',
  eventType: 'http.request',
  functionId: 'listUsers',
  method: 'GET',
  path: '/users'
})
```
</details>

`listUsers` function will be invoked for every HTTP GET request to `<Events API>/users` endpoint.

### Spaces

One additional concept in the Event Gateway are Spaces. Spaces provide isolation between resources. Space is a
coarse-grained sandbox in which entities (Functions and Subscriptions) can interact freely. All actions are
possible within a space: publishing, subscribing and invoking.

Space is not about access control/authentication/authorization. It's only about isolation. It doesn't enforce any
specific subscription path.

This is how Spaces fit different needs depending on use-case:

* single user - single user uses default space for registering function and creating subscriptions.
* multiple teams/departments - different teams/departments use different spaces for isolation and for hiding internal
  implementation and architecture.

Technically speaking Space is a mandatory field ("default" by default) on Function or Subscription object that user has
to provide during function registration or subscription creation. Space is a first class concept in Config API. Config
API can register function in specific space or list all functions or subscriptions from a space.

## CloudEvents Support

Event Gateway has the first-class support for [CloudEvents](https://cloudevents.io/). It means few things.

First of all, if the event emitted to the Event Gateway is in CloudEvents format, the Event Gateway is able to recognize it and trigger proper subscriptions based on event type specified in the event. Event Gateway supports both Structured Content and Binary Content modes described in [HTTP Transport Binding spec](https://github.com/cloudevents/spec/blob/master/http-transport-binding.md).

Secondly, there is a special, built-in [HTTP Request Event](./docs/api.md#http-request-event) type allowing reacting to raw HTTP requests that are not formatted according to CloudEvents spec. This event type can be especially helpful for building REST APIs.

Currently, Event Gateway supports [CloudEvents v0.1 schema](https://github.com/cloudevents/spec/blob/master/spec.md) specification.

## SDKs

* [SDK for Node.js](https://github.com/serverless/event-gateway-sdk)

## Versioning

This project uses [Semantic Versioning 2.0.0](http://semver.org/spec/v2.0.0.html). We are in initial development phase
right now (v0.X.Y). The public APIs should not be considered stable. Every breaking change will be listed in the
[release changelog](https://github.com/serverless/event-gateway/releases).

## FAQ

### What The Event Gateway is NOT

* it's not a replacement for message queues (no message ordering, currently weak durability guarantees only)
* it's not a replacement for streaming platforms (no processing capability and consumers group)
* it's not a replacement for existing service discovery solutions from the microservices world

### Event Gateway vs FaaS Providers

The Event Gateway is NOT a FaaS platform. It integrates with existing FaaS providers (AWS Lambda, Google Cloud Functions,
Azure Functions, OpenWhisk Actions). The Event Gateway enables building large serverless architectures in a unified way
across different providers.

## Background

SOA came along with a new set of challenges. In monolithic architectures, it was simple to call a built-in library or
rarely-changing external service. In SOA it involves much more network communication which [is not reliable](https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing). The main problems to solve include:

1.  Where is the service deployed? How many instances are there? Which instance is the closest to me? (service discovery)
2.  Requests to the service should be balanced between all service instances (load balancing)
3.  If a remote service call failed I want to retry it (retries)
4.  If the service instance failed I want to stop sending requests there (circuit breaking)
5.  Services are written in multiple languages, I want to communicate between them using the best language for the particular task (sidecar)
6.  Calling remote service should not require setting up new connection every time as it increases request time (persistent connections)

The following systems are solutions those problems:

* [Linkerd](https://linkerd.io/)
* [Istio](https://istio.io/)
* [Hystrix](https://github.com/Netflix/Hystrix/wiki) (library, not sidecar)
* [Finagle](https://twitter.github.io/finagle/) (library, not sidecar)

The main goal of those tools is to manage the inconveniences of network communication.

### Microservices Challenges & FaaS

The greatest benefit of serverless/FaaS is that it solves almost all of above problems:

1.  service discovery: I don't care! I have a function name, that's all I need.
2.  load balancing: I don't care! I know that there will be a function to handle my request (blue/green deployments still an issue though)
3.  retries: It's highly unusual that my request will not proceed as function instances are ephemeral and failing function is immediately replaced with a new instance. If it happens I can easily send another request. In case of failure, it's easy to understand what is the cause.
4.  circuit breaking: Functions are ephemeral and auto-scaled, low possibility of flooding/DoS & [cascading failures](https://landing.google.com/sre/book/chapters/addressing-cascading-failures.html).
5.  sidecar: calling function is as simple as calling method from cloud provider SDK.
6.  in FaaS setting up persistent connection between two functions defeats the purpose as functions instances are ephemeral.

Tools like Envoy/Linkerd solve different domain of technical problems that doesn't occur in serverless space. They have a lot of features that are unnecessary in the context of serverless computing.

### Service Discovery in FaaS = Function Discovery

Service discovery problems may be relevant to serverless architectures, especially when we have a multi-cloud setup or we want to call a serverless function from a legacy system (microservices, etc...). There is a need for some proxy that will know where the function is actually deployed and have retry logic built-in. Mapping from function name to serverless function calling metadata is a different problem from tracking the availability of a changing number of service instances. That's why there is a room for new tools that solves **function discovery** problem rather than the service discovery problem. Those problems are fundamentally different.

---

### Overview (`contrib/README.md`)

# Event Gateway Contributions

This folder contains services and contributions which complement the Event Gateway.

- Install Event Gateway on Kubernetes using [Helm](helm/README.md)
- Provision the Event Gateway on AWS ECS Fargate using [Terraform](terraform/README.md)
- Grafana Prometheus Dashboard [template](grafana/prometheus/eventgateway_all_spaces.json) for Event Gateway
- Grafana InfluxDB Dashboard [template](grafana/influxdb/eventgateway_all_spaces.json) for Event Gateway

---

### Overview (`libkv/README.md`)

# `libkv` package

This package implements `function.Service` and `subscription.Service` using `libkv` (`etcd`) as a backend store.

---

### Overview (`tests/README.md`)

# `tests` package

This package contains high-level integration tests. Unit tests are placed in the respective packages.

---

### Overview (`contrib/helm/README.md`)

# Event Gateway on Kubernetes 

This chart deploys the Event Gateway with etcd onto a Kubernetes cluster. Please note, the default instructions expect
an existing kubernetes cluster that supports ingress, such as GKE. If your environment doesn't have ingress support
set up, please follow the [minikube](MINIKUBE.md) instructions to set this up for your development environment.  

## Contents

1. [Quickstart](#quickstart)
1. [Examples](#examples)
    1. [Register a function](#register-a-function)
    1. [Query all function](#query-all-function)
    1. [Register an event type](#register-an-event-type)
    1. [Query all event types](#query-all-event-types)
    1. [Register a subscription](#register-a-subscription)
    1. [Query all subscriptions](#query-all-subscriptions)
    1. [Trigger an event](#trigger-an-event)
1. [Configuration](#configuration)
1. [Cleanup](#cleanup)

## Quickstart

Make sure you have helm installed on your machine and run `helm init` on your k8s cluster. This will set up the
`helm` and `tiller` functions required for easy deployment of config files to your cluster. You can follow
instructions [here](https://docs.helm.sh/using_helm/#quickstart) if you have not set this up previously.

Once installed, navigate to the `event-gateway/contrib/helm` folder and install the following components:

**etcd-operator**
```bash
helm install stable/etcd-operator --name ego [--namespace <namespace>]
```

**event-gateway**
```bash
helm install event-gateway --name eg [--namespace <namespace>]
```

This will install each of the `etcd-operator` and `event-gateway` into the `default` namespace in kubernetes. Please note,
this namespace has no bearing on your Event Gateway `spaces` as outlined in the [docs](https://github.com/serverless/event-gateway/blob/master/README.md). If you'd like
to install `etcd-operator` and `event-gateway` in another namespace, add the `--namespace <namespace>` option to
 both `helm install` commands above.

Next we'll need to collect the Event Gateway IP to use on the CLI. Since the Ingress was configured to receive all 
connections from the `eventgateway.minikube` host, you'll need to pass this as a header value to the request.
   
   ```bash
   export EVENT_GATEWAY_URL=$(kubectl get ingress event-gateway-ingress -o jsonpath={.status.loadBalancer.ingress[0].ip})
   curl --request GET \
        --url http://{EVENT_GATEWAY_URL}/v1/metrics \
        --header 'content-type: application/json' \
        --header 'host: eventgateway.minikube'
   ```

With your environment set up, you can now jump to the [examples](#examples) section to put your `event-gateway` to use!

## Examples

Once you've set the `EVENT_GATEWAY_URL` environment variable, you're set to start interacting with the `event-gateway`! 

**NOTE:** the events and configuration API ports are abstracted away from us via the kubernetes Ingress. The path-based 
routing will ensure the request goes to the proper service managed by the cluster. 

**DOUBLENOTE:** the examples below all assume the `default` namespace for the `event-gateway`. If you've updated or changed
this on your end, please don't forget to update the queries accordingly.

#### Register a function

Define the function registration payload, using **AWS** as an example:

```bash
cat > function.json <<EOF
{
    "functionId": "echo",
    "type": "awslambda",
    "provider": {
        "arn": "arn:aws:lambda:us-east-1:123456789012:function:event-gateway-tests-dev-echo",
        "region": "us-east-1",
        "awsAccessKeyID": "AAAAAAAAAAAAAAAAAAAA",
        "awsSecretAccessKey": "AAAAaBcDeFgHiJqLmNoPqRsTuVwXyz0123456789"
    }
}
EOF
```

Then call the registration endpoint with your json payload:

```bash
curl --request POST \
  --url http://${EVENT_GATEWAY_URL}/v1/spaces/default/functions \
  --header 'content-type: application/json' \
  --header 'host: eventgateway.minikube' \
  --data @function.json
```

And the corresponsing reply (if successful) should read something like the following:

```bash
{
	"space": "default",
	"functionId": "echo",
	"type": "awslambda",
	"provider": {
		"arn": "arn:aws:lambda:us-east-1:123456789012:function:event-gateway-tests-dev-echo",
		"region": "us-east-1",
		"awsAccessKeyId": "AAAAAAAAAAAAAAAAAAAA",
		"awsSecretAccessKey": "AAAAaBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789"
	}
}
```

**NOTE:** if you try to overwrite an existing function, you will receive an error! To replace an existing function
you will have to delete it first, then register the function once more. For example, trying to re-register the `echo`
function will yield:

```bash
curl --request POST \
   --url http://${EVENT_GATEWAY_URL}/v1/spaces/default/functions \
   --header 'content-type: application/json' \
   --header 'host: eventgateway.minikube' \
   --data @function.json

{
    "errors": [{
        "message": "Function \"echo\" already registered."
    }]
}
```

#### Query all functions

To check for registered functions, query the `config` API with the `GET` request:

```bash
curl --request GET \
  --url http://${EVENT_GATEWAY_URL}/v1/spaces/default/functions \
  --header 'content-type: application/json' \
  --header 'host: eventgateway.minikube'  | jq
```

You should see the functions list return your defined set of functions across all vendors.

```bash
{
  "functions": [
    {
      "space": "default",
      "functionId": "echo",
      "type": "awslambda",
      "provider": {
        "arn": "arn:aws:lambda:us-east-1:123456789012:function:event-gateway-tests-dev-echo",
        "region": "us-east-1",
        "awsAccessKeyId": "AAAAAAAAAAAAAAAAAAAA",
        "awsSecretAccessKey": "AAAAaBcDeFgHiJkLmNoPqRsTuVwXyZ0123456789"
      }
    }
  ]
}
```

#### Register an event type

To register an event, make sure to `POST` the event name to the `event-gateway`.

```bash
curl --request POST \
  --url http://${EVENT_GATEWAY_URL}/v1/spaces/default/eventtypes \
  --header 'content-type: application/json' \
  --header 'host: eventgateway.minikube' \
  --data '{ "name": "eventgateway.function.invoked" }'
```

The reply should look something like the following:

```bash
{ 
  "space": "default",
  "name": "eventgateway.function.invoked"
}
```

#### Query all event types

```bash
curl --request GET \
  --url http://${EVENT_GATEWAY_URL}/v1/spaces/default/eventtypes \
  --header 'content-type: application/json' \
  --header 'host: eventgateway.minikube'
```

Your registered events reply should look as follows:

```bash
{
  "eventTypes": [
    {
      "space": "default",
      "name": "eventgateway.function.invoked"
    }
  ]
}
```

#### Register a subscription

To register subscriptions to one of your registered event types, make sure to specify the `eventType` in 
the JSON POST payload.

```bash
curl --request POST \
  --url http://${EVENT_GATEWAY_URL}/v1/spaces/default/subscriptions \
  --header 'content-type: application/json' \
  --header 'host: eventgateway.minikube' \
  --data '{
    "type": "async",
    "eventType": "eventgateway.function.invoked",
    "functionId": "echo",
    "path": "/",
    "method": "POST"
}'
```

Your reply payload should include the `subscriptionId` for your new subscription:

```bash
{
  "space": "default",
  "subscriptionId": "YXN5bmMsZXZlbnRnYXRld2F5LmZ1bmN0aW9uLmludm9rZWQsZWNobywlMkYsUE9TVA",
  "type": "async",
  "eventType": "eventgateway.function.invoked",
  "functionId": "echo",
  "path": "/",
  "method": "POST"
}
```

#### Query all subscriptions

To list our your current subscrptions, you can do the following:

```bash
curl --request GET \
  --url http://${EVENT_GATEWAY_URL}/v1/spaces/default/subscriptions \
  --header 'content-type: application/json' \
  --header 'host: eventgateway.minikube'
```

The output should list each of the registered subscriptions:

```bash
{
  "subscriptions": [
    {
      "space": "default",
      "subscriptionId": "YXN5bmMsZXZlbnRnYXRld2F5LmZ1bmN0aW9uLmludm9rZWQsZWNobywlMkYsUE9TVA",
      "type": "async",
      "eventType": "eventgateway.function.invoked",
      "functionId": "echo",
      "path": "/",
      "method": "POST"
    }
  ]
}
```

#### Trigger an event

In order to trigger a registered function, call the `event-gateway` URL with the proper `functionId`. Following
our example from earlier, we've registered a `GET` function with `functionId` set to `echo`. To trigger this function,
we would:

```bash
curl --request GET \
  --url http://${EVENT_GATEWAY_URL}/echo \
  --header 'content-type: application/json' \
  --header 'host: eventgateway.minikube'
```

**NOTE**: as mentioned earlier, the `events` service is handled by the path-routing service of the kubernetes Ingress. Any path
that's prepended with `/v1` will ultimately route to the `config` service, while other paths default to the `events` service. 

## Configuration

| Parameter                   | Description                                  | Default                    |
|-----------------------------|----------------------------------------------|----------------------------|
| `images.repository`         | Event Gateway image                          | `serverless/event-gateway` |
| `images.tag`                | Event Gateway image tag                      | `0.9.0`                    |
| `replicaCount`              | Number of containers                         | `3`                        |
| `service.type`              | Type of Kubernetes service                   | `LoadBalancer`             |
| `service.annotations`       | Custom annotations for the service           | `[]`                       |
| `service.config.port`       | Config API port number                       | `4001`                     |
| `service.events.port`       | Events API port number                       | `4000`                     |
| `resources.limits.cpu`      | CPU resource limits                          | `200m`                     |
| `resources.limits.memory`   | Memory resource limits                       | `256Mi`                    |
| `resources.requests.cpu`    | CPU resource requests                        | `200m`                     |
| `resources.requests.memory` | Memory resource requests                     | `256Mi`                    |
| `command`                   | Options to pass to `event-gateway` command   | `[--db-hosts=eg-etcd-cluster-client:2379, --log-level=debug]`|
| `etcd_cluster_name`         | Name of the etcd cluster. Passed to the `--db-hosts` option as `<etcd-cluster-name>-client`  | `eg-etcd-cluster`|

The service annotations can be used to set any annotations required by your platform, for example, if
you update your values.yml with:

```
-  annotations: []
+  annotations:
+    - "service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0"
+    - "foo: bar"
```

then the service will be annotated as shown:

```
$ helm install event-gateway --debug --dry-run | grep "kind: Service" -A5
kind: Service
metadata:
  name: rafting-umbrellabird-event-gateway
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-internal: 0.0.0.0/0
    foo: bar
```

## Cleanup

When you'd like to clean up the deployments, it's easy to remove services using helm: 

```bash
helm delete --purge eg
helm delete --purge ego
```

---

### Overview (`contrib/terraform/README.md`)

# Event Gateway Terraform module

This module creates Event Gateway running on ECS Fargate with a standalone etcd cluster.

The module is an extract form the [Tectonic Installer repository](https://github.com/coreos/tectonic-installer).

## Usage

```hcl
module "event-gateway" {
  source = "github.com/serverless/event-gateway//contrib/terraform/modules/event-gateway"

  aws_region = "us-east-1"
  command_list = ["-db-hosts", "event-gateway-etcd-0.etcd:2379,event-gateway-etcd-1.etcd:2379,event-gateway-etcd-2.etcd:2379", "-log-level", "debug"]
  tags = {
    Application = "event-gateway"
  }
}

output "config_url" {
  value = "${module.event-gateway.config_url}"
}

output "events_url" {
  value = "${module.event-gateway.events_url}"
}
```

## Debugging etcd

It's possible to enable SSH access via bastion instance, by adding parameters:

```
bastion_enabled = true
ssh_key      = "eg-key"
```

Bastion IP can be distplayed by adding output:

```
output "bastion_ip" {
  value = "${module.event-gateway.bastion_ip}"
}
```

To connect to one of the etcd cluster hosts, run:

```bash
ssh -J ec2-user@<bastion_ip> core@<etcd_host_private_ip>
```

## Inputs

| Name | Description | Type | Default | Required |
|------|-------------|:----:|:-----:|:-----:|
| aws_region | AWS region for the stack | string | - | yes |
| bastion_enabled | Set to true enables SSH access to etcd nodes in the private subnet | string | `false` | no |
| command_list | List of parameters for the `event-gateway` command | list | `["-log-level", "debug"]` | no |
| config_alb_name | Config ALB name | string | `alb-config` | no |
| config_port | Port number of the Event Gateway Config API | string | `4001` | no |
| eg_image | Event Gateway docker image | string | `serverless/event-gateway:latest` | no |
| eg_vpc_name | Event Gateway VPC name | string | `eg-vpc` | no |
| etcd_base_domain | Name of the base domain for the etcd cluster | string | `etcd` | no |
| etcd_image | etcd Docker image | string | `quay.io/coreos/etcd:v3.1.8` | no |
| etcd_instance_count | Number of nodes in the etcd cluster | string | `3` | no |
| etcd_instance_type | Etcd node type | string | `t2.micro` | no |
| etcd_root_volume_iops | Number of IOPS of the etcd cluster volumes | string | `100` | no |
| etcd_root_volume_size | Size of the etcd cluster volumes (in GiB) | string | `30` | no |
| etcd_root_volume_type | Type of the etcd cluster volumes | string | `gp2` | no |
| etcd_ssh_key | (optional) Name of the preexisting SSH key | string | `` | no |
| etcd_tls_enabled | Enable TLS for the etcd cluster | string | `false` | no |
| events_alb_name | Events ALB name | string | `alb-events` | no |
| events_port | Event Gateway Events API port number | string | `4000` | no |
| fargate_cpu | Fargate instance CPU units | string | `256` | no |
| fargate_memory | Fargate instance memory | string | `512` | no |
| tags | Additional tags | map | `<map>` | no |
| task_count | Number of Event Gateway Fargate tasks | string | `3` | no |

## Outputs

| Name | Description |
|------|-------------|
| bastion_ip | Public IP of etcd bastion instance |
| config_url | Event Gateway Config API URL |
| events_url | Event Gateway Events API URL |

---

### Overview (`examples/aws-node-crm-service/README.md`)

# Using Custom Events

## Background

In a microservices architecture, you'll often have events in one service that are relevant to other services. In this example, we'll show you how to emit events from one service and subscribe to event from other services.

We'll use a common example -- the user creation flow. Your users may be created in the Users service, but the event when a user is created is important to multiple services. Perhaps you want to trigger a welcome email in your email service, post a celebratory message to Slack, or insert the user into your marketing team's CRM.

Let's focus on the last use case here. We'll emulate a CRM service that listens to events across our architecture and updates our CRM accordingly. We'll set up a subscription on the `user.created` event that's emitted from our Users service and insert the new user into our CRM.

## Deploying & Testing the CRM Service

First, let's set up our CRM service. I like to start with my function logic first, as my business logic is where I should be focusing. Take a look at `index.js`, which includes the logic:

```javascript
module.exports.handler = (event, context, cb) => {
  const user = event.data;
  console.log('Saving user to CRM: ');
  console.dir(user);

  // Add your CRM logic here
  // saveUserToCRM(user)

  cb(null, { message: 'success' })
}
```

This function is pretty basic as you'll need to implement the logic specific to your CRM. It accepts the event and creates a user object from the event data. Then it saves that user to my CRM. We'll just log the user object here rather than actually save it to a CRM.

Next, let's see how we configure that function to be triggered by our event. In the `serverless.yml`, look at the `functions` block:

```yml
functions:
  addUserToCRM:
    handler: index.handler
    events:
      - eventgateway:
          event: user.created
```

We've registered our function logic from `index.js`, and we've created an `eventgateway` subscription to the `user.created` event. Whenever that event is emitted into our Event Gateway space, this function will be triggered.

Finally, there are a few other relevant configuration sections of `serverless.yml`:

```yml
custom:
  eventgateway:
    url: <your URL> # e.g. tenant-myapp.slsgateway.com
    accessKey: <your Access Key>

plugins:
  - "@serverless/serverless-event-gateway-plugin"
```

This `serverless-event-gateway-plugin` adds additional functionality to register functions and subscriptions with the Event Gateway. The `eventgateway` section of the `custom` block configures the plugin with your application URL and access key. [Sign up for both here](https://dashboard.serverless.com).

Once you've entered your Application URL and Access Key, install your dependencies and deploy:

```bash
$ npm install
$ sls deploy
```

With your function deployed and subscription configured, it's time to test it out. You can use the plugin command `sls gateway emit` to emit a test event into your Event Gateway space. There's a sample event body in `event.json` to represent a test user.created event:

```bash
$ sls gateway emit --event "user.created" --data "$(cat event.json)"
Event emitted: user.created
Run `serverless logs -f <functionName>` to verify your subscribed function was triggered.
```

Note that we only get a message that an event was emitted. Custom events are processed asynchronously, and multiple functions can be subscribed to a custom event. This decouples producers and consumers, and it means a producer won't be able to tell who receives an event when it publishes it.

You can check your function logs to make sure everything is working properly:

```bash
$ sls logs -f addUserToCRM
START RequestId: 7b8800f5-2d34-11e8-8165-171c6f09ec0e Version: $LATEST
2018-03-21 18:20:07.365 (+00:00)	7b8800f5-2d34-11e8-8165-171c6f09ec0e	Saving user to CRM:
{ company: 'Test Corp, Inc.',
  email: 'test@testcorp.com',
  firstname: 'Test',
  lastname: 'Williams',
  username: 'test-user' }
END RequestId: 7b8800f5-2d34-11e8-8165-171c6f09ec0e
REPORT RequestId: 7b8800f5-2d34-11e8-8165-171c6f09ec0e	Duration: 5.77 ms	Billed Duration: 100 ms 	Memory Size: 1024 MB	Max Memory Used: 19 MB
```

As you can see, our function was triggered from our event and logged out the message as desired. Success! 

## Emitting Events in your Services

In the example above, we saw how to set up a consumer of events as well as how to publish test events via the command line. However, most events won't come from the command line -- they'll come from your application code, or webhooks, or database events. 

In this example, let's complete the story by emitting `user.created` events from our Users service. The easiest way to emit an event is to use the [`event-gateway-sdk`](https://github.com/serverless/event-gateway-sdk) for Node.

To use the SDK, you'll create an Event Gateway client configured for your application, then emit an event with the event name and payload to send.

The `emit.js` file shows how you could use the SDK in your application:

```javascript
// emit.js

const SDK = require('@serverless/event-gateway-sdk');
const URL = 'tenant-myapp.slsgateway.com'

const eventGateway = new SDK({
  url: URL
})

function createUser(user) {
  // Save your user to database
  // saveUserToDB(user)

  // Then emit your event
  eventGateway
    .emit({
      event: 'user.created',
      data: user
    })
    .then(() => console.log('Emitted user.created event!'))
}

const user = {
  "username": "sls-fan",
  "firstname": "Bill",
  "lastname": "Jones",
  "company": "Big Corp, Inc.",
  "email": "bjones12@bigcorp.com"
}

createUser(user);
```

The `createUser` function is similar to one you would have in your application -- save the new user to your database, then emit a `user.created` event that other services can subscribe to.

You can run this file as a test if you like. You'll need to edit the `APP` variable to match the application you used in your `serverless.yml`. Then just execute the file and check your function logs:

```bash
$ node emit.js
Emitted user.created event!

$ sls logs -f addUserToCRM
START RequestId: 5a11a5d8-2d36-11e8-ac4f-eb85ef6fadda Version: $LATEST
2018-03-21 18:33:29.796 (+00:00)	5a11a5d8-2d36-11e8-ac4f-eb85ef6fadda	Saving user to CRM:
{ company: 'Big Corp, Inc.',
  email: 'bjones12@bigcorp.com',
  firstname: 'Bill',
  lastname: 'Jones',
  username: 'sls-fan' }
END RequestId: 5a11a5d8-2d36-11e8-ac4f-eb85ef6fadda
REPORT RequestId: 5a11a5d8-2d36-11e8-ac4f-eb85ef6fadda	Duration: 1.64 ms	Billed Duration: 100 ms 	Memory Size: 1024 MB	Max Memory Used: 21 MB
```

Success!

---

### Overview (`examples/aws-node-users-service/README.md`)

# Using Event Gateway as an API Gateway

## Background

With the explosion of FaaS and Serverless architectures, applications are getting split up into smaller and smaller pieces. Rather than have a single bundle of code that handles all of your HTTP endpoints, you can create individual functions that handle a very specific task -- create a User, update my Shopping Cart, delete my Tweet.

This separation makes it easier for teams to move independently and ship more quickly, but it also presents a management problem. How do you make it easy for clients to access these hundreds of isolated functions?

Using the Event Gateway as an API Gateway solves this problem. You can have collections of functions split into services via the [Serverless Framework](https://github.com/serverless/serverless). These services can be deployed independently to the same domain in a way that's easy for clients to access.

In this example, we'll deploy an example Users service with a few different HTTP endpoints. We'll see how we can interact with those endpoints in a familiar, RESTful way.


## Deploying & Testing the Users Service

For our example Users service, let's say we want endpoints to create a new user, to get a specific user, and to delete a single user. We'll implement each of these endpoints in separate functions. Let's take a look at `index.js` where they're implemented:

```javascript
const faker = require('faker')

module.exports.get = (event, context, cb) => {
  console.log(event)

  cb(null, {
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      id: event.data.params.id,
      name: faker.name.findName(),
      email: faker.internet.email()
    })
  })
}

module.exports.post = (event, context, cb) => {
  console.log(event)
  cb(null, {
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      id: faker.random.number(),
      name: faker.name.findName(),
      email: faker.internet.email()
    })
  })
}

module.exports.delete = (event, context, cb) => {
  console.log(event)
  cb(null, {
    headers: {
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      message: `Deleted user with id ${event.data.params.id}`
    })
  })
}
```

We're exporting three functions -- `get`, `post`, and `delete` -- to handle our three endpoints. The logic is using the `faker` library to send out pretend data, but you would use some sort of data store in your actual application.

Next, let's see how we configure the functions to be triggered by our Event Gateway. In the `serverless.yml`, look at the `functions` block:

```yml
functions:
  getUser:
    handler: index.get
    events:
      - eventgateway:
          event: http
          method: GET
          path: /users/:id
  createUser:
    handler: index.post
    events:
      - eventgateway:
          event: http
          path: /users
          method: POST
  deleteUser:
    handler: index.delete
    events:
      - eventgateway:
          event: http
          path: /users/:id
          method: DELETE
```

For each function we've registered an `eventgateway` event of type `http`, indicating that it's an API Gateway event. We include the path and HTTP method to match on as well.

Finally, there are a few other relevant configuration sections of `serverless.yml`:

```yml
custom:
  eventgateway:
    url: <your URL> # e.g. tenant-myapp.slsgateway.com
    accessKey: <your Access Key>

plugins:
  - "@serverless/serverless-event-gateway-plugin"
```

This `serverless-event-gateway-plugin` adds additional functionality to register functions and subscriptions with the Event Gateway. The `eventgateway` section of the `custom` block configures the plugin with your Application URL and Access Key. You may get this by [signing up here](https://dashboard.serverless.com).

Once you've entered your Application URL and Access Key, install your dependencies and deploy:

```bash
$ npm install
$ sls deploy
```

Now let's give it a try! Using `curl` or in your browser, navigate to the `getUser` endpoint at `https://<applicationURL>/users/15`:

```bash
$ curl -X GET https://tenant-myapp.slsgateway.com/users/15 | jq "."
{
  "id": "15",
  "name": "Ariel Dach",
  "email": "Aubree22@gmail.com"
}
```

It worked! You can try your `POST` and `DELETE` endpoints as well.

You can reuse this same Application URL across multiple services using different endpoint paths to allow for agility across your team.

---

### Api (`docs/api.md`)

# API

The Event Gateway has two APIs: the Configuration API for registering functions and subscriptions, and the runtime Events API for sending events into the Event Gateway.
This document contains the API documentation for both Events and Configuration APIs. You can also find links to OpenAPI specs for these APIs.

## Contents

1.  [Events API](#events-api)
    1. [Event Definition](#event-definition)
    1. [How To Emit an Event](#how-to-emit-an-event)
    1. [HTTP Request Event](#http-request-event)
    1. [CORS](#cors)
    1. [Legacy Mode](#legacy-mode)
1.  [Configuration API](#configuration-api)
    1. [Event Types](#event-types)
        1. [Create Event Type](#create-event-type)
        1. [Update Event Type](#update-event-type)
        1. [Delete Event Type](#delete-event-type)
        1. [List Event Types](#list-event-types)
        1. [Get Event Type](#get-event-type)
    1. [Functions](#functions)
        1. [Register Function](#register-function)
        1. [Update Function](#update-function)
        1. [Delete Function](#delete-function)
        1. [List Functions](#list-functions)
        1. [Get Function](#get-function)
    1. [Subscriptions](#subscriptions)
        1. [Create Subscription](#create-subscription)
        1. [Update Subscription](#update-subscription)
        1. [Delete Subscription](#delete-subscription)
        1. [List Subscriptions](#list-subscriptions)
        1. [Get Subscription](#get-subscription)
    1. [CORS](#cors-1)
        1. [Create CORS Configuration](#create-cors-configuration)
        1. [Update CORS Configuration](#update-cors-configuration)
        1. [Delete CORS Configuration](#delete-cors-configuration)
        1. [List CORS Configurations](#list-cors-configurations)
        1. [Get CORS Configuration](#get-cors-configuration)
    1. [Prometheus Metrics](#prometheus-metrics)
    1. [Status](#status)

## Events API

The Event Gateway exposes an API for emitting events. By default Events API runs on `:4000` port.

### Event Definition

All data that passes through the Event Gateway is formatted as a CloudEvent, based on [CloudEvents v0.1 schema](https://github.com/cloudevents/spec/blob/master/spec.md).

Example:

```json
{
  "eventType": "myapp.user.created",
  "eventID": "66dfc31d-6844-42fd-b1a7-a489a49f65f3",
  "cloudEventsVersion": "0.1",
  "source": "https://serverless.com/event-gateway/#transformationVersion=0.1",
  "eventTime": "1990-12-31T23:59:60Z",
  "data": { "foo": "bar" },
  "contentType": "application/json"
}
```

### How To Emit an Event

Creating a subscription requires `path` (default: `/`), `method` (default: `POST`) and `eventType`. `path` indicates path under which you can send the event.

**Endpoint**

`POST <Events API URL>/<Subscription Path>`

**Request**

CloudEvents payload

**Response**

Status code:

* `202 Accepted` - this status code is returned if there is no [`sync` subscription](./subscription-types.md) defined. Otherwise, status code is controlled by function synchronously subscribed on this endpoint.

### HTTP Request Event

Not all data are events that's why Event Gateway has a special, built-in `http.request` event type that enables subscribing to
raw HTTP requests. It's especially helpful for building REST APIs or supporting legacy payloads. `http.request` event is a
CloudEvent created by Event Gateway where `data` field has the following structure:

* `path` - `string` - request path
* `method` - `string` - request method
* `headers` - `object` - request headers
* `host` - `string` - request host
* `query` - `object` - query parameters
* `params` - `object` - matched path parameters
* `body` - depends on `Content-Type` header - request payload

### CORS

By default cross-origin resource sharing (CORS) is disabled. CORS is configured per-method/path basis using
[CORS Configuration API](#cors-1).

Event Gateway handles preflight `OPTIONS` requests for you. You don't need to setup subscription for `OPTIONS` method
because the Event Gateway will respond with all appropriate headers.

### Legacy Mode

*Legacy mode is deprecated and will be removed in upcoming releases.*

In legacy mode, Event Gateway is able to recognize event type based on `Event` header. If the event is not formatted according to CloudEvents specification Event Gateway looks for this header and creates CloudEvent internally. In this case, whole request body is put into `data` field.

#### Event Data Type

The MIME type of the data block can be specified using the `Content-Type` header (by default it's
`application/octet-stream`). This allows the Event Gateway to understand how to deserialize the data block if it needs
to. In case of `application/json` type the Event Gateway passes JSON payload to the target functions. In any other case
the data block is base64 encoded.

## Configuration API

[OpenAPI spec](./openapi/openapi-config-api.yaml)

The Event Gateway exposes a RESTful JSON configuration API. By default Configuration API runs on `:4001` port.

### Event Types

#### Create Event Type

**Endpoint**

`POST <Configuration API URL>/v1/spaces/<space>/eventtypes`

**Request**

JSON object:

* `name` - `string` - required, event type name
* `authorizerId` - `string` - authorizer function ID
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `201 Created` on success
* `400 Bad Request` on validation error
* `409 Conflict` if event type already exists

JSON object:

* `space` - `string` - space name
* `name` - `string` - event type name
* `authorizerId` - `string` - authorizer function ID
* `metadata` - `object` - arbitrary metadata

---

#### Update Event Type

**Endpoint**

`PUT <Configuration API URL>/v1/spaces/<space>/eventtypes/<event type name>`

**Request**

JSON object:

* `authorizerId` - `string` - authorizer function ID
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `200 OK` on success
* `400 Bad Request` on validation error or if the authorizer function doesn't exist
* `404 Not Found` if event type doesn't exist

JSON object:

* `space` - `string` - space name
* `name` - `string` - event type name
* `authorizerId` - `string` - authorizer function ID
* `metadata` - `object` - arbitrary metadata

---

#### Delete Event Type

Delete event type. This operation fails if there is at least one subscription using the event type.

**Endpoint**

`DELETE <Configuration API URL>/v1/spaces/<space>/eventtypes/<event type name>`

**Response**

Status code:

* `204 No Content` on success
* `400 Bad Request` if there are subscriptions using the event type
* `404 Not Found` if event type doesn't exist

---

#### List Event Types

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/eventtypes`

**Query Parameters**

Endpoint allows filtering list of returned object with filters passed as query parameters. Currently, filters can only use metadata properties e.g. `metadata.service=usersService`.

**Response**

Status code:

* `200 OK` on success

JSON object:

* `eventTypes` - `array` of `object` - event types:
  * `space` - `string` - space name
  * `name` - `string` - event type name
  * `authorizerId` - `string` - authorizer function ID
  * `metadata` - `object` - arbitrary metadata

---

#### Get Event Type

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/eventtypes/<event type name>`

**Response**

Status code:

* `200 OK` on success
* `404 Not Found` if event type doesn't exist

JSON object:

* `space` - `string` - space name
* `name` - `string` - event type name
* `authorizerId` - `string` - authorizer function ID
* `metadata` - `object` - arbitrary metadata


### Functions

#### Register Function

**Endpoint**

`POST <Configuration API URL>/v1/spaces/<space>/functions`

**Request**

JSON object:

* `functionId` - `string` - required, function ID
* `type` - `string` - required, provider type: `awslambda` or `http`
* `provider` - `object` - required, provider specific information about a function, depends on type:
  * for AWS Lambda:
    * `arn` - `string` - required, AWS ARN identifier
    * `region` - `string` - required, region name
    * `awsAccessKeyId` - `string` - optional, AWS API key ID. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSecretAccessKey` - `string` - optional, AWS API access key. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSessionToken` - `string` - optional, AWS session token
  * for HTTP function:
    * `url` - `string` - required, the URL of an http or https remote endpoint
  * for AWS Kinesis connector:
    * `streamName` - `string` - required, AWS Kinesis Stream Name
    * `region` - `string` - required, region name
    * `awsAccessKeyId` - `string` - optional, AWS API key ID. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSecretAccessKey` - `string` - optional, AWS API access key. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSessionToken` - `string` - optional, AWS session token
  * for AWS Firehose connector:
    * `deliveryStreamName` - `string` - required, AWS Firehose Delivery Stream Name
    * `region` - `string` - required, region name
    * `awsAccessKeyId` - `string` - optional, AWS API key ID. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSecretAccessKey` - `string` - optional, AWS API access key. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSessionToken` - `string` - optional, AWS session token
  * for AWS SQS connector:
    * `queueUrl` - `string` - required, AWS SQS Queue URL
    * `region` - `string` - required, region name
    * `awsAccessKeyId` - `string` - optional, AWS API key ID. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSecretAccessKey` - `string` - optional, AWS API access key. By default credentials from the [environment](http://docs.aws.amazon.com/sdk-for-go/v1/developer-guide/configuring-sdk.html#specifying-credentials) are used.
    * `awsSessionToken` - `string` - optional, AWS session token
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `201 Created` on success
* `400 Bad Request` on validation error
* `409 Conflict` if function already exists

JSON object:

* `space` - `string` - space name
* `functionId` - `string` - function ID
* `provider` - `object` - provider specific information about a function
* `metadata` - `object` - arbitrary metadata

---

#### Update Function

**Endpoint**

`PUT <Configuration API URL>/v1/spaces/<space>/functions/<function ID>`

**Request**

JSON object:

* `type` - `string` - required, provider type: `awslambda` or `http`
* `provider` - `object` - required, provider specific information about a function, depends on type:
  * for AWS Lambda:
    * `arn` - `string` - required, AWS ARN identifier
    * `region` - `string` - required, region name
    * `awsAccessKeyId` - `string` - optional, AWS API key ID
    * `awsSecretAccessKey` - `string` - optional, AWS API key
    * `awsSessionToken` - `string` - optional, AWS session token
  * for HTTP function:
    * `url` - `string` - required, the URL of an http or https remote endpoint
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `200 OK` on success
* `400 Bad Request` on validation error
* `404 Not Found` if function doesn't exist

JSON object:

* `space` - `string` - space name
* `functionId` - `string` - function ID
* `provider` - `object` - provider specific information about a function
* `metadata` - `object` - arbitrary metadata

---

#### Delete Function

Delete all types of functions. This operation fails if the function is currently in-use by a subscription.

**Endpoint**

`DELETE <Configuration API URL>/v1/spaces/<space>/functions/<function ID>`

**Response**

Status code:

* `204 No Content` on success
* `404 Not Found` if function doesn't exist

---

#### List Functions

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/functions`

**Query Parameters**

Endpoint allows filtering list of returned object with filters passed as query parameters. Currently, filters can only use metadata properties e.g. `metadata.service=usersService`.

**Response**

Status code:

* `200 OK` on success

JSON object:

* `functions` - `array` of `object` - functions:
  * `space` - `string` - space name
  * `functionId` - `string` - function ID
  * `provider` - `object` - provider specific information about a function
  * `metadata` - `object` - arbitrary metadata

---

#### Get Function

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/functions/<function ID>`

**Response**

Status code:

* `200 OK` on success
* `404 Not Found` if function doesn't exist

JSON object:

* `space` - `string` - space name
* `functionId` - `string` - function ID
* `provider` - `object` - provider specific information about a function
* `metadata` - `object` - arbitrary metadata

### Subscriptions

#### Create Subscription

**Endpoint**

`POST <Configuration API URL>/v1/spaces/<space>/subscriptions`

**Request**

* `type` - `string` - subscription type, `sync` or `async`
* `eventType` - `string` - event type
* `functionId` - `string` - ID of function to receive events
* `path` - `string` - optional, URL path under which events (HTTP requests) are accepted, default: `/`
* `method` - `string` - optional, HTTP method that accepts requests, default: `POST`
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `201 Created` on success
* `400 Bad Request` on validation error
* `409 Conflict` if subscription already exists

JSON object:

* `space` - `string` - space name
* `subscriptionId` - `string` - subscription ID
* `type` - `string` - subscription type
* `eventType` - `string` - event type
* `functionId` - function ID
* `method` - `string` - HTTP method that accepts requests
* `path` - `string` - path that accepts requests, starts with `/`
* `metadata` - `object` - arbitrary metadata

---

#### Update Subscription

**Endpoint**

`PUT <Configuration API URL>/v1/spaces/<space>/subscriptions/<subscription ID>`

**Request**

_Note that `type`, `eventType`, `functionId`, `path`, and `method` may not be updated in an UpdateSubscription call._

* `type` - `string` - subscription type, `sync` or `async`
* `eventType` - `string` - event type
* `functionId` - `string` - ID of function to receive events
* `path` - `string` - optional, URL path under which events (HTTP requests) are accepted, default: `/`
* `method` - `string` - optional, HTTP method that accepts requests, default: `POST`
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `200 Created` on success
* `400 Bad Request` on validation error
* `404 Not Found` if subscription doesn't exist

JSON object:

* `space` - `string` - space name
* `subscriptionId` - `string` - subscription ID
* `type` - `string` - subscription type
* `eventType` - `string` - event type
* `functionId` - function ID
* `method` - `string` - HTTP method that accepts requests
* `path` - `string` - path that accepts requests, starts with `/`
* `metadata` - `object` - arbitrary metadata

---

#### Delete Subscription

**Endpoint**

`DELETE <Configuration API URL>/v1/spaces/<space>/subscriptions/<subscription ID>`

**Response**

Status code:

* `204 No Content` on success
* `404 Not Found` if subscription doesn't exist

---

#### List Subscriptions

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/subscriptions`

**Query Parameters**

Endpoint allows filtering list of returned object with filters passed as query parameters. Currently, filters can only use metadata properties e.g. `metadata.service=usersService`.

**Response**

Status code:

* `200 OK` on success

JSON object:

* `subscriptions` - `array` of `object` - subscriptions
  * `space` - `string` - space name
  * `subscriptionId` - `string` - subscription ID
  * `type` - `string` - subscription type
  * `eventType` - `string` - event type
  * `functionId` - function ID
  * `method` - `string` - HTTP method that accepts requests
  * `path` - `string` - path that accepts requests, starts with `/`
  * `metadata` - `object` - arbitrary metadata

---

#### Get Subscription

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/subscriptions/<subscription ID>`

**Response**

Status code:

* `200 OK` on success
* `404 NotFound` if subscription doesn't exist

JSON object:

* `space` - `string` - space name
* `subscriptionId` - `string` - subscription ID
* `type` - `string` - subscription type
* `eventType` - `string` - event type
* `functionId` - function ID
* `method` - `string` - HTTP method that accepts requests
* `path` - `string` - path that accepts requests, starts with `/`
* `metadata` - `object` - arbitrary metadata

### CORS

#### Create CORS Configuration

**Endpoint**

`POST <Configuration API URL>/v1/spaces/<space>/cors`

**Request**

* `method` - `string` - endpoint method
* `path` - `string` - endpoint path
* `allowedOrigins` - `array` of `string` - list of allowed origins. An origin may contain a wildcard (\*) to replace 0 or more characters (i.e.: http://\*.domain.com), default: `*`
* `allowedMethods` - `array` of `string` - list of allowed methods, default: `HEAD`, `GET`, `POST`
* `allowedHeaders` - `array` of `string` - list of allowed headers, default: `Origin`, `Accept`, `Content-Type`
* `allowCredentials` - `bool` - allow credentials, default: false
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `201 Created` on success
* `400 Bad Request` on validation error
* `409 Conflict` if CORS configuration already exists

JSON object:

* `space` - `string` - space name
* `corsId` - `string` - CORS configuration ID
* `method` - `string` - endpoint method
* `path` - `string` - endpoint path
* `allowedOrigins` - `array` of `string` - list of allowed origins
* `allowedMethods` - `array` of `string` - list of allowed methods
* `allowedHeaders` - `array` of `string` - list of allowed headers
* `allowCredentials` - `boolean` - allow credentials
* `metadata` - `object` - arbitrary metadata

---

#### Update CORS Configuration

**Endpoint**

`PUT <Configuration API URL>/v1/spaces/<space>/cors/<CORS ID>`

**Request**

_Note that `method`, and `path` may not be updated in an UpdateCORS call._

* `method` - `string` - endpoint method
* `path` - `string` - endpoint path
* `allowedOrigins` - `array` of `string` - list of allowed origins
* `allowedMethods` - `array` of `string` - list of allowed methods
* `allowedHeaders` - `array` of `string` - list of allowed headers
* `allowCredentials` - `boolean` - allow credentials
* `metadata` - `object` - arbitrary metadata

**Response**

Status code:

* `200 Created` on success
* `400 Bad Request` on validation error
* `404 Not Found` if CORS configuration doesn't exist

JSON object:

* `space` - `string` - space name
* `corsId` - `string` - CORS configuration ID
* `method` - `string` - endpoint method
* `path` - `string` - endpoint path
* `allowedOrigins` - `array` of `string` - allowed origins
* `allowedMethods` - `array` of `string` - allowed methods
* `allowedHeaders` - `array` of `string` - allowed headers
* `allowCredentials` - `boolean` - allow credentials
* `metadata` - `object` - arbitrary metadata

---

#### Delete CORS Configuration

**Endpoint**

`DELETE <Configuration API URL>/v1/spaces/<space>/cors/<CORS ID>`

**Response**

Status code:

* `204 No Content` on success
* `404 Not Found` if CORS configuration doesn't exist

---

#### List CORS Configurations

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/cors`

**Query Parameters**

Endpoint allows filtering list of returned object with filters passed as query parameters. Currently, filters can only use metadata properties e.g. `metadata.service=usersService`.

**Response**

Status code:

* `200 OK` on success

JSON object:

* `cors` - `array` of `object` - CORS configurations
  * `space` - `string` - space name
  * `corsId` - `string` - CORS configuration ID
  * `method` - `string` - endpoint method
  * `path` - `string` - endpoint path
  * `allowedOrigins` - `array` of `string` - allowed origins
  * `allowedMethods` - `array` of `string` - allowed methods
  * `allowedHeaders` - `array` of `string` - allowed headers
  * `allowCredentials` - `boolean` - allow credentials
  * `metadata` - `object` - arbitrary metadata

---

#### Get CORS Configuration

**Endpoint**

`GET <Configuration API URL>/v1/spaces/<space>/cors/<CORS ID>`

**Response**

Status code:

* `200 OK` on success
* `404 NotFound` if CORS configuration doesn't exist

JSON object:

* `space` - `string` - space name
* `corsId` - `string` - CORS configuration ID
* `method` - `string` - endpoint method
* `path` - `string` - endpoint path
* `allowedOrigins` - `array` of `string` - allowed origins
* `allowedMethods` - `array` of `string` - allowed methods
* `allowedHeaders` - `array` of `string` - allowed headers
* `allowCredentials` - `boolean` - allow credentials
* `metadata` - `object` - arbitrary metadata

### Prometheus Metrics

Endpoint exposing [Prometheus metrics](./prometheus-metrics.md).

**Endpoint**

`GET <Configuration API URL>/metrics`

### Status

Dummy endpoint (always returning `200 OK` status code) for checking if the event gateway instance is running.

**Endpoint**

`GET <Configuration API URL>/v1/status`

---

### Architecture (`docs/architecture.md`)

# Architecture

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Clustering (`docs/clustering.md`)

# Clustering

The Event Gateway is a horizontally scalable system. It can be scaled by adding instances to the cluster. A cluster is
a group of instances sharing the same database. A cluster can be created in one cloud region, across multiple regions,
across multiple cloud provider or even in both cloud and on-premise data centers.

The Event Gateway instances use a strongly consistent, subscribable DB (initially [etcd](https://coreos.com/etcd),
with support for Consul, and Zookeeper planned) to store and broadcast configuration. The instances locally
cache configuration used to drive low-latency event routing. The instance local cache is built asynchronously based on
events from backing DB.

The Event Gateway is a stateless service and there is no direct communication between different instances. All
configuration data is shared using backing DB. If the instance from region 1 needs to call a function from region 2 the
invocation is not routed through the instance in region 2. The instance from region 1 invokes the function from region 2
directly.

```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Developing (`docs/developing.md`)

# Developing

## Clone GitHub repo

```
mkdir -p $GOPATH/src/github.com/serverless
cd $GOPATH/src/github.com/serverless
git clone git@github.com:serverless/event-gateway.git
cd event-gateway
```

## Install [`dep`](https://github.com/golang/dep) package manager

On macOS you can install or upgrade to the latest released version with Homebrew:

```sh
$ brew install dep
$ brew upgrade dep
```

Or you can install via `go get`:

```sh
go get -u github.com/golang/dep/cmd/dep
```

## Install dependencies

```sh
go get -u github.com/hashicorp/go-plugin
go get -u github.com/hashicorp/go-hclog
go get -u golang.org/x/net/{context,http2,trace}
dep ensure
```

## Run locally

```sh
go run cmd/event-gateway/main.go -dev
```

---

### Event Types (`docs/event-types.md`)

# Event Types

Event Registry is a single source of truth about events occuring in the space. Every event emitted to a space has to have event
type registered beforehand. Event type can be registered using [Event Types Configuration API](./api.md#event-types).

## Authorization

Event Type can define authorizer function that will be called before calling a subscribed function. Authorizer function is a function registered in Event Gateway beforehand.

### Invocation Payload

The authorizer function is invoked with a special payload. The function has access to the whole request object because different parts of the request can be required for running authorization logic (e.g. API key can be stored in different headers or query params). The invocation payload is a JSON object with the following structure:

- `event` - `object` - event received and parsed by EG
- `request` - `object` - original HTTP request to the EG, this field is exactly the same as HTTP event, including body, which in case of CloudEvent will be exactly the same as event field

### Invocation Result

The authorize function is expected to return authorization response JSON object with the following structure:

- `authorization` - `object` - object containing authorization data, required if authorization is successful. Fields:
  - `principalId` - `string` - required if authorization is successful, the principal user identification associated with the token sent in the request
  - `context` - `map[string]string` - arbitrary data that will be accessible by the downstream function
- `error` - `object` - error information, required if authorization is unsuccessful. Fields:
  - `message` - `string` - authorization error message

If the authorization is successful `error` field has to be null or not defined otherwise Event Gateway treats authorization process as unsuccessful and `403 Forbidden` error is returned to the client assuming that there was sync subscription defined. `authorization` object is attached to CloudEvent Extensions field under `eventgateway.authorization` key.

If Event Gateway will not be able to parse the response from authorizer function or there will be error during invocation authorization process is considered as unsuccessful.

---

### Metrics (`docs/metrics.md`)

# Collecting and Viewing Event Gateway Metrics

This guide details how to collect and analyze metrics from the Event Gateway.

### Introduction

The Event Gateway exposes a number of [Prometheus](https://prometheus.io/)-based metrics + counters via the configuration
API to help monitor the health of the gateway.

## Contents

1. [Collecting Metrics](#collecting-metrics)
1. [Visualizing Data](#visualizing-data)
   1. [Prometheus](#prometheus)
   1. [InfluxDB](#influxdb)
1. [List of Metrics](#list-of-metrics)
   1. [Events API](#events-api)
   1. [Configuration API](#configuration-api)

### Collecting Metrics

Event Gateway metrics are exposed via a Prometheus text-based metric [exporter](https://prometheus.io/docs/instrumenting/exposition_formats/#text-format-example) and as such are
queryable by a variety of modern toolsets. While you can scrape data directly from your prometheus database, the following (non-exhaustive) list of sources can also
be configured to scrape Event Gateway metrics:

- [Telegraf](https://github.com/influxdata/telegraf/tree/master/plugins/inputs/prometheus)
- [Datadog](https://www.datadoghq.com/blog/monitor-prometheus-metrics/)
- [AppOptics](https://docs.appoptics.com/kb/host_infrastructure/integrations/prometheus/)
- [Sensu](https://blog.sensuapp.org/the-sensu-prometheus-collector-972c441d45e)
- [Splunk](https://splunkbase.splunk.com/app/4077/#/details)
- and others ...

### Visualizing Data

Included in the [contrib](../contrib/grafana) folder are identical dashboards to visualize Event Gateway metrics with different
data sources. Depending on your setup you'll find a [Prometheus](../contrib/grafana/prometheus) and [InfluxDB](../contrib/grafana/influxdb) version
of the dashboard.

Importing a Grafana [dashboard](http://docs.grafana.org/reference/export_import/#export-and-import) is relatively straightforward provided your datasources are configured
properly within your Grafana instance. Each of our dashboards lists respective datasources as a template variable, assuming the default datasource is set
to the type for your datasource; in other words, if you import the Prometheus dashboard the template will expect your default data source to be set
to Prometheus.

If your default datasource is not either of the Prometheus or InfluxDB sources, fear not! The template variable is set as the datasource for each
panel within the dashboard, so updating the datasource is as easy as choosing another option from the template dropdown.

**NOTE:** There are two dashboards included for each datasource: an aggregated dashboard for all spaces in a given Event Gateway deployment, and a second
dashboard that can drill down to individual spaces.

#### Prometheus

You can find the source for the Prometheus dashboard [here](../contrib/grafana/prometheus).

#### InfluxDB

You can find the source for the InfluxDB dashboard [here](../contrib/grafana/influxdb).

### List of Metrics

Both Events and Configuration API exposes Prometheus metrics. The metrics are accesible via `/v1/metrics` endpoint of Configuration API. The table below outlines the specific metrics available to query from the endpoint:

#### Events API

| Metric                                          | Type      | Labels          | Description                                                                                                             |
| ----------------------------------------------- | --------- | --------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `eventgateway_events_received_total`            | counter   | `space`, `type` | total of events received                                                                                                |
| `eventgateway_events_processed_total`           | counter   | `space`, `type` | total of processed events                                                                                               |
| `eventgateway_events_dropped_total`             | counter   | `space`, `type` | total of events dropped due to insufficient processing power                                                            |
| `eventgateway_events_backlog`                   | gauge     |                 | gauge of asynchronous events count waiting to be processed                                                              |
| `eventgateway_events_custom_processing_seconds` | histogram |                 | bucketed histogram of processing duration of an event<br> (from receiving the async custom event to calling a function) |

**Labels**

- `space` - space name
- `type` - event type name

#### Configuration API

| Metric                                         | Type      | Labels                           | Description                                                   |
| ---------------------------------------------- | --------- | -------------------------------- | ------------------------------------------------------------- |
| `eventgateway_eventtypes_total`                | gauge     | `space`                          | gauge of registered event types count                         |
| `eventgateway_functions_total`                 | gauge     | `space`                          | gauge of registered functions count                           |
| `eventgateway_subscriptions_total`             | gauge     | `space`                          | gauge of created subscriptions count                          |
| `eventgateway_config_requests_total`           | counter   | `space`, `resource`, `operation` | total of Config API requests                                  |
| `eventgateway_config_request_duration_seconds` | histogram |                                  | bucketed histogram of request duration of Config API requests |

**Labels**

- `space` - space name
- `resource` - Configuration API resource, possible values: `eventtype`, `function` or `subscription`
- `operation` - Configuration API operation, possible values: `create`, `get`, `delete`, `list`, `update`

---

### Reliability Guarantees (`docs/reliability-guarantees.md`)

# Reliability Guarantees

## Events are not durable

The event received by Event Gateway is stored only in memory, it's not persisted to disk before processing. This means that in case of hardware failure or software crash the event may not be delivered to the subscriber. For a synchronous subscription (`http`) it can manifest as error message returned to the requester. For asynchronous custom event with multiple subscribers it means that the event may not be delivered to all of the subscribers.

## Events are delivered _at most once_

Event Gateway attempts delivery fulfillment for an event only once and consequently any event received successfully by the Event Gateway is guaranteed to be received by the subscriber _at most once_. That said, the nature of Event Gateway provider implementation could result in retries under specific circumstances, but these should not cause delivering the same event multiple times. For example, Providers for AWS Services that use the AWS SDK are subject to auto retry logic that's built into the SDK ([AWS documentation on API retries](https://docs.aws.amazon.com/general/latest/gr/api-retries.html)).

AWS Lambda provider uses `RequestResponse` invocation type which means that retry logic for asynchronous AWS events doesn't apply here. Among others it means, that failed deliveries of custom events are not sent to DLQ. Please find more information in [Understanding Retry Behavior](https://docs.aws.amazon.com/lambda/latest/dg/retries-on-errors.html), "Synchronous invocation" section.

---

### Running Locally (`docs/running-locally.md`)

# Running Locally

## Installation (via GitHub releases)

On macOS or Linux run the following:

```
curl -sfL https://raw.githubusercontent.com/serverless/event-gateway/master/install.sh | sh
```

On Windows download [binary](https://github.com/serverless/event-gateway/releases).

## Installation (via Docker)

There is a [official Docker image](https://hub.docker.com/r/serverless/event-gateway/).

```
docker pull serverless/event-gateway
```

## Running Locally

Run `event-gateway` in `dev` mode:

```
event-gateway -dev
```

## Running in Docker

```
docker run -p 4000:4000 -p 4001:4001 serverless/event-gateway -dev
```

**Pass in your AWS credentials**

Mounts the `~/.aws` folder from the host to the `/home/.aws` folder inside the container. Event Gateway can then read the credentials from within the container.
```
docker run -p 4000:4000 -p 4001:4001 -e "HOME=/home" -v ~/.aws:/home/.aws serverless/event-gateway -dev
```

**Preserve state of etcd**

While testing if you restart the container running Event Gateway and want to preserve the data in etcd, you can specify a data dir with the `-embed-data-dir "/home/data"` flag specifying a destination folder. Then you can mount the folder `~/.event-gateway/data` from your host into the container at `/home/data`. Event Gateway will read the data from there.

```
docker run -p 4000:4000 -p 4001:4001 -v ~/.event-gateway/data:/home/data serverless/event-gateway -embed-data-dir "/home/data" -dev
```

## Operations

* [Register a Function](#register-a-function)
* [Subscribe to an Event](#subscribe-to-an-event)
* [Emit an Event](#emit-an-event)

### Register a Function

Register an AWS Lambda function in the Function Discovery.

```
curl --request POST \
  --url http://127.0.0.1:4001/v1/spaces/default/functions \
  --header 'content-type: application/json' \
  --data '{"functionId": "hello", "provider":{"type": "awslambda", "arn": "<Function AWS ARN>", "region": "<Region>", "accessKeyId": "<Access Key ID>", "secretAccessKey": "<Secret Access Key>"}}'
```

### Subscribe to an Event

Once the function is register you can subscribe it to you custom event.

```
curl --request POST \
  --url http://127.0.0.1:4001/v1/spaces/default/subscriptions \
  --header 'content-type: application/json' \
  --data '{"functionId": "hello", "event": "pageVisited"}'
```

### Emit an Event

An event can be emitted using [Events API](#events-api).

```
curl --request POST \
  --url http://127.0.0.1:4000/ \
  --header 'content-type: application/json' \
  --header 'event: pageVisited' \
  --data '{"userId": "123"}'
```

After emitting the event subscribed function is called asynchronously.

---

### Storage (`docs/storage.md`)

# Implications of Storage Semantics

For configuration, this system has this desired behavior:

1. something wants to configure something
1. everything that needs to know about that configuration reacts to it

We benefit from strongly consistent atomic writes because we want all configuration to
reach everything that needs to be configured. Weakly consistent systems
may not observe all updates, and we will end up with broken or half-working
features. We also benefit from efficient notification primitives for
keeping the overall system performant, such as watches. Without watches,
we have to continuously scan ranges of keys in the database until we
find new data, which is enormously expensive, slow, and hard to scale.

Applied to security, where we want to revoke a user's privileges completely,
if we are using a database without the ability to perform atomic writes,
even if it is strongly consistent,
then one component may try updating a user's account by first reading
their data, modifying it locally, then trying to update it in the database.
If we don't have atomic updates, the ordering could end up being this:

```
node A reads user Bob's data
node A locally updates Bob's data to add admin privileges
node B deletes Bob's data to remove all privileges
node B receives "write successful" from database
node A writes Bob's new data to the system with admin privileges
Node B then tells the system that was trying to delete Bob "Bob sucessfully deleted"
Bob steals all of the company's secrets
```

This is not desirable. However, some strongly consistent databases let us
perform compare-and-swap operations that let us atomically update Bob, without
losing any intermediate updates.

```
node A reads user Bob's data
node A locally updates Bob's data to add admin privileges
node B deletes Bob's data to remove all privileges
node B receives "write successful" from database
node A tries to do "update Bob unless changed since read" which fails
Node B then tells the system that was trying to delete Bob "Bob sucessfully deleted"
Bob is locked out and steals nothing
```

Atomic updates generally significantly reduce the cognitive burden for
building a system which must store and react to state changes in different
components. Watches are another significant help for this, as they
notify interested systems in relevant changes.

Watches are how we apply the event-driven model to our database.
When interesting changes happen, interested parties react to them.
This decouples the emitter from the reactor, greatly simplifying
interactions in the system. Without watches, we need to
have some way of detecting changes in a database. If we can scan through
all keys, we can do an O(N) traversal of the entire database, which
does not scale very high, but could work alright. If we don't have
the ability to scan through all data, we may actually have no way of
learning what changes are unless there is a top-level key that is set
that holds everything. This does not scale beyond a couple kilobytes.

# Storage Options

## stateless gateway services backed by zk/etcd/consul cluster, abstracted by docker/libkv

pros

* flexible support for the three most popular configuration databases
* very clear operational characteristics, does not confuse anyone about what's happening
* the gateway is fully stateless, easy to autoscale, clear semantics for operators
* lowest amount of work for us
* write once, run anywhere in the same way
* allows users to easily take advantage of existing database skills, tools, backup tools, monitoring, etc...

cons

* requires users to run their own cluster (they are already running things though, so this isn't a high marginal cost)

## embedded etcd in gateway, cluster of 3 or 5 active as "leaders", rest of gateways are stateless

pros

* single binary

cons

* unclear operational characteristics, when the cluster gets wedged it may be extremely hard to debug
* harder than running your own cluster, because you can't reuse existing database skills
* very hard on operators when things go wrong
* harder on operators to get things safely set up
* still effectively have a separate cluster if you want to autoscale without accidentally losing leaders
* creating a reliable "autopilot" etcd deployment system took tyler 4 months in the past

## embedded etcd in gateway, single node configured as "leader", rest of gateways are stateless

pros

* easy to set up
* can be used in combination with a separate etcd cluster for the best of both worlds
* does not require setting up a cluster to try out, demo, or run in small deployments
* clear operational characteristics, everyone knows it's not reliable

cons

* single point of failure on the single leader node

## dynamo + spanner + cosmosdb + on prem other databases

pros

* single binary
* easy deployment for users on cloud providers
* one fewer piece to keep running

cons

* dramatically increases the complexity for building the system for us
* we need to target multiple consistency models (hard+++)
* need to target systems without watch semantics (hard+++)
* need to target systems without atomic write semantics (hard+++)
* we need to spend much more effort on testing
* we need to spend much more effort on fixing bugs
* we need to spend much more effort on writing monitoring code
* we need to master all of these databases in order to program against them

## eventually consistent gossip-based config sharing with CRDTs

pros

* single binary
* no external dependencies, even on databases

cons

* if all instances go away, the configuration is gone
* we're basically building our own distributed database (hard+++++)
* need to create backup tooling
* unclear operational characteristics for people
* huge extra effort needs to go into correctness testing

## just config files, reloaded on file change

pros

* single binary
* flexible
* can be used with any of the other approaches to decouple functionality

cons

* users don't get an API for the gateway

# Recommendation

Keep the system stateless to make it easy to operate. Store state
in a database that supports atomic updates and watches, such as
etcd, zookeeper, or consul. Use the docker/libkv library to
support all 3 backing databases. This lets us treat every
deployment environment the same way, regardless of whether
it's in a cloud provider or on-prem. This combination of
operational clarity and database semantics will significantly
lower the amount of effort we need to put into engineering over time.

Purely for demo and trial purposes, allow a gateway to be started
with an `--embedded-master` flag which will start an embedded etcd instance
that other gateways can use as a shared backing store. This allows people to
try out the system without standing up an etcd cluster first.

The main downside, having to run a separate cluster in production, is probably
not a big deal for people who are interested in running this themselves.

Assumptions relating to this cost, and it not seeming very high:

1. most users will fall into these camps:
  * small/medium orgs interested in trying out locally but using the SaaS offering
  * orgs with more significant engineering resources who already run zk/etcd/consul
  * orgs with more significant engineering resources who are not averse to running zk/etcd/consul
  * orgs who are comfortable paying compose.com $30/mo for hosted etcd
1. users who are not interested in paying for SaaS but still want to run the gateway
   themselves WITHOUT running a database are unlikely to be very upset when they occasionally
   need to reconfigure the gateway when the single master goes away.

---

### Subscription Types (`docs/subscription-types.md`)

# Subscription Types

Event Gateway supports two subscription types: `async` and `sync`.

## `async` subscription

`async` subscription implements lightweight pub/sub system. There can be many async subscriptions listening to the same event type, on the same path and HTTP method. The function is asynchronously invoked by the Event Gateway.

## `sync` subscription

In case of `sync` subscription invoked function can control the HTTP response returned by the Event Gateway. Because of that, there can be only one `sync` subscription for a path, HTTP method, and event type tuple.

Status code, headers and response body can be controlled by returning JSON object with the following fields:

* `statusCode` - `int` - response status code, default: 200
* `headers` - `object` - response headers
* `body` - `string` - response body. Currently, the Event Gateway supports only string responses.

If the function invocation failed or the backing function didn't return JSON object in the above format Event Gateway returns `500 Internal Server Error`.

**Path parameters**

The Event Gateway allows creating `sync` subscription with parameterized paths. Every path segment prefixed with `:` is
treated as a parameter, e.g. `/users/:id`.

The Event Gateway prevents from creating subscriptions in following conflicting situations:

* registering static path when there is parameterized path registered already (`/users/:id` vs. `/users/foo`)
* registering parameterized path with different parameter name (`/users/:id` vs. `/users/:name`)

Key and value of matched parameters are passed to a function in an [HTTP Request Event](./api.md#http-request-event) under `params` field.

**Wildcard parameters**

Special type of path parameter is wildcard parameter. It's a path segment prefixed with `*`. Wildcard parameter can only
be specified at the end of the path and will match every character till the end of the path. For examples
parameter `/users/*userpath` for request path `/users/group1/user1` will match `group1/user1` as a `userpath` parameter.

---

### System Events And Plugin System (`docs/system-events-and-plugin-system.md`)

# System Events and Plugin System

## System Events

System Events are special type of events emitted by the Event Gateway instance internally. They are emitted on each stage
of event processing flow starting from receiving event to function invocation end. Those events are:

* `eventgateway.event.received` - the event is emitted when an event was received by Events API. Data fields:
  * `event` - event payload
  * `path` - Events API path
  * `headers` - HTTP request headers
* `eventgateway.function.invoking` - the event emitted before invoking a function. Data fields:
  * `space` - space name
  * `event` - event payload
  * `functionId` - registered function ID
* `eventgateway.function.invoked` - the event emitted after successful function invocation. Data fields:
  * `space` - space name
  * `event` - event payload
  * `functionId` - registered function ID
  * `result` - function response
* `eventgateway.function.invocationFailed` - the event emitted after failed function invocation. Data fields:
  * `space` - space name
  * `event` - event payload
  * `functionId` - registered function ID
  * `error` - invocation error

## Plugin System

The Event Gateway is built with extensibility in mind. Built-in plugin system allows reacting on system events and
manipulate how an event is processed through the Event Gateway.

_Current implementation supports plugins written only in Golang. We plan to support other languages in the future._

Plugin system is based on [go-plugin](https://github.com/hashicorp/go-plugin). A plugin needs to implement the following
interface:

```go
type Reacter interface {
	Subscriptions() []Subscription
	React(event event.Event) error
}
```

`Subscription` model indicates the event that plugin subscribes to and the subscription type. A subscription can be either
sync or async. Sync (blocking) subscription means that in case of error returned from `React` method the event won't be
further processed by the Event Gateway.

`React` method is called for every system event that plugin subscribed to.

For more details, see [the example plugin](../plugin/example/reacter).

---

### Openapi Openapi Config Api.Yaml (`docs/openapi/openapi-config-api.yaml`)

openapi: "3.0.1"

info:
  title: Event Gateway - Configuration API
  version: "0.8.1"
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
  contact:
    name: Serverless, Inc.
    url: http://www.serverless.com

servers:
- url: http://localhost:4001/{basePath}
  description: Locally running Event Gateway
  variables:
    basePath:
      default: v1

tags:
- name: "event type"
  description: "Operations about event types"
- name: "function"
  description: "Operations about functions"
- name: "subscription"
  description: "Operations about subscriptions"
- name: "cors"
  description: "Operations about CORS"

paths:
  /spaces/{spaceName}/eventtypes:
    summary: "Operations about event types"
    get:
      summary: "List event types"
      tags:
      - "event type"
      operationId: "ListEventTypes"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/Filters"
      responses:
        200:
          description: "event types returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventTypes"
        500:
          $ref: '#/components/responses/Error'
    post:
      summary: "Create event type"
      tags:
      - "event type"
      operationId: "CreateEventType"
      parameters:
      - $ref: "#/components/parameters/Space"
      requestBody:
        $ref: "#/components/requestBodies/CreateEventType"
      responses:
        201:
          description: "event type created"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventType"
        400:
          $ref: '#/components/responses/ValidationError'
        500:
          $ref: '#/components/responses/Error'

  /spaces/{spaceName}/eventtypes/{eventTypeName}:
    summary: "Operations about single event type"
    get:
      summary: "Get event type"
      tags:
      - "event type"
      operationId: "GetEventType"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/EventTypeName"
      responses:
        200:
          description: "event type returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventType"
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    put:
      summary: "Update event type"
      tags:
      - "event type"
      operationId: "UpdateEventType"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/EventTypeName"
      requestBody:
        $ref: "#/components/requestBodies/UpdateEventType"
      responses:
        200:
          description: "event type updated"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EventType"
        400:
          $ref: '#/components/responses/ValidationError'
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    delete:
      summary: "Delete event type"
      tags:
      - "event type"
      operationId: "DeleteEventType"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/EventTypeName"
      responses:
        204:
          description: "event type deleted"
        400:
          $ref: '#/components/responses/EventTypeHasSubscriptionsError'
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'

  /spaces/{spaceName}/functions:
    summary: "Operations about functions"
    get:
      summary: "List functions"
      tags:
      - "function"
      operationId: "ListFunctions"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/Filters"
      responses:
        200:
          description: "functions returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Functions"
        500:
          $ref: '#/components/responses/Error'
    post:
      summary: "Register function"
      tags:
      - "function"
      operationId: "CreateFunction"
      parameters:
      - $ref: "#/components/parameters/Space"
      requestBody:
        $ref: "#/components/requestBodies/CreateFunction"
      responses:
        201:
          description: "function registered"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Function"
        400:
          $ref: '#/components/responses/ValidationError'
        500:
          $ref: '#/components/responses/Error'

  /spaces/{spaceName}/functions/{functionId}:
    summary: "Operations about single function"
    get:
      summary: "Get function"
      tags:
      - "function"
      operationId: "GetFunction"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/FunctionID"
      responses:
        200:
          description: "function returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Function"
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    put:
      summary: "Update function"
      tags:
      - "function"
      operationId: "UpdateFunction"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/FunctionID"
      requestBody:
        $ref: "#/components/requestBodies/UpdateFunction"
      responses:
        200:
          description: "function updated"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Function"
        400:
          $ref: '#/components/responses/ValidationError'
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    delete:
      summary: "Delete function"
      tags:
      - "function"
      operationId: "DeleteFunction"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/FunctionID"
      responses:
        204:
          description: "function deleted"
        400:
          $ref: '#/components/responses/FunctionHasSubscriptionsError'
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'

  /spaces/{spaceName}/subscriptions:
    summary: "Operations about subscriptions"
    get:
      summary: "List subscriptions"
      tags:
      - "subscription"
      operationId: "ListSubscriptions"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/Filters"
      responses:
        200:
          description: "subscriptions returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Subscriptions"
        500:
          $ref: '#/components/responses/Error'
    post:
      summary: "Create subscription"
      tags:
      - "subscription"
      operationId: "CreateSubscription"
      parameters:
      - $ref: "#/components/parameters/Space"
      requestBody:
        $ref: "#/components/requestBodies/CreateSubscription"
      responses:
        201:
          description: "subscription created"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Subscription"
        400:
          $ref: '#/components/responses/ValidationError'
        500:
          $ref: '#/components/responses/Error'

  /spaces/{spaceName}/subscription/{subscriptionId}:
    summary: "Operations about single subscription"
    get:
      summary: "Get subscription"
      tags:
      - "subscription"
      operationId: "GetSubscription"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/SubscriptionID"
      responses:
        200:
          description: "subscription returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Subscription"
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    put:
      summary: "Update subscription"
      tags:
      - "subscription"
      operationId: "UpdateSubscription"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/SubscriptionID"
      requestBody:
        $ref: "#/components/requestBodies/UpdateSubscription"
      responses:
        200:
          description: "subscription updated"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Subscription"
        400:
          $ref: '#/components/responses/ValidationError'
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    delete:
      summary: "Delete subscription"
      tags:
      - "subscription"
      operationId: "DeleteSubscription"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/SubscriptionID"
      responses:
        204:
          description: "subscription deleted"
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'

  /spaces/{spaceName}/cors:
    summary: "Operations about CORS configuration"
    get:
      summary: "List CORS configurations"
      tags:
      - "cors"
      operationId: "ListCORS"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/Filters"
      responses:
        200:
          description: "CORS configurations returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CORSes"
        500:
          $ref: '#/components/responses/Error'
    post:
      summary: "Create CORS configuration"
      tags:
      - "cors"
      operationId: "CreateCORS"
      parameters:
      - $ref: "#/components/parameters/Space"
      requestBody:
        $ref: "#/components/requestBodies/CreateCORS"
      responses:
        201:
          description: "CORS configuration created"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CORS"
        400:
          $ref: '#/components/responses/ValidationError'
        500:
          $ref: '#/components/responses/Error'

  /spaces/{spaceName}/cors/{corsId}:
    summary: "Operations about single CORS configuration"
    get:
      summary: "Get CORS configuration"
      tags:
      - "cors"
      operationId: "GetCORS"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/CORSID"
      responses:
        200:
          description: "CORS configuration returned"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CORS"
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    put:
      summary: "Update CORS configuration"
      tags:
      - "cors"
      operationId: "UpdateCORS"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/CORSID"
      requestBody:
        $ref: "#/components/requestBodies/UpdateCORS"
      responses:
        200:
          description: "CORS configuration updated"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CORS"
        400:
          $ref: '#/components/responses/ValidationError'
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'
    delete:
      summary: "Delete CORS configuration"
      tags:
      - "cors"
      operationId: "DeleteCORS"
      parameters:
      - $ref: "#/components/parameters/Space"
      - $ref: "#/components/parameters/CORSID"
      responses:
        204:
          description: "CORS configuration deleted"
        404:
          $ref: '#/components/responses/NotFoundError'
        500:
          $ref: '#/components/responses/Error'

components:
  schemas:
    SpaceName:
      type: string
    FunctionID:
      type: string
    SubscriptionID:
      type: string
    CORSID:
      type: string
    SubscriptionType:
      type: string
      enum:
      - async
      - sync
    ProviderType:
      type: string
      description: "function provider"
      enum:
      - awsfirehose
      - awskinesis
      - awslambda
      - awssqs
      - http
    Provider:
      type: object
      description: "function provider configuration"
      oneOf:
      - $ref: '#/components/schemas/AWSFirehose'
      - $ref: '#/components/schemas/AWSKinesis'
      - $ref: '#/components/schemas/AWSLambda'
      - $ref: '#/components/schemas/AWSSQS'
      - $ref: '#/components/schemas/HTTP'
    EventType:
      type: object
      properties:
        space:
          $ref: '#/components/schemas/SpaceName'
        name:
          $ref: '#/components/schemas/EventTypeName'
        authorizerId:
          $ref: '#/components/schemas/FunctionID'
    Function:
      type: object
      properties:
        space:
          $ref: '#/components/schemas/SpaceName'
        functionId:
          $ref: '#/components/schemas/FunctionID'
        type:
          $ref: '#/components/schemas/ProviderType'
        provider:
          $ref: '#/components/schemas/Provider'
    EventTypes:
      type: object
      properties:
        eventTypes:
          type: array
          items:
            $ref: '#/components/schemas/EventType'
    Functions:
      type: object
      properties:
        functions:
          type: array
          items:
            $ref: '#/components/schemas/Function'
    Subscription:
      type: object
      properties:
        space:
          $ref: '#/components/schemas/SpaceName'
        subscriptionId:
          $ref: '#/components/schemas/SubscriptionID'
        type:
          $ref: '#/components/schemas/SubscriptionType'
        eventType:
          $ref: '#/components/schemas/EventType'
        functionId:
          $ref: '#/components/schemas/FunctionID'
        path:
          $ref: '#/components/schemas/Path'
        method:
          $ref: '#/components/schemas/Method'
    Subscriptions:
      type: object
      properties:
        subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/Subscription'
    CORS:
      type: object
      properties:
        space:
          $ref: '#/components/schemas/SpaceName'
        corsId:
          $ref: '#/components/schemas/CORSID'
        method:
          $ref: '#/components/schemas/Method'
        path:
          $ref: '#/components/schemas/Path'
        allowedOrigins:
          $ref: '#/components/schemas/AllowedOrigins'
        allowedMethods:
          $ref: '#/components/schemas/AllowedMethods'
        allowedHeaders:
          $ref: '#/components/schemas/AllowedHeaders'
        allowCredentials:
          $ref: '#/components/schemas/AllowCredentials'
    CORSes:
      type: object
      properties:
        cors:
          type: array
          items:
            $ref: '#/components/schemas/CORS'
    AWSFirehose:
      type: object
      properties:
        deliveryStreamName:
          $ref: '#/components/schemas/DeliveryStreamName'
        region:
          $ref: '#/components/schemas/Region'
        awsAccessKeyId:
          $ref: '#/components/schemas/AWSAccessKeyId'
        awsSecretAccessKey:
          $ref: '#/components/schemas/AWSSecretAccessKey'
        awsSessionToken:
          $ref: '#/components/schemas/AWSSessionToken'
    AWSKinesis:
      type: object
      properties:
        streamName:
          $ref: '#/components/schemas/StreamName'
        region:
          $ref: '#/components/schemas/Region'
        awsAccessKeyId:
          $ref: '#/components/schemas/AWSAccessKeyId'
        awsSecretAccessKey:
          $ref: '#/components/schemas/AWSSecretAccessKey'
        awsSessionToken:
          $ref: '#/components/schemas/AWSSessionToken'
    AWSLambda:
      type: object
      properties:
        arn:
          $ref: '#/components/schemas/ARN'
        region:
          $ref: '#/components/schemas/Region'
        awsAccessKeyId:
          $ref: '#/components/schemas/AWSAccessKeyId'
        awsSecretAccessKey:
          $ref: '#/components/schemas/AWSSecretAccessKey'
        awsSessionToken:
          $ref: '#/components/schemas/AWSSessionToken'
    AWSSQS:
      type: object
      properties:
        queueUrl:
          $ref: '#/components/schemas/QueueURL'
        region:
          $ref: '#/components/schemas/Region'
        awsAccessKeyId:
          $ref: '#/components/schemas/AWSAccessKeyId'
        awsSecretAccessKey:
          $ref: '#/components/schemas/AWSSecretAccessKey'
        awsSessionToken:
          $ref: '#/components/schemas/AWSSessionToken'
    HTTP:
      type: object
      properties:
        url:
          $ref: '#/components/schemas/URL'
    ARN:
      type: string
      description: "AWS ARN identifier"
    Region:
      type: string
      description: "AWS Region name"
    AWSAccessKeyId:
      type: string
      description: "AWS Access Key ID"
    AWSSecretAccessKey:
      type: string
      description: "AWS Secret Access Key"
    AWSSessionToken:
      type: string
      description: "AWS Session Token"
    StreamName:
      type: string
      description: "AWS Kinesis stream name"
    DeliveryStreamName:
      type: string
      description: "AWS Firehose delivery stream name"
    QueueURL:
      type: string
      description: "AWS SQS Queue URL"
    URL:
      type: string
      format: url
      description: "HTTP endpoint URL"
    EventTypeName:
      type: string
      description: "event type"
    Method:
      type: string
      description: "(only for HTTP event) HTTP method that accepts requests"
    Path:
      type: string
      description: "URL path under which events (HTTP requests) are accepted"
      default: "/"
    AllowedOrigins:
      type: array
      items:
        type: string
    AllowedMethods:
      type: array
      items:
        type: string
    AllowedHeaders:
      type: array
      items:
        type: string
    AllowCredentials:
      type: boolean
    Error:
      type: object
      description: "response error object"
      properties:
        message:
          type: string
    Errors:
      type: object
      description: "error response object"
      properties:
        errors:
          type: array
          items:
            $ref: "#/components/schemas/Error"
  parameters:
    Space:
      in: "path"
      name: "spaceName"
      description: "space name"
      required: true
      schema:
        $ref: "#/components/schemas/SpaceName"
    EventTypeName:
      in: "path"
      name: "eventTypeName"
      description: "event type name"
      required: true
      schema:
        $ref: "#/components/schemas/EventTypeName"
    FunctionID:
      in: "path"
      name: "functionId"
      description: "function identifier"
      required: true
      schema:
        $ref: "#/components/schemas/FunctionID"
    SubscriptionID:
      in: "path"
      name: "subscriptionId"
      description: "subscription identifier"
      required: true
      schema:
        $ref: "#/components/schemas/SubscriptionID"
    CORSID:
      in: "path"
      name: "corsId"
      description: "CORS configuration identifier"
      required: true
      schema:
        $ref: "#/components/schemas/CORSID"
    Filters:
      in: "query"
      name: "filters"
      description: filter out returned list of objects. Currently, filters can only use metadata properties e.g. `metadata.service=usersService`.
      style: form
      example:
        "metadata.service": usersService
      schema:
        type: object
        additionalProperties:
          type: string
  requestBodies:
    CreateEventType:
      description: "event type create request body"
      content:
        application/json:
          schema:
            type: object
            required:
              - name
            properties:
              name:
                $ref: '#/components/schemas/EventTypeName'
              authorizerId:
                $ref: '#/components/schemas/FunctionID'
    UpdateEventType:
      description: "event type update request body"
      content:
        application/json:
          schema:
            type: object
            properties:
              authorizerId:
                $ref: '#/components/schemas/FunctionID'
    CreateFunction:
      description: "function create request body"
      content:
        application/json:
          schema:
            type: object
            required:
              - functionId
              - type
              - provider
            properties:
              functionId:
                $ref: '#/components/schemas/FunctionID'
              type:
                $ref: '#/components/schemas/ProviderType'
              provider:
                $ref: '#/components/schemas/Provider'
    UpdateFunction:
      description: "function update request body"
      content:
        application/json:
          schema:
            type: object
            properties:
              type:
                $ref: '#/components/schemas/ProviderType'
              provider:
                $ref: '#/components/schemas/Provider'
    CreateSubscription:
      description: "subscription create request body"
      content:
        application/json:
          schema:
            type: object
            required:
              - type
              - eventType
              - functionId
            properties:
              type:
                $ref: '#/components/schemas/SubscriptionType'
              eventType:
                $ref: '#/components/schemas/EventType'
              functionId:
                $ref: '#/components/schemas/FunctionID'
              path:
                $ref: '#/components/schemas/Path'
              method:
                $ref: '#/components/schemas/Method'
    UpdateSubscription:
      description: "subscription update request body"
      content:
        application/json:
          schema:
            type: object
            properties:
              type:
                $ref: '#/components/schemas/SubscriptionType'
              eventType:
                $ref: '#/components/schemas/EventType'
              functionId:
                $ref: '#/components/schemas/FunctionID'
              path:
                $ref: '#/components/schemas/Path'
              method:
                $ref: '#/components/schemas/Method'
    CreateCORS:
      description: "CORS configuration create request body"
      content:
        application/json:
          schema:
            type: object
            properties:
              method:
                $ref: '#/components/schemas/Method'
              path:
                $ref: '#/components/schemas/Path'
              allowedOrigins:
                $ref: '#/components/schemas/AllowedOrigins'
              allowedMethods:
                $ref: '#/components/schemas/AllowedMethods'
              allowedHeaders:
                $ref: '#/components/schemas/AllowedHeaders'
              allowCredentials:
                $ref: '#/components/schemas/AllowCredentials'
    UpdateCORS:
      description: "CORS configuration update request body"
      content:
        application/json:
          schema:
            type: object
            properties:
              method:
                $ref: '#/components/schemas/Method'
              path:
                $ref: '#/components/schemas/Path'
              allowedOrigins:
                $ref: '#/components/schemas/AllowedOrigins'
              allowedMethods:
                $ref: '#/components/schemas/AllowedMethods'
              allowedHeaders:
                $ref: '#/components/schemas/AllowedHeaders'
              allowCredentials:
                $ref: '#/components/schemas/AllowCredentials'
  responses:
    Error:
      description: "internal server error"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Errors'
    NotFoundError:
      description: "not found error"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Errors'
    ValidationError:
      description: "validation error"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Errors'
    FunctionHasSubscriptionsError:
      description: "function is subscribed to at least one event"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Errors'
    EventTypeHasSubscriptionsError:
      description: "there are subscriptions using the event type"
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Errors'

---

--- METRICS ---
- Files Extracted: 20
- Estimated Token Budget: ~29637 tokens
- Canonical Reference: https://codewiki.google/github.com/serverless/gateway
