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:
Lance Ball 2021-08-31 15:46:32 -04:00 committed by GitHub
parent db9bc5a162
commit 648cd6b283
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 3925 deletions

View File

@ -1,4 +1,4 @@
FROM node:10 FROM node:16-slim
# Create app directory # Create app directory
WORKDIR /usr/src/app WORKDIR /usr/src/app
@ -12,7 +12,7 @@ RUN npm install
# RUN npm ci --only=production # RUN npm ci --only=production
# Bundle app source # Bundle app source
COPY . . COPY index.js ./
EXPOSE 8080 EXPOSE 8080
CMD [ "node", "index.js" ] CMD [ "npm", "start" ]

View File

@ -7,9 +7,9 @@ aliases:
- /docs/eventing/samples/writing-event-source-easy-way - /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 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). - 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: Create the project and add the dependencies:
```bash ```bash
npm init npm init -y
npm install cloudevents-sdk@2.0.1 --save 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 ## Using ContainerSource
A ContainerSource creates a container for your event source image and manages this container. 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 ### 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 ```javascript
// File - index.js // 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']; const source = 'urn:event:from:heartbeat/example';
const type = 'heartbeat.example';
console.log("Sink URL is " + sinkUrl);
let emitter = new HTTPEmitter({
url: sinkUrl
});
let eventIndex = 0; let eventIndex = 0;
setInterval(function () { setInterval(() => {
console.log("Emitting event #" + ++eventIndex); console.log(`Emitting event # ${++eventIndex}`);
let myevent = new CloudEvent({ // Create a new CloudEvent each second
source: "urn:event:from:my-api/resource/123", const event = new CloudEvent({ source, type, data: {'hello': `World # ${eventIndex}`} });
type: "your.event.source.type",
id: "your-event-id",
dataContentType: "application/json",
data: {"hello": "World " + eventIndex},
});
// Emit the event // Emits the 'cloudevent' Node.js event application-wide
emitter.send(myevent) event.emit();
.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);
});
}, 1000); }, 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 ```dockerfile
# File - Dockerfile # File - Dockerfile
FROM node:10 FROM node:16
WORKDIR /usr/src/app WORKDIR /usr/src/app
COPY package*.json ./ COPY package*.json ./
RUN npm install RUN npm install
COPY . . COPY . .
EXPOSE 8080 EXPOSE 8080
CMD [ "node", "index.js" ] 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 ### 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: 1. Build and push the image:
```bash ```bash
docker build . -t path/to/image/registry/node-knative-heartbeat-source:v1 export REGISTRY=docker.io/myregistry
docker push path/to/image/registry/node-knative-heartbeat-source:v1 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: 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: template:
spec: spec:
containers: 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: 3. Create the ContainerSource object:
@ -125,12 +138,12 @@ Binary mode is used in most cases because:
apiVersion: sources.knative.dev/v1 apiVersion: sources.knative.dev/v1
kind: ContainerSource kind: ContainerSource
metadata: metadata:
name: test-heartbeats name: heartbeat-source
spec: spec:
template: template:
spec: spec:
containers: containers:
- image: path/to/image/registry/node-knative-heartbeat-source:v1 - image: docker.io/myregistry/node-heartbeat-source:v1
name: heartbeats name: heartbeats
sink: sink:
ref: ref:
@ -148,9 +161,9 @@ Binary mode is used in most cases because:
Validation: valid Validation: valid
Context Attributes, Context Attributes,
specversion: 1.0 specversion: 1.0
type: your.event.source.type type: heartbeat.example
source: urn:event:from:your-api/resource/123 source: urn:event:from:heartbeat/example
id: your-event-id id: 47e69d34-def7-449b-8382-3652495f9163
datacontenttype: application/json datacontenttype: application/json
Data, 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: 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 ```bash
$ kubectl logs test-heartbeats-deployment-7575c888c7-85w5t $ kubectl logs heartbeat-source-7575c888c7-85w5t
Sink URL is http://event-display.default.svc.cluster.local Sink URL is http://event-display.default.svc.cluster.local
Emitting event #1 Emitting event #1
@ -187,7 +200,7 @@ SinkBinding does not create any containers. It injects the sink information to a
template: template:
spec: spec:
containers: 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: 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: spec:
containers: containers:
- name: node-heartbeats - name: node-heartbeats
image: path/to/image/registry/node-knative-heartbeat-source:v1 image: docker.io/myregistry/node-heartbeat-source:v1
ports: ports:
- containerPort: 8080 - containerPort: 8080
``` ```
@ -259,9 +272,9 @@ SinkBinding does not create any containers. It injects the sink information to a
Validation: valid Validation: valid
Context Attributes, Context Attributes,
specversion: 1.0 specversion: 1.0
type: your.event.source.type type: heartbeat.example
source: urn:event:from:your-api/resource/123 source: urn:event:from:heartbeat/example
id: your-event-id id: 47e69d34-def7-449b-8382-3652495f9163
datacontenttype: application/json datacontenttype: application/json
Data, Data,
{ {
@ -272,9 +285,9 @@ SinkBinding does not create any containers. It injects the sink information to a
Validation: valid Validation: valid
Context Attributes, Context Attributes,
specversion: 1.0 specversion: 1.0
type: your.event.source.type type: heartbeat.example
source: urn:event:from:your-api/resource/123 source: urn:event:from:heartbeat/example
id: your-event-id id: 47e69d34-def7-449b-8382-3652495f9163
datacontenttype: application/json datacontenttype: application/json
Data, Data,
{ {

View File

@ -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); const source = 'urn:event:from:heartbeat/example';
const type = 'heartbeat.example';
let emitter = new HTTPEmitter({
url: sinkUrl
});
let eventIndex = 0; let eventIndex = 0;
setInterval(function () { setInterval(() => {
console.log("Emitting event #" + ++eventIndex); console.log(`Emitting event # ${++eventIndex}`);
let myevent = new CloudEvent({ // Create a new CloudEvent each second
source: "urn:event:from:my-api/resource/123", const event = new CloudEvent({ source, type, data: {'hello': `World # ${eventIndex}`} });
type: "your.event.source.type",
id: "your-event-id",
dataContentType: "application/json",
data: {"hello": "World " + eventIndex},
});
// Emit the event // Emits the 'cloudevent' Node.js event application-wide
emitter.send(myevent) event.emit();
.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);
});
}, 1000); }, 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(); registerGracefulExit();
function registerGracefulExit() { function registerGracefulExit() {
let logExit = function () { process.on('exit', logExit);
console.log("Exiting"); //catches ctrl+c event
process.exit(); process.on('SIGINT', logExit);
}; process.on('SIGTERM', logExit);
// catches 'kill pid' (for example: nodemon restart)
// handle graceful exit process.on('SIGUSR1', logExit);
//do something when app is closing process.on('SIGUSR2', logExit);
process.on('exit', logExit); }
//catches ctrl+c event
process.on('SIGINT', logExit); function logExit(message = 'Exiting...') {
process.on('SIGTERM', logExit); // Handle graceful exit
// catches "kill pid" (for example: nodemon restart) console.log(message);
process.on('SIGUSR1', logExit); process.exit();
process.on('SIGUSR2', logExit);
} }

View File

@ -4,11 +4,13 @@
"description": "", "description": "",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
}, },
"author": "", "author": "",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"cloudevents-sdk": "^2.0.1" "cloudevents": "^4.0.3",
"got": "^11.8.2"
} }
} }

View File

@ -15,7 +15,7 @@ limitations under the License.
*/ */
const express = require('express') const express = require('express')
const { CloudEvent, Emitter, HTTP } = require('cloudevents') const { CloudEvent, HTTP } = require('cloudevents')
const PORT = process.env.PORT || 8080 const PORT = process.env.PORT || 8080
const target = process.env.K_SINK const target = process.env.K_SINK
const app = express() const app = express()

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,7 @@
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"axios": "^0.21.1", "axios": "^0.21.1",
"cloudevents": "^4.0.0", "cloudevents": "^4.0.3",
"express": "^4.17.1", "express": "^4.17.1",
"nodemon": "^2.0.4" "nodemon": "^2.0.4"
}, },