From ec74b586e24804171e1c3b51c6f1ffe2089e3c41 Mon Sep 17 00:00:00 2001 From: Stanislav Jakuschevskij Date: Wed, 8 Oct 2025 12:51:53 +0200 Subject: [PATCH] Fix e2e tutorial solution files and documentation. Both function templates are updated to reflect the most recent version of the function cli. The code was also updated to build and execute. The `solution/setup.sh` was removed because it is empty. Mentioned Kind/Minikube with a link to the local registry setup docs in `solution/README.md`. This is needed for Camel K operator installation. Add corrected Camel K operator installation to `slack-sink/README.md` and to `solution.sh`. Also remove `kamel` cli installation check. Fixed typo and whitespace in the Javascript code of the node-server. Signed-off-by: Stanislav Jakuschevskij --- .../solution/ML-bad-word-filter/.gitignore | 1 + .../solution/ML-bad-word-filter/Procfile | 1 - .../solution/ML-bad-word-filter/README.md | 50 +++ .../solution/ML-bad-word-filter/app.sh | 3 - .../solution/ML-bad-word-filter/func.py | 44 -- .../solution/ML-bad-word-filter/func.yaml | 8 +- .../ML-bad-word-filter/function/__init__.py | 1 + .../ML-bad-word-filter/function/func.py | 44 ++ .../ML-bad-word-filter/pyproject.toml | 27 ++ .../ML-bad-word-filter/requirements.txt | 3 - .../ML-bad-word-filter/tests/test_func.py | 50 +++ .../solution/ML-sentiment-analysis/.gitignore | 1 + .../solution/ML-sentiment-analysis/Procfile | 1 - .../solution/ML-sentiment-analysis/README.md | 401 ++---------------- .../solution/ML-sentiment-analysis/app.sh | 3 - .../solution/ML-sentiment-analysis/func.py | 61 --- .../solution/ML-sentiment-analysis/func.yaml | 8 +- .../function/__init__.py | 1 + .../ML-sentiment-analysis/function/func.py | 61 +++ .../ML-sentiment-analysis/pyproject.toml | 27 ++ .../ML-sentiment-analysis/requirements.txt | 4 - .../solution/ML-sentiment-analysis/setup.py | 22 - .../ML-sentiment-analysis/tests/test_func.py | 50 +++ .../bookstore-sample-app/solution/README.md | 15 +- .../solution/node-server/index.js | 4 +- .../bookstore-sample-app/solution/setup.sh | 0 .../solution/slack-sink/README.md | 119 +++--- .../bookstore-sample-app/solution/solution.sh | 36 +- .../start/node-server/index.js | 4 +- 29 files changed, 460 insertions(+), 590 deletions(-) delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/Procfile create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/README.md delete mode 100755 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/app.sh delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.py create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/__init__.py create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/func.py create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/pyproject.toml delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/requirements.txt create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/Procfile delete mode 100755 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/app.sh delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.py create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/__init__.py create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/func.py create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/pyproject.toml delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/requirements.txt delete mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/setup.py create mode 100644 code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py delete mode 100755 code-samples/eventing/bookstore-sample-app/solution/setup.sh diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.gitignore b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.gitignore index 965f0d4ef0d..a333709f03a 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.gitignore +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/.gitignore @@ -3,3 +3,4 @@ # generally not be tracked in source control. To instruct the system to track # .func in source control, comment the following line (prefix it with '# '). /.func +/.s2i \ No newline at end of file diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/Procfile b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/Procfile deleted file mode 100644 index bb57f5a81c7..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: python -m parliament . diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/README.md b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/README.md new file mode 100644 index 00000000000..66ce1946005 --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/README.md @@ -0,0 +1,50 @@ +# Python CloudEvents Function + +Welcome to your new Python Function! A minimal Function implementation can +be found in `./function/func.py`. + +For more, run `func --help` or read the [Python Functions doc](https://github.com/knative/func/blob/main/docs/function-templates/python.md) + +## Usage + +Knative Functions allow for the deployment of your source code directly to a +Kubernetes cluster with Knative installed. + +### Function Structure + +Python functions must implement a `new()` method that returns a function +instance. The function class can optionally implement: +- `handle()` - Process CloudEvent requests +- `start()` - Initialize the function with configuration +- `stop()` - Clean up resources when the function stops +- `alive()` / `ready()` - Health and readiness checks + +See the default implementation in `./function/func.py` + +### Running Locally + +Use `func run` to test your Function locally before deployment. +For development environments where Python is installed, it's suggested to use +the `host` builder as follows: + +```bash +# Run function on the host (outsdie of a container) +func run --builder=host +``` + +### Deploying + +Use `func deploy` to deploy your Function to a Knative-enabled cluster: + +```bash +# Deploy with interactive prompts (recommended for first deployment) +func deploy --registry ghcr.io/myuser +``` + +Functions are automatically built, containerized, and pushed to a registry +before deployment. Subsequent deployments will update the existing function. + +## Roadmap + +Our project roadmap can be found: https://github.com/orgs/knative/projects/49 + diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/app.sh b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/app.sh deleted file mode 100755 index 4da37d4d8d2..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/app.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -exec python -m parliament "$(dirname "$0")" diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.py deleted file mode 100644 index 1692ccbef76..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.py +++ /dev/null @@ -1,44 +0,0 @@ -from parliament import Context -from profanity_check import predict -from cloudevents.http import CloudEvent - - -# The function to convert the bad word filter result into a CloudEvent -def create_cloud_event(inputText, data): - attributes = { - "type": "new-review-comment", - "source": "book-review-broker", - "datacontenttype": "application/json", - "badwordfilter": data, - } - - # Put the bad word filter result into a dictionary - data = {"reviewText": inputText, "badWordResult": data} - - # Create a CloudEvent object - event = CloudEvent(attributes, data) - - return event - - -def inappropriate_language_filter(text): - profanity_result = predict([text["reviewText"]]) - result = "good" - if profanity_result[0] == 1: - result = "bad" - - profanity_event = create_cloud_event(text["reviewText"], result) - return profanity_event - - -def main(context: Context): - """ - Function template - The context parameter contains the Flask request object and any - CloudEvent received with the request. - """ - - print("Received CloudEvent: ", context.cloud_event) - - # Add your business logic here - return inappropriate_language_filter(context.cloud_event.data) diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.yaml b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.yaml index a0224174f2f..33e03966014 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.yaml +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/func.yaml @@ -1,8 +1,14 @@ +# $schema: https://raw.githubusercontent.com/knative/func/d04ff0a3/schema/func_yaml-schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/knative/func/d04ff0a3/schema/func_yaml-schema.json specVersion: 0.36.0 name: bad-word-filter runtime: python -created: 2024-03-27T23:12:06.178272+08:00 +registry: localhost:5000 +namespace: default +created: 2025-09-20T18:38:53.634514281+02:00 +invoke: cloudevent build: builder: s2i deploy: namespace: default + image: localhost:5000/bad-word-filter@sha256:690a4eb5aaae56dfbfabcb8a3f0943a48b97851e2b90ce40397091bb0866655c diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/__init__.py b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/__init__.py new file mode 100644 index 00000000000..c16dbac257c --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/__init__.py @@ -0,0 +1 @@ +from .func import new diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/func.py new file mode 100644 index 00000000000..f0ead6c886a --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/function/func.py @@ -0,0 +1,44 @@ +import logging +from cloudevents.http import CloudEvent +from profanity_check import predict + +def new(): + return Function() + +class Function: + async def handle(self, scope, receive, send): + """ Handle all HTTP requests to this Function. The incoming CloudEvent is in scope["event"]. """ + logging.info("Request Received") + + # 1. Extract the CloudEvent from the scope + request_event = scope["event"] + + # 2. Extract the data payload from the event, analyze and create CloudEvent + response_event = self.inappropriate_language_filter(request_event.data) + + # 3. Send the response + logging.info(f"Sending response: {response_event.data}") + await send(response_event) + + def create_cloud_event(self, inputText, data): + attributes = { + "type": "new-review-comment", + "source": "book-review-broker", + "datacontenttype": "application/json", + "badwordfilter": data, + } + + # Put the bad word filter result into a dictionary + data = {"reviewText": inputText, "badWordResult": data} + + # Create a CloudEvent object + return CloudEvent(attributes, data) + + def inappropriate_language_filter(self, text): + review_text = text.get("reviewText", "") + profanity_result = predict([review_text]) + result = "good" + if profanity_result[0] == 1: + result = "bad" + + return self.create_cloud_event(review_text, result) diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/pyproject.toml b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/pyproject.toml new file mode 100644 index 00000000000..675a89c0794 --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "function" +description = "" +version = "0.1.0" +requires-python = ">=3.9" +readme = "README.md" +license = "MIT" +dependencies = [ + "httpx", + "cloudevents", + "pytest", + "pytest-asyncio", + "alt-profanity-check==1.4.1.post1" +] +authors = [ + { name="Your Name", email="you@example.com"}, +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pytest.ini_options] +asyncio_mode = "strict" +asyncio_default_fixture_loop_scope = "function" + + diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/requirements.txt b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/requirements.txt deleted file mode 100644 index 69aa57d8e14..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -parliament-functions==0.1.0 -alt-profanity-check==1.4.1.post1 -cloudevents==1.10.1 diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py new file mode 100644 index 00000000000..ad126f72302 --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-bad-word-filter/tests/test_func.py @@ -0,0 +1,50 @@ +""" +Unit tests for the CloudEvent function implementation. +Tests both the function health endpoints and its handling of CloudEvents. + +Functions are currently served as ASGI applications using hypercorn, so we +use httpx for testing. To run the tests using poetry: + +poetry run python -m unittest discover +""" +import json +import asyncio +import pytest +from cloudevents.http import CloudEvent +from function import new + + +@pytest.mark.asyncio +async def test_func(): + f = new() # Instantiate Function to Test + + # A test CloudEvent + attributes = { + "id": "test-id", + "type": "com.example.test1", + "source": "https://example.com/event-producer", + } + data = {"message": "test message"} + event = CloudEvent(attributes, data) + + invoked = False # Flag indicating send method was invoked + + # Send + # confirms the Functions responds with a CloudEvent which echoes + # the data sent. + async def send(e): + nonlocal invoked + invoked = True # Flag send was invoked + + # Ensure we got a CloudEvent + assert isinstance(e, CloudEvent), f"Expected CloudEvent, got {type(e)}" + + # Ensure it echoes the data sent + assert e.data == data, f"Expected data {data}, got {e.data}" + + # Invoke the Function + scope = {"event": event} # Add the CloudEvent to the scope + await f.handle(scope, {}, send) + + # Assert send was called + assert invoked, "Function did not call send" diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.gitignore b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.gitignore index 965f0d4ef0d..a333709f03a 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.gitignore +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/.gitignore @@ -3,3 +3,4 @@ # generally not be tracked in source control. To instruct the system to track # .func in source control, comment the following line (prefix it with '# '). /.func +/.s2i \ No newline at end of file diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/Procfile b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/Procfile deleted file mode 100644 index bb57f5a81c7..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/Procfile +++ /dev/null @@ -1 +0,0 @@ -web: python -m parliament . diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/README.md b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/README.md index f71bd55cce8..66ce1946005 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/README.md @@ -1,393 +1,50 @@ -# Sentiment Analysis Service for Bookstore Reviews +# Python CloudEvents Function -As a bookstore owner, you aim to receive instant notifications in a Slack channel whenever a customer submits a new **negative** review comment. By leveraging Knative Function, you can set up a serverless function that contains a simple sentiment analysis service to categorize review comments by sentiment. -## What Knative features will we learn about? +Welcome to your new Python Function! A minimal Function implementation can +be found in `./function/func.py`. -- The easiness to use **Knative Function** to deploy your service, and make it be managed by **Knative Serving**, which give you the ability to auto-scale your service to zero, and scale up to handle the demand. +For more, run `func --help` or read the [Python Functions doc](https://github.com/knative/func/blob/main/docs/function-templates/python.md) -## What does the final deliverable look like? -A running serverless Knative function that contains a python application that can receives the new review comments as CloudEvent and returns the sentiment classification of the input text as CloudEvent. +## Usage -The function's output will be only from -- Positive -- Neutral -- Negative -## Install Prerequisites +Knative Functions allow for the deployment of your source code directly to a +Kubernetes cluster with Knative installed. ---- -(Warning box: please make sure you have a running cluster with Knative Eventing and Serving installed. If not, click here. And you have the container registry ready.) +### Function Structure ---- +Python functions must implement a `new()` method that returns a function +instance. The function class can optionally implement: +- `handle()` - Process CloudEvent requests +- `start()` - Initialize the function with configuration +- `stop()` - Clean up resources when the function stops +- `alive()` / `ready()` - Health and readiness checks -### Prerequisite 1: Install Knative `func` CLI -Knative Function enables you to easily create, build, and deploy stateless, event-driven functions as [Knative Services](https://knative.dev/docs/serving/services/#:~:text=Knative%20Services%20are%20used%20to,the%20Service%20to%20be%20configured){:target="_blank"} by using the func CLI. +See the default implementation in `./function/func.py` -In order to do so, you need to install the `func` CLI. -You can follow the [official documentation](https://knative.dev/docs/getting-started/install-func/){:target="_blank"} to install the `func` CLI. +### Running Locally -Running `func version` in your terminal to verify the installation, and you should see the version of the `func` CLI you installed. - -## Implementation -The process is straightforward: -- Begin by utilizing the `func create` command to generate your code template. -- Next, incorporate your unique code into this template. -- Finally, execute `func deploy` to deploy your application seamlessly to the Kubernetes cluster. - -This workflow ensures a smooth transition from development to deployment within the Knative Functions ecosystem. - ---- -a warning box: -(Troubleshooting: if you see `command not found`, you may need to add the `func` CLI to your PATH.) ---- -### Step 1: Create a Knative Function template - -Create a new function using the `func` CLI: - -```bash -func create -l -``` - -In this case, we are creating a python function, so the command will be: - -```bash -func create -l python sentiment-analysis -``` - -This command will create a new directory with the name `sentiment-analysis` and a bunch of files in it. The `func` CLI will generate a basic function template for you to start with. - -You can find all the supported languages templates [here](https://knative.dev/docs/functions/){:target="_blank"}. - -The file tree will look like this: -```bash -sentiment-analysis -├── func.yaml -├── .funcignore -├── .gitignore -├── requirements.txt -├── app.sh -├── Procfile -└── func.py - -``` -### Step 2: Replace the generated code with the sentiment analysis logic -`func.py` is the file that contains the code for the function. You can replace the generated code with the sentiment analysis logic. You can use the following code as a starting point: - -```python title="func.py" -from parliament import Context -from flask import Request,request, jsonify -import json -from textblob import TextBlob -from time import sleep -from cloudevents.http import CloudEvent, to_structured - -# The function to convert the sentiment analysis result into a CloudEvent -def create_cloud_event(data): - attributes = { - "type": "knative.sampleapp.sentiment.response", - "source": "sentiment-analysis", - "datacontenttype": "application/json", - } - - # Put the sentiment analysis result into a dictionary - data = {"result": data} - - # Create a CloudEvent object - event = CloudEvent(attributes, data) - - return event - -def analyze_sentiment(text): - analysis = TextBlob(text) - sentiment = "Neutral" - if analysis.sentiment.polarity > 0: - sentiment = "Positive" - elif analysis.sentiment.polarity < 0: - sentiment = "Negative" - - # Convert the sentiment into a CloudEvent - sentiment = create_cloud_event(sentiment) - - # Sleep for 3 seconds to simulate a long-running process - sleep(3) - - return sentiment - -def main(context: Context): - """ - Function template - The context parameter contains the Flask request object and any - CloudEvent received with the request. - """ - - print("Received CloudEvent: ", context.cloud_event) - - # Add your business logic here - return analyze_sentiment(context.cloud_event.data) - -``` - -### Step 3: Configure the dependencies -The `requirements.txt` file contains the dependencies for the function. You can add the following dependencies to the `requirements.txt` file: - -```bash -Flask==3.0.2 -textblob==0.18.0.post0 -parliament-functions==0.1.0 -cloudevents==1.10.1 -``` -Knative function will automatically install the dependencies listed here when you build the function. - -### Step 4: Configre the pre-built environment -In order to properly use the `textblob` library, you need to download the corpora, which is a large collection of text data that is used to train the sentiment analysis model. You can do this by creating a new file called `setup.py`, -knative function will ensure that the `setup.py` file is executed after the dependencies have been installed. - - -The `setup.py` file should contain the following code for your bookstore: - - -```python -from setuptools import setup, find_packages -from setuptools.command.install import install -import subprocess - - -class PostInstallCommand(install): - """Post-installation for installation mode.""" - def run(self): - # Call the superclass run method - install.run(self) - # Run the command to download the TextBlob corpora - subprocess.call(['python', '-m', 'textblob.download_corpora', 'lite']) - - -setup( - name="download_corpora", - version="1.0", - packages=find_packages(), - cmdclass={ - 'install': PostInstallCommand, - } -) -``` - - - - -### Step 5: Try to build and run your Knative Function on your local machine - -In knative function, there are two ways to build: using the [pack build](https://github.com/knative/func/blob/8f3f718a5a036aa6b6eaa9f70c03aeea740015b9/docs/reference/func_build.md?plain=1#L46){:target="_blank"} or using the [source-to-image (s2i) build](https://github.com/knative/func/blob/4f48549c8ad4dad34bf750db243d81d503f0090f/docs/reference/func_build.md?plain=1#L43){:target="_blank"}. - -Currently. only the **s2i** build is supported if you need to run setup.py. When building with s2i, the `setup.py` file will be executed automatically after the dependencies have been installed. - -Before we get started, configure the container registry to push the image to the container registry. You can use the following command to configure the container registry: +Use `func run` to test your Function locally before deployment. +For development environments where Python is installed, it's suggested to use +the `host` builder as follows: ```bash -export FUNC_REGISTRY= +# Run function on the host (outsdie of a container) +func run --builder=host ``` -In this case, we will use the s2i build by adding the flag `-b=s2i`, and `-v` to see the verbose output. +### Deploying -```bash -func build -b=s2i -v -``` - -When the build is complete, you will see the following output: - -```bash -🙌 Function built: /sentiment-analysis-app:latest -``` - -This command will build the function and push the image to the container registry. After the build is complete, you can run the function using the following command: - ---- -An alert box -Issue you may experience: -``` -Error: '/home/Kuack/Documents/knative/docs/code-samples' does not contain an initialized function -``` -Solution: You may want to check whether you are in the correct directory. You can use the following command to check the current directory. - - -If you are in the right directory, and the error still occurs, try to check your func.yaml, - -as it has to contain the field `created` and the right time stamp to be treated as a valid knative function. - ---- +Use `func deploy` to deploy your Function to a Knative-enabled cluster: ```bash -func run -b=s2i -v +# Deploy with interactive prompts (recommended for first deployment) +func deploy --registry ghcr.io/myuser ``` -In the future, you can **skip the step of `func build`**, because func run will automatically build the function for you. - -You will see the following output if the function is running successfully: - -``` -function up-to-date. Force rebuild with --build -Running on host port 8080 ----> Running application from script (app.sh) ... -```` - -Now you can test the function by sending a request to the function using the following command: - -```bash -curl -X POST http://localhost:8080 \ --H "ce-id: 12345" \ --H "ce-source: /your/source" \ --H "ce-type: sentiment-analysis-request" \ --H "ce-specversion: 1.0" \ --H "Content-Type: application/json" \ --d '{"input":"I love Knative so much!"}' -``` -where `-H` are the headers, and `-d` is the input text. The input text is a **sting**. Be careful with the quotes. - -If the function is running successfully, you will see the following output (the `data` field in the Response CloudEvent only): - -```bash -{ - "input":"I love Knative so much!", - "result": "Positive" -} -``` - -Knative function also have an easy way to simulate the CloudEvent, you can use the following command to simulate the CloudEvent: - -```bash -func invoke -f=cloudevent --data='{"input": "I love Knative so much"}' --content-type=application/json --type="new-comment" -v -``` -where the `-f` flag indicates the type of the data, is either `HTTP` or `cloudevent`, and the `--data` flag is the input text. -You can read more about `func invoke` [here](https://github.com/knative/func/blob/main/docs/reference/func_invoke.md){:target="_blank"}. - -In this case, you will get the full CloudEvent response: - -```bash -Context Attributes, - specversion: 1.0 - type: knative.sampleapp.sentiment.response - source: sentiment-analysis - id: af0c0f59-9130-4a6c-96ef-6d72c2f4ce50 - time: 2024-02-31T18:48:00.232436Z - datacontenttype: application/json -Data, - { - "input":"I love Knative so much!", - "result": "Positive" - } - -``` - -### Step 6: Deploy the function to the cluster -After you have finished the code, you can deploy the function to the cluster using the following command: - -```bash -func deploy -b=s2i -v -``` - -When the deployment is complete, you will see the following output: - -```bash -✅ Function updated in namespace "default" and exposed at URL: - http://sentiment-analysis-app.default.10.99.46.8.sslip.io -``` - -You can also find the URL by running the following command: - -```bash -kubectl get kservice -A -``` - -You will see the URL in the output: - -```bash -NAMESPACE NAME URL LATESTCREATED LATESTREADY READY REASON -default sentiment-analysis-app http://sentiment-analysis-app.default.10.99.46.8.sslip.io sentiment-analysis-app-00002 sentiment-analysis-app-00002 True -``` - -Please note: if your URL ends with .svc.cluster.local, that means you can only access the function from within the cluster. You probably forget to configure the network or [start the tunnel](https://knative.dev/docs/getting-started/quickstart-install/#__tabbed_3_2){:target="_blank"} if you are using minikube. - -### Step 7: Verify the Deployment -After deployment, the `func` CLI provides a URL to access your function. You can verify the function's operation by sending a request with a sample review comment. - -Simply use Knative function's command `func invoke` to directly send a CloudEvent to the function on your cluster: - -```bash -func invoke -f=cloudevent --data="i love knative community so much" -v -t="http://sentiment-analysis-app.default.10.99.46.8.sslip.io" -``` - -- `-f` flag indicates the type of the data, is either `HTTP` or `cloudevent` -- `--data` flag is the input text -- `-t` flag is the URI to the Knative Function. - -If the function is running successfully, you will see the following output: - -```bash -Context Attributes, - specversion: 1.0 - type: knative.sampleapp.sentiment.response - source: sentiment-analysis - id: 6a246e0c-2b24-4f22-93c4-f1265c569b2d - time: 2024-02-31T19:03:50.434822Z - datacontenttype: application/json -Data, - { - "input":"I love Knative so much!", - "result": "Positive" - } -``` - ---- -Recall note box: you can get the URL to the function by running the following command: -```bash -kubectl get kservice -A -``` ---- -Another option is to use curl to send a CloudEvents to the function. -Using curl command to send a CloudEvents to the Broker: -```bash -[root@curler:/]$ curl -v "http://sentiment-analysis-app.default.10.99.46.8.sslip.io" \ --X POST \ --H "ce-id: 12345" \ --H "ce-source: my-local" \ --H "ce-type: sentiment-analysis-request" \ --H "ce-specversion: 1.0" \ --H "Content-Type: application/json" \ --d '"I love Knative so much! Sent to the cluster"' -``` - -Expect to receive a JSON response indicating the sentiment classification of the input text. - -```bash -{ - "input":"I love Knative so much!", - "result": "Positive" -} -``` -If you see the response, it means that the function is running successfully. - ---- -### The magic time about Serverless: autoscaling to zero -If you use the following command to query all the pods in the cluster, you will see that the pod is running: - -```bash -kubectl get pods -A -``` -where `-A` is the flag to query all the pods in all namespaces. - -And you will find that your sentiment analysis app is running: - -```bash -NAMESPACE NAME READY STATUS RESTARTS AGE -default sentiment-analysis-app-00002-deployment 2/2 Running 0 2m -``` - -But if you wait for a while without sending any CloudEvent to your function, and query the pods again, you will find that the pod that has your sentiment analysis app **disappeared**! - - -This is because **Knative Serving's autoscaler** will automatically scale down to zero if there is no request to the function! ---- +Functions are automatically built, containerized, and pushed to a registry +before deployment. Subsequent deployments will update the existing function. -Congratulations! You have successfully set up the sentiment analysis service for your bookstore. +## Roadmap -## Conclusion +Our project roadmap can be found: https://github.com/orgs/knative/projects/49 -In this tutorial, you learned how to create a serverless function that contains a simple sentiment analysis service with Knative function. \ No newline at end of file diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/app.sh b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/app.sh deleted file mode 100755 index 4da37d4d8d2..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/app.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -exec python -m parliament "$(dirname "$0")" diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.py deleted file mode 100644 index 8d73da75f57..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.py +++ /dev/null @@ -1,61 +0,0 @@ -from parliament import Context -from flask import Request, request, jsonify -import json -from textblob import TextBlob -from time import sleep -from cloudevents.http import CloudEvent, to_structured - - -# The function to convert the sentiment analysis result into a CloudEvent -def create_cloud_event(inputText, badWordResult, data): - attributes = { - "type": "moderated-comment", - "source": "sentiment-analysis", - "datacontenttype": "application/json", - "sentimentResult": data, - "badwordfilter": badWordResult, - } - - # Put the sentiment analysis result into a dictionary - data = { - "reviewText": inputText, - "badWordResult": badWordResult, - "sentimentResult": data, - } - - # Create a CloudEvent object - event = CloudEvent(attributes, data) - - return event - - -def analyze_sentiment(text): - analysis = TextBlob(text["reviewText"]) - sentiment = "neutral" - if analysis.sentiment.polarity > 0: - sentiment = "positive" - elif analysis.sentiment.polarity < 0: - sentiment = "negative" - - badWordResult = "" - try: - badWordResult = text["badWordResult"] - except: - pass - # Convert the sentiment into a CloudEvent - sentiment = create_cloud_event(text["reviewText"], badWordResult, sentiment) - - return sentiment - - -def main(context: Context): - """ - Function template - The context parameter contains the Flask request object and any - CloudEvent received with the request. - """ - - print("Sentiment Analysis Received CloudEvent: ", context.cloud_event) - - # Add your business logic here - return analyze_sentiment(context.cloud_event.data) diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.yaml b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.yaml index 5431eb92763..5ac7d1e09c0 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.yaml +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/func.yaml @@ -1,8 +1,14 @@ +# $schema: https://raw.githubusercontent.com/knative/func/d04ff0a3/schema/func_yaml-schema.json +# yaml-language-server: $schema=https://raw.githubusercontent.com/knative/func/d04ff0a3/schema/func_yaml-schema.json specVersion: 0.36.0 name: sentiment-analysis-app runtime: python -created: 2024-02-01T00:18:00.06485162-04:00 +registry: localhost:5000 +namespace: default +created: 2025-09-20T16:25:54.767714524+02:00 +invoke: cloudevent build: builder: s2i deploy: namespace: default + image: localhost:5000/sentiment-analysis-app@sha256:fa2335ff00b780ae12a8d01e1512db874803e16ccba8ce0893223e023417b79a diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/__init__.py b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/__init__.py new file mode 100644 index 00000000000..c16dbac257c --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/__init__.py @@ -0,0 +1 @@ +from .func import new diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/func.py new file mode 100644 index 00000000000..8ed46f20b69 --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/function/func.py @@ -0,0 +1,61 @@ +import logging +from cloudevents.http import CloudEvent +from textblob import TextBlob +import textblob + +def new(): + return Function() + + +class Function: + async def handle(self, scope, receive, send): + """ Handle all HTTP requests to this Function. The incoming CloudEvent is in scope["event"]. """ + logging.info("Request Received") + + # 1. Get the incoming CloudEvent + request_event = scope["event"] + + # 2. Extract the data payload from the event, analyze and create CloudEvent + response_event = self.analyze_sentiment(request_event.data) + + # 3. Send the response + logging.info(f"Sending response: {response_event.data}") + await send(response_event) + + def create_cloud_event(self, inputText, badWordResult, data): + attributes = { + "type": "moderated-comment", + "source": "sentiment-analysis", + "datacontenttype": "application/json", + "sentimentResult": data, + "badwordfilter": badWordResult, + } + + # Put the sentiment analysis result into a dictionary + data = { + "reviewText": inputText, + "badWordResult": badWordResult, + "sentimentResult": data, + } + + return CloudEvent(attributes, data) + + def analyze_sentiment(self, text): + review_text = text.get("reviewText", "") + analysis = TextBlob(review_text) + sentiment = "neutral" + + if analysis.sentiment.polarity > 0: + sentiment = "positive" + elif analysis.sentiment.polarity < 0: + sentiment = "negative" + + badWordResult = "" + try: + badWordResult = text["badWordResult"] + except KeyError: + pass + + # Convert the sentiment into a CloudEvent + return self.create_cloud_event(review_text, badWordResult, sentiment) + diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/pyproject.toml b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/pyproject.toml new file mode 100644 index 00000000000..447dd7520ef --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "function" +description = "" +version = "0.1.0" +requires-python = ">=3.9" +readme = "README.md" +license = "MIT" +dependencies = [ + "httpx", + "cloudevents", + "pytest", + "pytest-asyncio", + "textblob", +] +authors = [ + { name="Your Name", email="you@example.com"}, +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.pytest.ini_options] +asyncio_mode = "strict" +asyncio_default_fixture_loop_scope = "function" + + diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/requirements.txt b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/requirements.txt deleted file mode 100644 index ab724e8a7a3..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/requirements.txt +++ /dev/null @@ -1,4 +0,0 @@ -Flask==3.0.2 -textblob==0.18.0.post0 -parliament-functions==0.1.0 -cloudevents==1.10.1 \ No newline at end of file diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/setup.py b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/setup.py deleted file mode 100644 index 2cdd67ede38..00000000000 --- a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/setup.py +++ /dev/null @@ -1,22 +0,0 @@ -from setuptools import setup, find_packages -from setuptools.command.install import install -import subprocess - - -class PostInstallCommand(install): - """Post-installation for installation mode.""" - def run(self): - # Call the superclass run method - install.run(self) - # Run the command to download the TextBlob corpora - subprocess.call(['python', '-m', 'textblob.download_corpora', 'lite']) - - -setup( - name="download_corpora", - version="1.0", - packages=find_packages(), - cmdclass={ - 'install': PostInstallCommand, - } -) diff --git a/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py new file mode 100644 index 00000000000..ad126f72302 --- /dev/null +++ b/code-samples/eventing/bookstore-sample-app/solution/ML-sentiment-analysis/tests/test_func.py @@ -0,0 +1,50 @@ +""" +Unit tests for the CloudEvent function implementation. +Tests both the function health endpoints and its handling of CloudEvents. + +Functions are currently served as ASGI applications using hypercorn, so we +use httpx for testing. To run the tests using poetry: + +poetry run python -m unittest discover +""" +import json +import asyncio +import pytest +from cloudevents.http import CloudEvent +from function import new + + +@pytest.mark.asyncio +async def test_func(): + f = new() # Instantiate Function to Test + + # A test CloudEvent + attributes = { + "id": "test-id", + "type": "com.example.test1", + "source": "https://example.com/event-producer", + } + data = {"message": "test message"} + event = CloudEvent(attributes, data) + + invoked = False # Flag indicating send method was invoked + + # Send + # confirms the Functions responds with a CloudEvent which echoes + # the data sent. + async def send(e): + nonlocal invoked + invoked = True # Flag send was invoked + + # Ensure we got a CloudEvent + assert isinstance(e, CloudEvent), f"Expected CloudEvent, got {type(e)}" + + # Ensure it echoes the data sent + assert e.data == data, f"Expected data {data}, got {e.data}" + + # Invoke the Function + scope = {"event": event} # Add the CloudEvent to the scope + await f.handle(scope, {}, send) + + # Assert send was called + assert invoked, "Function did not call send" diff --git a/code-samples/eventing/bookstore-sample-app/solution/README.md b/code-samples/eventing/bookstore-sample-app/solution/README.md index 944c0494177..c84566e217b 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/README.md @@ -19,18 +19,21 @@ Here's an overview of the components in the solution: ## Additional Files -- `setup.sh`: Script for setting up the required services including installing Knative, frontend, and backend node-server -- `solution.sh`: Script for installing everything, deploying the entire solution. **It includes the setup script as well.** +- `solution.sh`: Script for installing everything, deploying the entire solution. ## Running the Solution -1. Have a running Kubernetes cluster. -2. Install all the prerequisites and deploy the entire solution using the `solution.sh` script: - ``` +1. Have a locally running Kubernetes cluster e.g. [Kind](https://kind.sigs.k8s.io/docs/user/quick-start/#installation) or [Minikube](https://minikube.sigs.k8s.io/docs/start). +1. Have a locally running container registry, e.g. [Kind registry setup](https://kind.sigs.k8s.io/docs/user/local-registry/) or [Minikube registry setup](https://minikube.sigs.k8s.io/docs/handbook/registry/#enabling-insecure-registries). +1. Install all the prerequisites and deploy the entire solution using the `solution.sh` script: + + ```sh ./solution.sh ``` + If you encountered any permission issues, run the following command: - ``` + + ```sh chmod +x solution.sh ``` diff --git a/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js b/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js index 1a171a3c17d..78d2fb42b98 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js +++ b/code-samples/eventing/bookstore-sample-app/solution/node-server/index.js @@ -24,7 +24,7 @@ const pool = new Pool({ app.ws('/comments', (ws, req) => { console.log('WebSocket connection established on /comments'); - + // Function to send all comments to the connected client const sendComments = async () => { try { @@ -44,7 +44,7 @@ app.ws('/comments', (ws, req) => { // Optionally, you can trigger this function based on certain conditions // Here, we just send data immediately after connection and on an interval sendComments(); - const interval = setInterval(sendComments, 1000); // Send comments every 10 seconds + const interval = setInterval(sendComments, 1000); // Send comments every second ws.on('close', () => { console.log('WebSocket connection on /comments closed'); diff --git a/code-samples/eventing/bookstore-sample-app/solution/setup.sh b/code-samples/eventing/bookstore-sample-app/solution/setup.sh deleted file mode 100755 index e69de29bb2d..00000000000 diff --git a/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md b/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md index 467d27ff6e8..b64e12f408a 100644 --- a/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md +++ b/code-samples/eventing/bookstore-sample-app/solution/slack-sink/README.md @@ -1,63 +1,82 @@ # Bookstore Notification Service: with Apache Camel K and Knative Eventing - As a bookstore owner, you aim to receive instant notifications in a Slack channel whenever a customer submits a new review comment. By leveraging Knative Eventing and Apache Camel K, you can set up an event-driven service that automates these notifications, ensuring you're always informed. + ## What Knative features will we learn about? + - Knative's ability to connect with third-party services, such as Slack, through event-driven integration using Apache Camel K. ## What does the final deliverable look like? + When a CloudEvent with the type `new-review-comment` is sent to the Knative Eventing Broker, it triggers a message to be sent in a designated Slack channel. ## Install prerequisites -### Prerequisite 1: Install Camel CLI -Install the Camel K CLI (`kamel`) on your local machine. You can find the installation instructions [here](https://camel.apache.org/camel-k/2.4.x/cli/cli.html){:target="_blank"}. +### Prerequisite 1: Install Apache Camel-Kamelets -**Troubleshot**: If after installation you run `kamel version` and you get an error message, you may need to add the `kamel` binary to your system's PATH. You can do this by moving the `kamel` binary to a directory that is already in your PATH, or by adding the directory where `kamel` is located to your PATH. +Install Apache Camel K operator on your cluster using any of the methods listed in [the official installation docs](https://camel.apache.org/camel-k/2.8.x/installation/installation.html). We will use the installation via Kustomize: -```bash -$ export PATH=$PATH: +```sh +kubectl create ns camel-k && \ +kubectl apply -k github.com/apache/camel-k/install/overlays/kubernetes/descoped?ref=v2.8.0 --server-side ``` +Now you need to setup an `IntegrationPlatform` with a container registry. You can read more about it in [the official installation docs](https://camel.apache.org/camel-k/2.8.x/installation/installation.html#integration-platform). For all our needs we only need to create the `IntegrationPlatform` CR with a container registry entry. For example let's say we're using a Kind cluster with a local registry named `kind-registry` on port `5000`. Then your `IntegrationPlatform` CR will look like the following: -### Prerequisite 2: Install Apache Camel-Kamelets -Next, install Camel K on your cluster using the Camel K CLI: - -```bash -$ kamel install --registry docker.io --organization --registry-auth-username --registry-auth-password +```yaml +apiVersion: camel.apache.org/v1 +kind: IntegrationPlatform +metadata: + name: camel-k + namespace: camel-k # Make sure this is the namespace where your operator is running +spec: + build: + registry: + address: kind-registry:5000 + insecure: true ``` -Replace the placeholders with your actual Docker registry information. - -If you are using other container registries, you may need to read more [here](https://camel.apache.org/camel-k/2.4.x/installation/registry/registry.html){:target="_blank"} for the installation. +Install it with one command: -You will see this message if the installation is successful: - -``` -OLM is not available in the cluster. Fallback to regular installation. -Camel K installed in namespace default +```sh +cat <" \ -X POST \ -H "Ce-Id: review1" \ @@ -169,13 +191,14 @@ Using curl command to send a CloudEvent to the Broker: You can find the URI to your Broker by running the following command: ```bash -$ kubectl get broker book-review-broker +kubectl get broker book-review-broker NAME URL AGE READY REASON book-review-broker http://broker-ingress.knative-eventing.svc.cluster.local/default/book-review-broker 5m37s True ``` Wait a few seconds, and you should see a notification in your Slack channel. Congratulations! You have successfully set up the notification service for your bookstore. + ## Conclusion -In this tutorial, you learned how to set up an event-driven service that automates notifications to a Slack channel using Knative Eventing and Apache Camel K. By leveraging these technologies, you can easily connect your applications to third-party services, and pass information between them in real-time. +In this tutorial, you learned how to set up an event-driven service that automates notifications to a Slack channel using Knative Eventing and Apache Camel K. By leveraging these technologies, you can easily connect your applications to third-party services, and pass information between them in real-time. diff --git a/code-samples/eventing/bookstore-sample-app/solution/solution.sh b/code-samples/eventing/bookstore-sample-app/solution/solution.sh index 031fead8d87..ae263294666 100755 --- a/code-samples/eventing/bookstore-sample-app/solution/solution.sh +++ b/code-samples/eventing/bookstore-sample-app/solution/solution.sh @@ -52,35 +52,39 @@ then exit fi -# Detect whether the user has kamel CLI installed -if ! command -v kamel &> /dev/null -then - echo "" - echo "⚠️ Kamel CLI not found. Please install the Kamel CLI by following the instructions at https://camel.apache.org/camel-k/latest/installation/installation.html." - exit -fi - # Prompt for the Docker registry details echo "" -echo "📝 Please provide the details of your Container registry to install the Camel-K." -read -p "🔑 Enter the registry hostname (e.g., docker.io or quay.io): " REGISTRY_HOST -read -p "🔑 Enter the registry username: " REGISTRY_USER -read -s -p "🔑 Enter the registry password: " REGISTRY_PASSWORD +echo "📝 Please provide the details of your local running Container registry to install the Camel-K." +read -p "🔑 Enter the registry host (e.g. kind-registry): " REGISTRY_HOST +read -p "🔑 Enter the registry port: " REGISTRY_PORT echo "" echo "✅ All the required details have been captured and saved locally." # Set the registry details as environment variables export REGISTRY_HOST=$REGISTRY_HOST -export REGISTRY_USER=$REGISTRY_USER -export REGISTRY_PASSWORD=$REGISTRY_PASSWORD +export REGISTRY_PORT=$REGISTRY_PORT # Set the KO_DOCKER_REPO environment variable -export KO_DOCKER_REPO=$REGISTRY_HOST/$REGISTRY_USER +export KO_DOCKER_REPO=$REGISTRY_HOST/$REGISTRY_PORT # Install Camel-K echo "" echo "📦 Installing Camel-K..." -kamel install --registry $REGISTRY_HOST --organization $REGISTRY_USER --registry-auth-username $REGISTRY_USER --registry-auth-password $REGISTRY_PASSWORD +kubectl create ns camel-k && \ +kubectl apply -k github.com/apache/camel-k/install/overlays/kubernetes/descoped?ref=v2.8.0 --server-side + +cat < { console.log('WebSocket connection established on /comments'); - + // Function to send all comments to the connected client const sendComments = async () => { try { @@ -44,7 +44,7 @@ app.ws('/comments', (ws, req) => { // Optionally, you can trigger this function based on certain conditions // Here, we just send data immediately after connection and on an interval sendComments(); - const interval = setInterval(sendComments, 1000); // Send comments every 10 seconds + const interval = setInterval(sendComments, 1000); // Send comments every second ws.on('close', () => { console.log('WebSocket connection on /comments closed');