mirror of https://github.com/knative/docs.git
eventing(samples): update Node.js CloudEvents ContainerSource example (#4039)
* eventing(samles): update Node.js CloudEvents ContainerSource The example had some old versions of Node and CloudEvents. This change updates the versions for those dependencies, and makes some modifications to the example code based on these dependency changes. Signed-off-by: Lance Ball <lball@redhat.com> * fixup: review feedback Co-authored-by: Samia Nneji <snneji@vmware.com> Co-authored-by: Samia Nneji <snneji@vmware.com>
This commit is contained in:
parent
db9bc5a162
commit
648cd6b283
|
@ -1,4 +1,4 @@
|
|||
FROM node:10
|
||||
FROM node:16-slim
|
||||
|
||||
# Create app directory
|
||||
WORKDIR /usr/src/app
|
||||
|
@ -12,7 +12,7 @@ RUN npm install
|
|||
# RUN npm ci --only=production
|
||||
|
||||
# Bundle app source
|
||||
COPY . .
|
||||
COPY index.js ./
|
||||
|
||||
EXPOSE 8080
|
||||
CMD [ "node", "index.js" ]
|
||||
CMD [ "npm", "start" ]
|
||||
|
|
|
@ -7,9 +7,9 @@ aliases:
|
|||
- /docs/eventing/samples/writing-event-source-easy-way
|
||||
---
|
||||
|
||||
# Writing an event source using Javascript
|
||||
# Writing an event source using JavaScript
|
||||
|
||||
This tutorial provides instructions to build an event source in Javascript and implement it with a ContainerSource or SinkBinding.
|
||||
This tutorial provides instructions to build an event source in JavaScript and implement it with a ContainerSource or SinkBinding.
|
||||
|
||||
- Using a [ContainerSource](../../containersource/README.md) is a simple way to turn any dispatcher container into a Knative event source.
|
||||
- Using [SinkBinding](../../sinkbinding/README.md) provides a framework for injecting environment variables into any Kubernetes resource that has a `spec.template` and is [PodSpecable](https://pkg.go.dev/knative.dev/pkg/apis/duck/v1#PodSpecable).
|
||||
|
@ -21,88 +21,101 @@ ContainerSource and SinkBinding both work by injecting environment variables to
|
|||
Create the project and add the dependencies:
|
||||
|
||||
```bash
|
||||
npm init
|
||||
npm install cloudevents-sdk@2.0.1 --save
|
||||
npm init -y
|
||||
npm install cloudevents got --save
|
||||
```
|
||||
|
||||
**NOTE:** Due to this [bug](https://github.com/cloudevents/sdk-javascript/issues/191), you must use version 2.0.1 of the Javascript SDK or newer.
|
||||
|
||||
## Using ContainerSource
|
||||
|
||||
A ContainerSource creates a container for your event source image and manages this container.
|
||||
|
||||
The sink URL to post the events will be made available to the application through the `K_SINK` environment variable by the ContainerSource.
|
||||
The sink URL where events are posted will be made available to the application through the `K_SINK` environment variable by the ContainerSource.
|
||||
|
||||
### Example
|
||||
|
||||
The following example event source emits an event to the sink every 1000 milliseconds:
|
||||
The following example event source emits an event to the sink every second:
|
||||
|
||||
```javascript
|
||||
// File - index.js
|
||||
const got = require('got');
|
||||
const { CloudEvent, Emitter, emitterFor } = require('cloudevents');
|
||||
|
||||
const { CloudEvent, HTTPEmitter } = require("cloudevents-sdk");
|
||||
const K_SINK = process.env['K_SINK'];
|
||||
K_SINK || logExit('Error: K_SINK Environment variable is not defined');
|
||||
console.log(`Sink URL is ${K_SINK}`);
|
||||
|
||||
let sinkUrl = process.env['K_SINK'];
|
||||
|
||||
console.log("Sink URL is " + sinkUrl);
|
||||
|
||||
let emitter = new HTTPEmitter({
|
||||
url: sinkUrl
|
||||
});
|
||||
const source = 'urn:event:from:heartbeat/example';
|
||||
const type = 'heartbeat.example';
|
||||
|
||||
let eventIndex = 0;
|
||||
setInterval(function () {
|
||||
console.log("Emitting event #" + ++eventIndex);
|
||||
setInterval(() => {
|
||||
console.log(`Emitting event # ${++eventIndex}`);
|
||||
|
||||
let myevent = new CloudEvent({
|
||||
source: "urn:event:from:my-api/resource/123",
|
||||
type: "your.event.source.type",
|
||||
id: "your-event-id",
|
||||
dataContentType: "application/json",
|
||||
data: {"hello": "World " + eventIndex},
|
||||
});
|
||||
// Create a new CloudEvent each second
|
||||
const event = new CloudEvent({ source, type, data: {'hello': `World # ${eventIndex}`} });
|
||||
|
||||
// Emit the event
|
||||
emitter.send(myevent)
|
||||
.then(response => {
|
||||
// Treat the response
|
||||
console.log("Event posted successfully");
|
||||
console.log(response.data);
|
||||
})
|
||||
.catch(err => {
|
||||
// Deal with errors
|
||||
console.log("Error during event post");
|
||||
console.error(err);
|
||||
});
|
||||
// Emits the 'cloudevent' Node.js event application-wide
|
||||
event.emit();
|
||||
}, 1000);
|
||||
|
||||
// Create a function that can post an event
|
||||
const emit = emitterFor(event => {
|
||||
got.post(K_SINK, event)
|
||||
.then(response => {
|
||||
console.log('Event posted successfully');
|
||||
console.log(response.data);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Error during event post');
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Send the CloudEvent any time a Node.js 'cloudevent' event is emitted
|
||||
Emitter.on('cloudevent', emit);
|
||||
|
||||
registerGracefulExit();
|
||||
|
||||
function registerGracefulExit() {
|
||||
process.on('exit', logExit);
|
||||
//catches ctrl+c event
|
||||
process.on('SIGINT', logExit);
|
||||
process.on('SIGTERM', logExit);
|
||||
// catches 'kill pid' (for example: nodemon restart)
|
||||
process.on('SIGUSR1', logExit);
|
||||
process.on('SIGUSR2', logExit);
|
||||
}
|
||||
|
||||
function logExit(message = 'Exiting...') {
|
||||
// Handle graceful exit
|
||||
console.log(message);
|
||||
process.exit();
|
||||
}
|
||||
```
|
||||
|
||||
```dockerfile
|
||||
# File - Dockerfile
|
||||
|
||||
FROM node:10
|
||||
FROM node:16
|
||||
WORKDIR /usr/src/app
|
||||
COPY package*.json ./
|
||||
RUN npm install
|
||||
COPY . .
|
||||
EXPOSE 8080
|
||||
CMD [ "node", "index.js" ]
|
||||
|
||||
```
|
||||
|
||||
The example code uses _Binary_ mode for CloudEvents. To employ structured code, change `let binding = new v1.BinaryHTTPEmitter(config);` to `let binding = new v1.StructuredHTTPEmitter(config);`.
|
||||
|
||||
Binary mode is used in most cases because:
|
||||
- It is faster in terms of serialization and deserialization.
|
||||
- It works better with CloudEvent-aware proxies, such as Knative Channels, and can simply check the header instead of parsing the payload.
|
||||
|
||||
### Procedure
|
||||
|
||||
Before publishing the ContainerSource, you must build the application
|
||||
image, and push it to a container registry that your cluster can access.
|
||||
|
||||
1. Build and push the image:
|
||||
|
||||
```bash
|
||||
docker build . -t path/to/image/registry/node-knative-heartbeat-source:v1
|
||||
docker push path/to/image/registry/node-knative-heartbeat-source:v1
|
||||
export REGISTRY=docker.io/myregistry
|
||||
docker build . -t $REGISTRY/node-heartbeat-source:v1
|
||||
docker push $REGISTRY/node-heartbeat-source:v1
|
||||
```
|
||||
|
||||
2. Create the event display service which logs any CloudEvents posted to it:
|
||||
|
@ -116,7 +129,7 @@ Binary mode is used in most cases because:
|
|||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: docker.io/aliok/event_display-864884f202126ec3150c5fcef437d90c@sha256:93cb4dcda8fee80a1f68662ae6bf20301471b046ede628f3c3f94f39752fbe08
|
||||
- image: gcr.io/knative-releases/knative.dev/eventing-contrib/cmd/event_display
|
||||
```
|
||||
|
||||
3. Create the ContainerSource object:
|
||||
|
@ -125,12 +138,12 @@ Binary mode is used in most cases because:
|
|||
apiVersion: sources.knative.dev/v1
|
||||
kind: ContainerSource
|
||||
metadata:
|
||||
name: test-heartbeats
|
||||
name: heartbeat-source
|
||||
spec:
|
||||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: path/to/image/registry/node-knative-heartbeat-source:v1
|
||||
- image: docker.io/myregistry/node-heartbeat-source:v1
|
||||
name: heartbeats
|
||||
sink:
|
||||
ref:
|
||||
|
@ -148,9 +161,9 @@ Binary mode is used in most cases because:
|
|||
Validation: valid
|
||||
Context Attributes,
|
||||
specversion: 1.0
|
||||
type: your.event.source.type
|
||||
source: urn:event:from:your-api/resource/123
|
||||
id: your-event-id
|
||||
type: heartbeat.example
|
||||
source: urn:event:from:heartbeat/example
|
||||
id: 47e69d34-def7-449b-8382-3652495f9163
|
||||
datacontenttype: application/json
|
||||
Data,
|
||||
{
|
||||
|
@ -161,7 +174,7 @@ Binary mode is used in most cases because:
|
|||
5. Optional: If you are interested in seeing what is injected into the event source as a `K_SINK`, you can check the logs:
|
||||
|
||||
```bash
|
||||
$ kubectl logs test-heartbeats-deployment-7575c888c7-85w5t
|
||||
$ kubectl logs heartbeat-source-7575c888c7-85w5t
|
||||
|
||||
Sink URL is http://event-display.default.svc.cluster.local
|
||||
Emitting event #1
|
||||
|
@ -187,7 +200,7 @@ SinkBinding does not create any containers. It injects the sink information to a
|
|||
template:
|
||||
spec:
|
||||
containers:
|
||||
- image: docker.io/aliok/event_display-864884f202126ec3150c5fcef437d90c@sha256:93cb4dcda8fee80a1f68662ae6bf20301471b046ede628f3c3f94f39752fbe08
|
||||
- image: gcr.io/knative-releases/knative.dev/eventing-contrib/cmd/event_display
|
||||
```
|
||||
|
||||
2. Create a Kubernetes deployment that runs the event source:
|
||||
|
@ -211,7 +224,7 @@ SinkBinding does not create any containers. It injects the sink information to a
|
|||
spec:
|
||||
containers:
|
||||
- name: node-heartbeats
|
||||
image: path/to/image/registry/node-knative-heartbeat-source:v1
|
||||
image: docker.io/myregistry/node-heartbeat-source:v1
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
```
|
||||
|
@ -259,9 +272,9 @@ SinkBinding does not create any containers. It injects the sink information to a
|
|||
Validation: valid
|
||||
Context Attributes,
|
||||
specversion: 1.0
|
||||
type: your.event.source.type
|
||||
source: urn:event:from:your-api/resource/123
|
||||
id: your-event-id
|
||||
type: heartbeat.example
|
||||
source: urn:event:from:heartbeat/example
|
||||
id: 47e69d34-def7-449b-8382-3652495f9163
|
||||
datacontenttype: application/json
|
||||
Data,
|
||||
{
|
||||
|
@ -272,9 +285,9 @@ SinkBinding does not create any containers. It injects the sink information to a
|
|||
Validation: valid
|
||||
Context Attributes,
|
||||
specversion: 1.0
|
||||
type: your.event.source.type
|
||||
source: urn:event:from:your-api/resource/123
|
||||
id: your-event-id
|
||||
type: heartbeat.example
|
||||
source: urn:event:from:heartbeat/example
|
||||
id: 47e69d34-def7-449b-8382-3652495f9163
|
||||
datacontenttype: application/json
|
||||
Data,
|
||||
{
|
||||
|
|
|
@ -1,54 +1,54 @@
|
|||
const { CloudEvent, HTTPEmitter } = require("cloudevents-sdk");
|
||||
const got = require('got');
|
||||
const { CloudEvent, Emitter, emitterFor } = require('cloudevents');
|
||||
|
||||
let sinkUrl = process.env['K_SINK'];
|
||||
const K_SINK = process.env['K_SINK'];
|
||||
K_SINK || logExit('Error: K_SINK Environment variable is not defined');
|
||||
console.log(`Sink URL is ${K_SINK}`);
|
||||
|
||||
console.log("Sink URL is " + sinkUrl);
|
||||
|
||||
let emitter = new HTTPEmitter({
|
||||
url: sinkUrl
|
||||
});
|
||||
const source = 'urn:event:from:heartbeat/example';
|
||||
const type = 'heartbeat.example';
|
||||
|
||||
let eventIndex = 0;
|
||||
setInterval(function () {
|
||||
console.log("Emitting event #" + ++eventIndex);
|
||||
setInterval(() => {
|
||||
console.log(`Emitting event # ${++eventIndex}`);
|
||||
|
||||
let myevent = new CloudEvent({
|
||||
source: "urn:event:from:my-api/resource/123",
|
||||
type: "your.event.source.type",
|
||||
id: "your-event-id",
|
||||
dataContentType: "application/json",
|
||||
data: {"hello": "World " + eventIndex},
|
||||
});
|
||||
// Create a new CloudEvent each second
|
||||
const event = new CloudEvent({ source, type, data: {'hello': `World # ${eventIndex}`} });
|
||||
|
||||
// Emit the event
|
||||
emitter.send(myevent)
|
||||
.then(response => {
|
||||
// Treat the response
|
||||
console.log("Event posted successfully");
|
||||
console.log(response.data);
|
||||
})
|
||||
.catch(err => {
|
||||
// Deal with errors
|
||||
console.log("Error during event post");
|
||||
console.error(err);
|
||||
});
|
||||
// Emits the 'cloudevent' Node.js event application-wide
|
||||
event.emit();
|
||||
}, 1000);
|
||||
|
||||
// Create a function that can post an event
|
||||
const emit = emitterFor(event => {
|
||||
got.post(K_SINK, event)
|
||||
.then(response => {
|
||||
console.log('Event posted successfully');
|
||||
console.log(response.data);
|
||||
})
|
||||
.catch(err => {
|
||||
console.log('Error during event post');
|
||||
console.error(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Send the CloudEvent any time a Node.js 'cloudevent' event is emitted
|
||||
Emitter.on('cloudevent', emit);
|
||||
|
||||
registerGracefulExit();
|
||||
|
||||
function registerGracefulExit() {
|
||||
let logExit = function () {
|
||||
console.log("Exiting");
|
||||
process.exit();
|
||||
};
|
||||
|
||||
// handle graceful exit
|
||||
//do something when app is closing
|
||||
process.on('exit', logExit);
|
||||
//catches ctrl+c event
|
||||
process.on('SIGINT', logExit);
|
||||
process.on('SIGTERM', logExit);
|
||||
// catches "kill pid" (for example: nodemon restart)
|
||||
process.on('SIGUSR1', logExit);
|
||||
process.on('SIGUSR2', logExit);
|
||||
process.on('exit', logExit);
|
||||
//catches ctrl+c event
|
||||
process.on('SIGINT', logExit);
|
||||
process.on('SIGTERM', logExit);
|
||||
// catches 'kill pid' (for example: nodemon restart)
|
||||
process.on('SIGUSR1', logExit);
|
||||
process.on('SIGUSR2', logExit);
|
||||
}
|
||||
|
||||
function logExit(message = 'Exiting...') {
|
||||
// Handle graceful exit
|
||||
console.log(message);
|
||||
process.exit();
|
||||
}
|
||||
|
|
|
@ -4,11 +4,13 @@
|
|||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "node index.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cloudevents-sdk": "^2.0.1"
|
||||
"cloudevents": "^4.0.3",
|
||||
"got": "^11.8.2"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ limitations under the License.
|
|||
*/
|
||||
|
||||
const express = require('express')
|
||||
const { CloudEvent, Emitter, HTTP } = require('cloudevents')
|
||||
const { CloudEvent, HTTP } = require('cloudevents')
|
||||
const PORT = process.env.PORT || 8080
|
||||
const target = process.env.K_SINK
|
||||
const app = express()
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -14,7 +14,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"axios": "^0.21.1",
|
||||
"cloudevents": "^4.0.0",
|
||||
"cloudevents": "^4.0.3",
|
||||
"express": "^4.17.1",
|
||||
"nodemon": "^2.0.4"
|
||||
},
|
||||
|
|
Loading…
Reference in New Issue