From 1c41481cfac5b51be267c795fa4f557b871b0b16 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 7 Apr 2025 12:17:09 +0800 Subject: [PATCH 1/4] Add polyglot client example --- .gitignore | 7 + Makefile | 3 + examples/910_polyglot_demo/Makefile | 118 ++ examples/910_polyglot_demo/README.adoc | 728 ++++++++++ examples/910_polyglot_demo/go/Dockerfile | 28 + examples/910_polyglot_demo/go/go.mod | 18 + examples/910_polyglot_demo/go/go.sum | 29 + examples/910_polyglot_demo/go/main.go | 127 ++ .../images/example-overview.png | Bin 0 -> 101753 bytes examples/910_polyglot_demo/js/.dockerignore | 2 + examples/910_polyglot_demo/js/Dockerfile | 21 + examples/910_polyglot_demo/js/main.js | 87 ++ .../910_polyglot_demo/js/package-lock.json | 1266 +++++++++++++++++ examples/910_polyglot_demo/js/package.json | 13 + examples/910_polyglot_demo/py/Dockerfile | 15 + examples/910_polyglot_demo/py/main.py | 100 ++ .../910_polyglot_demo/py/requirements.txt | 2 + .../yaml/coherence-cluster.yaml | 14 + .../910_polyglot_demo/yaml/go-client.yaml | 52 + .../910_polyglot_demo/yaml/js-client.yaml | 52 + .../910_polyglot_demo/yaml/py-client.yaml | 53 + examples/README.adoc | 10 + 22 files changed, 2745 insertions(+) create mode 100644 examples/910_polyglot_demo/Makefile create mode 100644 examples/910_polyglot_demo/README.adoc create mode 100644 examples/910_polyglot_demo/go/Dockerfile create mode 100644 examples/910_polyglot_demo/go/go.mod create mode 100644 examples/910_polyglot_demo/go/go.sum create mode 100644 examples/910_polyglot_demo/go/main.go create mode 100644 examples/910_polyglot_demo/images/example-overview.png create mode 100644 examples/910_polyglot_demo/js/.dockerignore create mode 100644 examples/910_polyglot_demo/js/Dockerfile create mode 100644 examples/910_polyglot_demo/js/main.js create mode 100644 examples/910_polyglot_demo/js/package-lock.json create mode 100644 examples/910_polyglot_demo/js/package.json create mode 100644 examples/910_polyglot_demo/py/Dockerfile create mode 100644 examples/910_polyglot_demo/py/main.py create mode 100644 examples/910_polyglot_demo/py/requirements.txt create mode 100644 examples/910_polyglot_demo/yaml/coherence-cluster.yaml create mode 100644 examples/910_polyglot_demo/yaml/go-client.yaml create mode 100644 examples/910_polyglot_demo/yaml/js-client.yaml create mode 100644 examples/910_polyglot_demo/yaml/py-client.yaml diff --git a/.gitignore b/.gitignore index f98e09801..ca54735b0 100644 --- a/.gitignore +++ b/.gitignore @@ -61,3 +61,10 @@ bundle.Dockerfile hack/istio*/ /java/certs/ .flattened-pom.xml + +# Examples noise +node_modules +.coh-history +.cohql-history +runner +venv diff --git a/Makefile b/Makefile index 617166cec..dd68ae7e2 100644 --- a/Makefile +++ b/Makefile @@ -907,6 +907,9 @@ copyright: ## Check copyright headers -X tools.go \ -X .tpl \ -X .txt \ + -X node_modules \ + -X venv \ + -X runner \ -X .yaml \ -X pkg/data/assets/ \ -X zz_generated. diff --git a/examples/910_polyglot_demo/Makefile b/examples/910_polyglot_demo/Makefile new file mode 100644 index 000000000..d9139a310 --- /dev/null +++ b/examples/910_polyglot_demo/Makefile @@ -0,0 +1,118 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# +CURRDIR := $(shell pwd) +NAMESPACE ?= coherence-demo +IMAGE_PREFIX ?= +IMAGE_VERSION ?= 1.0.0 +PLATFORM ?= linux/arm64 +GO_IMAGE ?= $(IMAGE_PREFIX)polyglot-client-go:$(IMAGE_VERSION) +PY_IMAGE ?= $(IMAGE_PREFIX)polyglot-client-py:$(IMAGE_VERSION) +JS_IMAGE ?= $(IMAGE_PREFIX)polyglot-client-js:$(IMAGE_VERSION) + + +# ====================================================================================================================== +# Makefile targets start here +# ====================================================================================================================== + +# ---------------------------------------------------------------------------------------------------------------------- +# Display the Makefile help - this is a list of the targets with a description. +# This target MUST be the first target in the Makefile so that it is run when running make with no arguments +# ---------------------------------------------------------------------------------------------------------------------- +help: ## Display this help. + @awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-25s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST) + + +# ====================================================================================================================== +# Build targets +# ====================================================================================================================== +##@ Build + +.PHONY: create-namespace +create-namespace: ## Create the namespace + kubectl create namespace $(NAMESPACE) || true + +.PHONY: delete-namespace +delete-namespace: ## Delete the namespace + kubectl delete namespace $(NAMESPACE) || true + +.PHONY: create-go-image +create-go-image: ## Create the Go Docker image + cd go && docker buildx build --platform $(PLATFORM) -t $(GO_IMAGE) . + +.PHONY: create-py-image +create-py-image: ## Create the Python Docker image + cd py && docker buildx build --platform $(PLATFORM) -t $(PY_IMAGE) . + +.PHONY: create-js-image +create-js-image: ## Create the JavaScript Docker image + cd js && docker buildx build --platform $(PLATFORM) -t $(JS_IMAGE) . + +.PHONY: kind-load-images +kind-load-images: ## Load images into kind + kind load docker-image $(GO_IMAGE) + kind load docker-image $(PY_IMAGE) + kind load docker-image $(JS_IMAGE) + +.PHONY: create-all-images +create-all-images: create-go-image create-js-image create-py-image ## Create all images + +.PHONY: push-image +push-image: ## Push the docker image + docker push $(IMAGE_REGISTRY)/$(DOCS_IMAGE):$(DOCS_VERSION) + +# ====================================================================================================================== +# Deploy targets +# ====================================================================================================================== +##@ Deploy + +.PHONY: deploy-operator +deploy-operator: ## Deploy the Coherence Operator + kubectl apply -f https://github.com/oracle/coherence-operator/releases/download/v3.4.3/coherence-operator.yaml + +.PHONY: undeploy-operator +undeploy-operator: ## Undeploy the Coherence Operator + kubectl delete -f https://github.com/oracle/coherence-operator/releases/download/v3.4.3/coherence-operator.yaml + +.PHONY: deploy-coherence +deploy-coherence: ## Deploy the Coherence Cluster + kubectl -n $(NAMESPACE) apply -f yaml/coherence-cluster.yaml + sleep 5 + kubectl -n $(NAMESPACE) get pods + +.PHONY: undeploy-coherence +undeploy-coherence: ## Deploy the Coherence Cluster + kubectl -n $(NAMESPACE) delete -f yaml/coherence-cluster.yaml + +.PHONY: deploy-go-client +deploy-go-client: ## Deploy the Go client + kubectl -n $(NAMESPACE) apply -f yaml/go-client.yaml + +.PHONY: undeploy-go-client +undeploy-go-client: ## Undeploy the Go client + kubectl -n $(NAMESPACE) delete -f yaml/go-client.yaml + +.PHONY: deploy-py-client +deploy-py-client: ## Deploy the Python client + kubectl -n $(NAMESPACE) apply -f yaml/py-client.yaml + +.PHONY: undeploy-py-client +undeploy-py-client: ## Undeploy the Python client + kubectl -n $(NAMESPACE) delete -f yaml/py-client.yaml + +.PHONY: deploy-js-client +deploy-js-client: ## Deploy the JavaScript client + kubectl -n $(NAMESPACE) apply -f yaml/js-client.yaml + +.PHONY: undeploy-js-client +undeploy-js-client: ## Deploy the JavaScript client + kubectl -n $(NAMESPACE) delete -f yaml/js-client.yaml + +.PHONY: deploy-all-clients +deploy-all-clients: deploy-go-client deploy-js-client deploy-py-client ## Deploy all clients + +.PHONY: undeploy-all-clients +undeploy-all-clients: undeploy-go-client undeploy-js-client undeploy-py-client ## Undeploy all clients diff --git a/examples/910_polyglot_demo/README.adoc b/examples/910_polyglot_demo/README.adoc new file mode 100644 index 000000000..e4afd6964 --- /dev/null +++ b/examples/910_polyglot_demo/README.adoc @@ -0,0 +1,728 @@ +/////////////////////////////////////////////////////////////////////////////// + + Copyright (c) 2025 Oracle and/or its affiliates. + Licensed under the Universal Permissive License v 1.0 as shown at + http://oss.oracle.com/licenses/upl. + +/////////////////////////////////////////////////////////////////////////////// += Polyglot Client Demo + +== Polyglot Client Demo + +This example shows how to deploy simple Python, JavaScript or Go applications that connect to Coherence running in Kubernetes. +The Coherence Operator is used to deploy a Coherence cluster and the applications connect via gRPC using the gRPC proxy. + +A basic REST serve is written in each language which exposes the following endpoints to create, get, update or remove JSON people objects in the Coherence cluster. + +* `POST /api/people` - create a person +* `GET /api/people` - return all people +* `GET /api/people/{id}` - return a single person +* `DELETE /api/people/{id}` - delete a person + +The example shows how to connect to the Coherence cluster from any of the clients via two different methods: + +1. From an application deployed with Kubernetes (purple processes shown below, accessed via `1. REST Client`) + +2. From an application outside of Kubernetes using simple port-forward or LBR (yellow gRPC Service, accessed via `2. Python, JS or Go Client`) + +**The diagram below outlines the example components.** + +NOTE: We use `port-forward` for this example, but you would normally expose the services via load balancers. + +image::images/example-overview.png[Service Details,width="100%",align="center"] + +See below for information on the Coherence langauge clients used: + +* https://github.com/oracle/coherence-py-client[Coherence Python Client] +* https://github.com/oracle/coherence-js-client[Coherence JavaScript Client] +* https://github.com/oracle/coherence-go-client[Coherence Go Client] + +== What the Example Will Cover + +* <> +** <> +** <> +** <> +** <> +* <> +** <> +** <> +** <> +** <> +* <> +** <> +** <> +** <> +* <> + + +[#pre] +=== PreRequisites + +You must have: + +1. Docker running on your system. Either Docker Desktop or Rancher will work +2. Access to a Kubernetes cluster. You can use `kind` to create a local cluster on Mac. See https://kind.sigs.k8s.io/[Kind Documentation] +3. `kubectl` executable - See https://kubernetes.io/docs/tasks/tools/[Kubernetes Documentation] + +NOTE: We have `Makefile` targets that will make the building and running of the example easier. + +[#pre-1] +==== Clone the Coherence Operator Repository: + +To build the examples, you first need to clone the Operator GitHub repository to your development machine. + +[source,bash] +---- +git clone https://github.com/oracle/coherence-operator + +cd coherence-operator/examples/910_polyglot_demo +---- + +[#pre-2] +==== Create the coherence-demo Namespace + +[source,bash] +---- +make create-namespace +---- + +[source,bash] +.Output +---- +kubectl create namespace coherence-demo || true +namespace/coherence-demo created +---- + +[#pre-3] +==== Install the Coherence Operator + +TIP: The Coherence Operator is installed installed into a namespace called `coherence`. To change this see the documentation below. + +[source,bash] +---- +make deploy-operator +---- + +[source,bash] +.Output +---- +kubectl apply -f https://github.com/oracle/coherence-operator/releases/download/v3.4.3/coherence-operator.yaml + +namespace/coherence created +customresourcedefinition.apiextensions.k8s.io/coherence.coherence.oracle.com created +... +service/coherence-operator-webhook created +deployment.apps/coherence-operator-controller-manager created +---- + +You can check to see the Coherence Operator in running by issuing the following command, which will wait for it to be available. + +[source,bash] +---- +kubectl -n coherence wait --for=condition=available deployment/coherence-operator-controller-manager --timeout 120s +---- + +When you see the following message, you can continue. + +[source,bash] +.Output +---- +deployment.apps/coherence-operator-controller-manager condition met +---- + +See the <> for more information about installing the Coherence Operator. + +[#pre-4] +==== Download Additional Software (Optional) + +If you are planning on running the clients locally, E.g. **not just from within the Kuberenetes cluster**, and connect to the cluster, you must install the following +software for your operating system. + +Otherwise, you can continue to the next step. + +1. Python 3.9 or Later - https://www.python.org/downloads/[https://www.python.org/downloads/] +2. Node 18.15.x or later and NPM 9.x or later - https://nodejs.org/en/download[https://nodejs.org/en/download] +3. Go 1.23 or later - https://go.dev/doc/install[https://go.dev/doc/install] + +TIP: If you are just going to run the example within your Kubernetes cluster then you do not need the software. + +[#deploy] +=== Deploy the Example + +[#dep-1] +==== Examine the Docker Files + +Each of the clients has a `Dockerfile` to build for the specific language client. You can inspect each of them below: + +**Python Client** + +[source,bash,indent=0] +---- +include::py/Dockerfile[tag=dockerfile] +---- + +**JavScript Client** + +[source,bash,indent=0] +---- +include::js/Dockerfile[tag=dockerfile] +---- + +**Go Client** + +[source,bash,indent=0] +---- +include::go/Dockerfile[tag=dockerfile] +---- + +[#code] +==== Examine the Code + +You can view the source of each of the language clients here: + +* link:py/main.py[py/main.py] +* link:js/main.js[js/main.js] +* link:go/main.go[go/main.go] + +[#dep-2] +==== Build the Example images + +TIP: If you are deploying to a remote cluster or deploying to a different architecture in Kubernetes, please read the *Important Notes* below before you build. + +Build each of the images using make, or run `make create-all-images`, as shown below. + +* `make create-py-image` +* `make create-js-image` +* `make create-go-image` + +[source,bash,indent=0] +---- + make create-all-images +---- + +[source,bash,indent=0] +.Output +---- +cd go && docker buildx build --platform linux/arm64 -t polyglot-client-go:1.0.0 . +[+] Building 27.8s (12/12) FINISHED +.... +cd js && docker buildx build --platform linux/arm64 -t polyglot-client-js:1.0.0 . +[+] Building 4.2s (10/10) FINISHED +... +cd py && docker buildx build --platform linux/arm64 -t polyglot-client-py:1.0.0 . +[+] Building 3.0s (8/8) FINISHED +---- + +You will end up with the following images: + +* `polyglot-client-py:1.0.0` - Python Client +* `polyglot-client-js:1.0.0` - JavaScript Client +* `polyglot-client-go:1.0.0` - Go Client + +**Important Notes** + +1. The images are built by default for arm64, you can specify `PLATFORM=linux/amd64` before your make commands to change the architecture. + +2. If you need to push the images to a remote repository, you will need to specify the IMAGE_PREFIX=.. before the make commands, e.g.: ++ +[source,bash,indent=0] +---- +IMAGE_PREFIX=ghcr.io/username/repo/ make create-py-image +---- ++ +[source,bash,indent=0] +.Output +---- +cd py && docker buildx build --platform linux/arm64 -t ghcr.io/username/repo/polyglot-client-py:1.0.0 . +... +---- ++ +NOTE: In this example the image will be `ghcr.io/username/repo/polyglot-client-py:1.0.0`. You will also need to update the deployment yaml files. + +[#dep-3] +==== Push Images + +Choose one of the following methods, depending upon if you are using a local *kind* cluster or not. + +**You are running a local cluster using kind** + +Run the following to load the images you just created to the cluster. + +[source,bash] +---- +make kind-load-images +---- + +**You need to push to a remote repository** + +1. Push the images to your local container repository. E.g. for the example above: ++ +[source,bash] +---- +docker push ghcr.io/username/repo/polyglot-client-py:1.0.0 +docker push ghcr.io/username/repo/polyglot-client-js:1.0.0 +docker push ghcr.io/username/repo/polyglot-client-go:1.0.0 +---- + +2. Modify the following files to change the image name accordingly in the following deployment yaml files: ++ +* link:yaml/py-client.yaml[py-client.yaml] +* link:yaml/js-client.yaml[js-client.yaml] +* link:yaml/go-client.yaml[go-client.yaml] + +[#dep-4] +==== 4. Deploy the Coherence Cluster + +The following deployment file is used to deploy a 3 node Coherence cluster to your Kubernetes cluster. + +[source,bash,indent=0] +---- +include::yaml/coherence-cluster.yaml[] +---- +<1> the cluster name +<2> the number of replicas to create +<3> Image to use to start Coherence +<4> Enable management +<5> Enable gRPC port + +NOTE: When we deploy this yaml, each of the ports will become a service that we can use to connect to. + +Deploy the Coherence Cluster using the following: + +[source,bash,indent=0] +---- +make deploy-coherence +---- + +[source,bash,indent=0] +.Output +---- +kubectl -n coherence-demo apply -f yaml/coherence-cluster.yaml +coherence.coherence.oracle.com/demo-cluster created +sleep 5 +kubectl -n coherence-demo get pods +NAME READY STATUS RESTARTS AGE +demo-cluster-0 0/1 Running 0 5s +demo-cluster-1 0/1 Running 0 5s +demo-cluster-2 0/1 Running 0 5s +---- + +Issue the following command to wait until the Coherence cluster is ready. + +[source,bash,indent=0] +---- +kubectl -n coherence-demo wait --for=condition=ready coherence/demo-cluster --timeout 120s +coherence.coherence.oracle.com/demo-cluster condition met +---- + +[#run-example] +=== Run the example + +Choose one of the methods below to demo the application. + +1. <> +2. <> + +[#run-1] +==== Run from within Kubernetes + +In this section, we will deploy all the client pods to Kubernetes, and access the REST endpoints via their services, using the `port-forward` command. + +See **1. REST Client** on the right of the diagram below: + +image::images/example-overview.png[Service Details,width="100%",align="center"] + +NOTE: We are accessing the application HTTP endpoint via port-forward, but the client pods within Kubernetes +are directly accessing cluster within Kubernetes. + +When we deploy the clients, the yaml used is shown below: + +* link:yaml/py-client.yaml[Python Client] +* link:yaml/js-client.yaml[JavaScript Client] +* link:yaml/go-client.yaml[Go Client] + +By default, the Python, JavaScript and Go clients connect to `localhost:1408` on startup, but you can specify the gRPC host and port to connect to in the +code or use the `COHERENCE_SERVER_ADDRESS` environment variable to specify this which is more flexible. + +Each of the clients, (Python shown below for example), have this variable set to `demo-cluster-grpc:1408` where `demo-cluster-grpc` +is the service for the grpc port created when we deployed the Coherence cluster. + +[source,yaml,indent=0] +---- +include::yaml/py-client.yaml[tag=yaml] +---- + +**Deploy the clients** + +Firstly, run the following to deploy all the clients. This yaml also deploys a service from which you can connect to the client. + +[source,bash,indent=0] +---- +make deploy-all-clients +---- + +[source,bash,indent=0] +.Output +---- +kubectl -n coherence-demo apply -f yaml/go-client.yaml +service/go-client created +deployment.apps/go-client created +kubectl -n coherence-demo apply -f yaml/js-client.yaml +service/js-client created +deployment.apps/js-client created +kubectl -n coherence-demo apply -f yaml/py-client.yaml +service/py-client created +deployment.apps/py-client created +---- + +Issue the following to show the deployments and services. + +[source,bash,indent=0] +---- +kubectl get deployments -n coherence-demo +---- + +[source,bash,indent=0] +.Output +---- +NAME READY UP-TO-DATE AVAILABLE AGE +go-client 1/1 1 1 3s +js-client 1/1 1 1 3s +py-client 1/1 1 1 3s +---- + +**Port forward to access the HTTP endpoint** + +To port-forward the clients we will first need to view the services: + +[source,bash,indent=0] +---- +kubectl get services -n coherence-demo +---- + +[source,bash,indent=0] +.Output +---- +NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE +demo-cluster-grpc ClusterIP 10.96.200.57 1408/TCP 8m2s +demo-cluster-management ClusterIP 10.96.46.69 30000/TCP 8m2s +demo-cluster-sts ClusterIP None 7/TCP,7575/TCP,7574/TCP,6676/TCP,1408/TCP,30000/TCP 8m2s +demo-cluster-wka ClusterIP None 7/TCP,7575/TCP,7574/TCP,6676/TCP 8m2s +go-client-http ClusterIP 10.96.249.42 8080/TCP 32s +js-client-http ClusterIP 10.96.114.88 8080/TCP 31s +py-client-http ClusterIP 10.96.196.163 8080/TCP 31s +---- + +The services we are interested in are the `go-client-http`, `js-client-http` or `py-client-http`. + +As all the clients expose the same API, You can choose any of the clients to port-forward to. For this example will +choose the JavaScript client. + +[source,bash,indent=0] +---- +kubectl port-forward -n coherence-demo svc/js-client-http 8080:8080 +---- + +[source,bash,indent=0] +.Output +---- +Forwarding from 127.0.0.1:8080 -> 8080 +Forwarding from [::1]:8080 -> 8080 +---- + +[#rest-endpoints] +**Exercise the REST endpoints** + +Use the following commands to work with the REST endpoints. + +**Create two People** + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/json' http://localhost:8080/api/people -d '{"id": 1,"name":"Tim", "age": 25}' +---- + +[source,bash] +---- +curl -X POST -H 'Content-Type: application/json' http://localhost:8080/api/people -d '{"id": 2,"name":"John", "age": 35}' +---- + +*List the people in the cache* + +[source,bash] +---- +curl http://localhost:8080/api/people +---- + +[source,bash] +.Output +---- +[{"id":1,"name":"Tim","age":25},{"id":2,"name":"John","age":35}] +---- + +*Get a single Person* + +[source,bash] +---- +curl http://localhost:8080/api/people/1 +---- + +[source,bash] +.Output +---- +{"id":1,"name":"Tim","age":25} +---- + +*Remove a Person* + +[source,bash] +---- +curl -X DELETE http://localhost:8080/api/people/1 +---- + +[source,bash] +.Output +---- +OK +---- + +*Try to retrieve the deleted person* + +[source,bash] +---- +curl http://localhost:8080/api/people/1 +---- + +[source,bash] +.Output +---- +Not Found +---- + +[#run-2] +==== View the cache information + +You can use the https://github.com/oracle/coherence-cli[Coherence CLI] to view cluster and cache information. + +NOTE: The Coherence CLI is automatically bundled with the Coherence Operator and can be accessed via `kuebctl exec`. + +**Display the cluster members** + +Use the following to show the cluster members: + +[source,bash] +---- +kubectl exec demo-cluster-0 -c coherence -n coherence-demo -- /coherence-operator/utils/cohctl get members +---- + +[source,bash] +.Output +---- + +Total cluster members: 3 +Storage enabled count: 3 +Departure count: 0 + +Cluster Heap - Total: 8,964 MB Used: 150 MB Available: 8,814 MB (98.3%) +Storage Heap - Total: 8,964 MB Used: 150 MB Available: 8,814 MB (98.3%) + +NODE ID ADDRESS PORT PROCESS MEMBER ROLE STORAGE MAX HEAP USED HEAP AVAIL HEAP + 1 demo-cluster-wka.coherence-demo.svc/10.244.0.31 7575 56 demo-cluster-0 demo-cluster true 2,988 MB 45 MB 2,943 MB + 2 demo-cluster-wka.coherence-demo.svc/10.244.0.32 7575 57 demo-cluster-2 demo-cluster true 2,988 MB 36 MB 2,952 MB + 3 demo-cluster-wka.coherence-demo.svc/10.244.0.33 7575 57 demo-cluster-1 demo-cluster true 2,988 MB 69 MB 2,919 MB +---- + +**Display the cache information** + +Use the following to show the cache information: + +[source,bash] +---- +kubectl exec demo-cluster-0 -c coherence -n coherence-demo -- /coherence-operator/utils/cohctl get caches -o wide +---- + +NOTE: You will see other system caches, but you should also see the `people` cache with one entry. + +[source,bash] +.Output +---- +Total Caches: 7, Total primary storage: 0 MB + +SERVICE CACHE COUNT SIZE AVG SIZE PUTS GETS REMOVES EVICTIONS HITS MISSES HIT PROB +"$SYS:Concurrent" executor-assignments 0 0 MB 0 0 0 0 0 0 0 0.00% +"$SYS:Concurrent" executor-executors 2 0 MB 1,248 12,105 12,103 1 0 12,103 0 100.00% +"$SYS:Concurrent" executor-tasks 0 0 MB 0 0 0 0 0 0 0 0.00% +"$SYS:Concurrent" locks-exclusive 0 0 MB 0 0 0 0 0 0 0 0.00% +"$SYS:Concurrent" locks-read-write 0 0 MB 0 0 0 0 0 0 0 0.00% +"$SYS:Concurrent" semaphores 0 0 MB 0 0 0 0 0 0 0 0.00% +PartitionedCache people 1 0 MB 224 6 29 2 0 26 3 89.66% +---- + +[#run-3] +==== Run locally using native clients + +Depending upon the client you are wanting to run you need to ensure you have installed the relevant +client software as shown in the <>. + +TIP: Ensure you stopped the port-forward from the previous step if you ran this. + +See **2. Python, JS or GO Client** on the bottom of the diagram below: + +image::images/example-overview.png[Service Details,width="100%",align="center"] + + +**Run Port forward** + +Firstly we have to run a `port-forward` command to port-forward the gRPC 1408 locally to the `demo-cluster-grpc:1408` port on the Kubernetes cluster. + +Typically, if you want to access a service from outside the Kubernetes cluster, you would have a load balancer configured to access this directly, but in example we will use `port-forward`. + +Run the following: + +[source,bash] +---- +kubectl -n coherence-demo port-forward svc/demo-cluster-grpc 1408:1408 +---- + +[source,bash] +.Output +---- +Forwarding from 127.0.0.1:1408 -> 1408 +Forwarding from [::1]:1408 -> 1408 +---- + +the **DIAGARM**... + +Then follow the instructions to start either of the clients below, which will list on port 8080 locally and connect to the Coherence cluster via gRPC on localhost:1408 which will be port-forwarded. + + +**Python Client** + +NOTE: We are install a python virtual environment for this example. + +1. Change to the `py` directory + +2. Create a python virtual environment ++ +[source,bash] +---- +python3 -m venv ./venv +. venv/bin/activate +---- + +3. Install the requirements ++ +[source,bash] +---- +python3 -m pip install -r requirements.txt +---- + +4. Run the Python example ++ +[source,bash] +---- +python3 main.py +---- ++ +[source,bash] +.Output +---- +2025-04-07 11:06:42,501 - coherence - INFO - Session [5d940a05-1cfc-4e6c-9ef8-52cc6e7705ba] connected to [localhost:1408]. +2025-04-07 11:06:42,525 - coherence - INFO - Session(id=5d940a05-1cfc-4e6c-9ef8-52cc6e7705ba, connected to [localhost:1408] proxy-version=14.1.2.0.1, protocol-version=1 proxy-member-id=1) +[2025-04-07 11:06:42 +0800] [27645] [INFO] Running on http://0.0.0.0:8080 (CTRL + C to quit) +---- ++ +NOTE: This is now showing the HTTP server is running locally and connecting via port-forward to the Coherence Cluster. + +5. Exercise the REST end-points as per the instructions <> + +**JavaScript Client** + +1. Change to the `js` directory + +2. Install the modules ++ +[source,bash] +---- +npm install +---- + +3. Run the JavaScript example ++ +[source,bash] +---- +node main.js +---- + +4. Exercise the REST end-points as per the instructions <> + +**Go Client** + +1. Change to the `go` directory + +2. Ensure you have the latest Coherence Go client ++ +[source,bash] +---- +npm install +---- + +3. Build the executable ++ +[source,bash] +---- +go get github.com/oracle/coherence-go-client/v2@latest +go mod tidy +---- + +4. Run the Go example ++ +[source,bash] +---- +/runner +---- ++ +[source,bash] +.Output +---- +2025/04/07 11:19:21 INFO: Session [2073aa45-68aa-426d-a0b8-99405dcaa942] connected to [localhost:1408] Coherence version: 14.1.2.0.1, serverProtocolVersion: 1, proxyMemberId: 1 +Server running on port 8080 +---- + +5. Exercise the REST end-points as per the instructions <> + + +[#cleanup] +=== Cleaning Up + +1. Undeploy the clients using: ++ +[source,bash] +---- +make undeploy-all-clients +---- + +2. Undeploy the Coherence cluster ++ +[source,bash] +---- +make undeploy-coherence +---- + +3. Undeploy the Coherence Operator ++ +[source,bash] +---- +make undeploy-operator +---- + +4. Delete the namespace ++ +[source,bash] +---- +make delete-namespace +---- + + + diff --git a/examples/910_polyglot_demo/go/Dockerfile b/examples/910_polyglot_demo/go/Dockerfile new file mode 100644 index 000000000..eaab49a45 --- /dev/null +++ b/examples/910_polyglot_demo/go/Dockerfile @@ -0,0 +1,28 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# tag::dockerfile[] + +FROM golang:1.24 as builder + +# Set working dir inside the container +WORKDIR /app + +# Copy your Go module files and source code +COPY go.mod ./ +COPY go.sum ./ +COPY main.go ./ + +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o runner . + +FROM scratch + +COPY --from=builder /app/runner /files/runner + +EXPOSE 8080 +ENTRYPOINT ["/files/runner"] +CMD ["-h"] + +# end::dockerfile[] \ No newline at end of file diff --git a/examples/910_polyglot_demo/go/go.mod b/examples/910_polyglot_demo/go/go.mod new file mode 100644 index 000000000..1f31b9c30 --- /dev/null +++ b/examples/910_polyglot_demo/go/go.mod @@ -0,0 +1,18 @@ +module main + +go 1.23.0 + +toolchain go1.24.1 + +require github.com/oracle/coherence-go-client/v2 v2.1.0 + +require ( + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.6.0 // indirect + golang.org/x/net v0.36.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 // indirect + google.golang.org/grpc v1.58.3 // indirect + google.golang.org/protobuf v1.36.1 // indirect +) diff --git a/examples/910_polyglot_demo/go/go.sum b/examples/910_polyglot_demo/go/go.sum new file mode 100644 index 000000000..45f9c7436 --- /dev/null +++ b/examples/910_polyglot_demo/go/go.sum @@ -0,0 +1,29 @@ +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/onsi/gomega v1.36.2 h1:koNYke6TVk6ZmnyHrCXba/T/MoLBXFjeC1PtvYgw0A8= +github.com/onsi/gomega v1.36.2/go.mod h1:DdwyADRjrc825LhMEkD76cHR5+pUnjhUN8GlHlRPHzY= +github.com/oracle/coherence-go-client/v2 v2.1.0 h1:EQ9kmwJP9ERA/TtdsIG/mEDyUMm596FxM2pKR+9l1zw= +github.com/oracle/coherence-go-client/v2 v2.1.0/go.mod h1:Myuz1GfRntOmNTV2tdFLSFJafmL1Iq1PJiwXZjGCmhI= +golang.org/x/net v0.36.0 h1:vWF2fRbw4qslQsQzgFqZff+BItCvGFQqKzKIzx1rmoA= +golang.org/x/net v0.36.0/go.mod h1:bFmbeoIPfrw4sMHNhb4J9f6+tPziuGjq7Jk/38fxi1I= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98 h1:bVf09lpb+OJbByTj913DRJioFFAjf/ZGxEz7MajTp2U= +google.golang.org/genproto/googleapis/rpc v0.0.0-20230711160842-782d3b101e98/go.mod h1:TUfxEVdsvPg18p6AslUXFoLdpED4oBnGwyqk3dV1XzM= +google.golang.org/grpc v1.58.3 h1:BjnpXut1btbtgN/6sp+brB2Kbm2LjNXnidYujAVbSoQ= +google.golang.org/grpc v1.58.3/go.mod h1:tgX3ZQDlNJGU96V6yHh1T/JeoBQ2TXdr43YbYSsCJk0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk= +google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/examples/910_polyglot_demo/go/main.go b/examples/910_polyglot_demo/go/main.go new file mode 100644 index 000000000..abe5937a6 --- /dev/null +++ b/examples/910_polyglot_demo/go/main.go @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/oracle/coherence-go-client/v2/coherence" + "log" + "net/http" + "strconv" +) + +type Person struct { + ID int `json:"id"` + Name string `json:"name"` + Age int `json:"age"` +} + +var ( + cache coherence.NamedMap[int, Person] + ctx = context.TODO() +) + +func main() { + session, err := coherence.NewSession(ctx, coherence.WithPlainText()) + if err != nil { + log.Println("unable to connect to Coherence", err) + return + } + defer session.Close() + + cache, err = coherence.GetNamedMap[int, Person](session, "people") + if err != nil { + log.Println("unable to create namedMap 'people'", err) + return + } + + http.HandleFunc("/api/people", personHandler) + http.HandleFunc("/api/people/", personByIDHandler) + + fmt.Println("Server running on port 8080") + log.Fatal(http.ListenAndServe(":8080", nil)) + +} + +func personHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPost: + var p Person + if err := json.NewDecoder(r.Body).Decode(&p); err != nil { + http.Error(w, "Invalid JSON", http.StatusBadRequest) + return + } + + _, err := cache.Put(ctx, p.ID, p) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + w.WriteHeader(http.StatusCreated) + _ = json.NewEncoder(w).Encode(p) + + case http.MethodGet: + var people = make([]Person, 0) + + for ch := range cache.Values(ctx) { + if ch.Err != nil { + http.Error(w, ch.Err.Error(), http.StatusInternalServerError) + return + } + people = append(people, ch.Value) + } + _ = json.NewEncoder(w).Encode(people) + return + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func personByIDHandler(w http.ResponseWriter, r *http.Request) { + idStr := r.URL.Path[len("/api/people/"):] + id, err := strconv.Atoi(idStr) + if err != nil { + fmt.Println(err) + http.Error(w, "Invalid ID", http.StatusBadRequest) + return + } + + switch r.Method { + case http.MethodGet: + p, err1 := cache.Get(ctx, id) + + if err1 != nil { + http.Error(w, err1.Error(), http.StatusInternalServerError) + } + if p == nil { + http.NotFound(w, r) + return + } + w.Header().Set("Content-Type", "application/json") + err1 = json.NewEncoder(w).Encode(&p) + if err1 != nil { + http.Error(w, err1.Error(), http.StatusInternalServerError) + return + } + + case http.MethodDelete: + old, err2 := cache.Remove(ctx, id) + if err2 != nil { + http.Error(w, err2.Error(), http.StatusInternalServerError) + return + } + if old == nil { + http.NotFound(w, r) + return + } + w.WriteHeader(http.StatusNoContent) + + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} diff --git a/examples/910_polyglot_demo/images/example-overview.png b/examples/910_polyglot_demo/images/example-overview.png new file mode 100644 index 0000000000000000000000000000000000000000..de4f76f3533943a1c19e1033cee99a5ebbec7e56 GIT binary patch literal 101753 zcmeEu2|Uzm+rP1ADZ7MHB9e6&BwIp~U6w43WvpZ02~$dC*J6!|ELq1kw#X96T7Y*g5RJS{M66^KMcTsVWD%vV&}wp!JqQZ&Paql!d%tX9XAS8TmT9K%{;0n zb=6LDi70@-k+${-@JktCVef={#nRQxOTg9%bQggM2nz^(Z4#8&m)a+H}mU%v^1^O^4f+C&Jau7Hn~2!}x)5AGleVArYI+U@BaT zxs$6U!gaF=%pt-BRpb(q2Or@6K@}`*&8%I`9KeI48CcZDfEr+UPg{i7<^Z;qo2@5K zIh^*=64UX3>6-aiTAn*4stZo-+wt(DI(RF)nmOC3IazL;sHOMTl7z*@HV3uz*=!M) z00)R$rnM`6o~?D`8gy)Z@tfILI_TzMYl(2%8g27UcPA&LyRGxLjTTOhj(`om>1gKa z>g4rpH!COP*6y}ubq4(M%}cljogWSr*Q#%8=?*vt$8r)P;+t#1wVy=TTHD}f7KTY~ ze&S$;@4U4WHyblcC$G)-f1u0eIpR;#)yWBrw~cJuKgM@*@K!(|aqNpf$E|q)=lsv# z{pB=Vf03U5pW!SKb`D}EoJ8Hu%3F$i@+*kAX>0KVs^bXnX@>OJI`Yj1H+LUA3%fdb zI9eib6auVz+{?z+9iiiFW`XPO1^5fJ*|<9(!ISWY1aAz7aCJxE*$q#WUw5&wv44J) zzaFSE0?4Vms}EQue#EU)*y3Ih=+?8>HeSHD*=*wjsQ6Y#vn~F!{$*&~qJImGK%XrV z|MTVJF!4K1`04QjZ29i^RXxlRu8s(IAYTBakRERC05>)|Z6gUdT)>OR-2lScBNUvF zPB>_BbaDhVIc|ouwZ>tR1z;K+ctDv&AP{yowv`k0U$`_8sMMM8lomHp&Z&ZnbTvyx3OTUx)CbKM-EQ$p!&L1jrRo zd(H?~Gj~AM?+FjU1%T{7BF{Fmnz=b|BCnOLH%@lH;Q}rZsFf80W?=!^zvUtDiKV!t zxe!j$w^sOvQdDFMn72e3iYL@If=c{^p7^%E4n4Ot>NXtRPS0QDSV+^!63}-`TDD0v zP(;o+M0Iv`vOu^2eEO;UZ6mO`nT5UehCFI`xFcpBoFRWloGdG(Jiy3DU;~q54+<`HMGt0nKLK2{L zJ5Fy|ZhwZt6&Cvrh<~DSh4FUsb|8l0d}ADx{dJI77;kj`>b(3^>3#>m|E0#Wu;jOt z7yFsG;)%SS@^}vU3lV-xt8PQ)ALJF`Xeo~iHQ+oUq?wzW?WPfA=IZ`q^ER{UTQT1p z6=C^9IO02B7ASZp57!Ns7`*aRPjAEE|L0C4jv%;b5Qy9S;+SrCZSaKn8F5?@NHcd^ zPn;D9R}eHjiQJEu7r_Yo~g1vVS<` z_kabJ{0@iQ&0MV!?*EG^p&(KSy!l_w`k$Z7R__g`{$KU{ZN9(okIuiQkGl;nW(pjC zTU^ZN&$1m6!j@tPaiEoc@CGGe;!rc#pX4Qo;%STb67Y)X?`Kg6|LEYKGV?Ukv2e9@ z#tGE_5g3P4P`rEhHze>D5^mE$${@k;ovralx%GchffdE8sJ|aQaS8}WylwQf*4D%& zlXP%-7+hHHJKFwgD&VaAf8h~qH}^NfZ&o7zAp8b1hl$|;7bJu%aY_Imy4`roQWExO zfiYBkOF!Yk_$Rc5ZFSjhk%-_y9B&i+`=ji5duSVNf1u{?VlN`RZQTuUfj9vnAsn4> z&xYVxNI(>ZbNX=4CvdhDF21_)fIHlc2S6iDwh?)pj zTaY5z$S>kv-`v~pVzq8L#+x=4SREMpr`GMJ^!#(X=vNc(7nAFo$UUCFwlfpH?awi9 zp?Gr^ce-G!e}Z`n#Ro#Sru^j@FFk)-^A-n}-vR1o!?p<7?=5tqcp=*q9$|635dJDX zI0^idgy%m)C`9oIH9UvnWfIS!zrWA-y&nJt`KS$E!cY146^b8yzW*el_`53rQCuYv z4z%#RE-56iW#{8r?+@U1iLFEfeo_g^?or2jsH=X-t^mH0RC zyQuho5WoMJ9X_e6tH~t-L*fc{%w54>*0{g^2&WRn@FXln!Qekc@gzjdp?{j=_(PS> zPojAErtPecj|Jh8_AlT!{+I#&uczkELUw->YW|qz{jaCy&jNOT6KZZ%j{TW2oj=jN z{JUGyFnlH%Z`2C?36=#6ww3tYnp8ycKgY7bho=6I-|^Mp+j0#5eoOiXe*ZH($M5+a z`XA&uLVqmo`9II`uj4s>&+mVV=lDIp|0j8l&>tg%A_5>1g)a%gWg<`MfFHP90YbP` z^FOR1ut&IC;O<1>azW0x0v?2`q9;fq;cmU)QkB14dct-1rBo63u&og4TOV?(Lh-vS zrYKZY6jTI(J15`YHb9!0BaoU-Zno~57vz4qjDo*za{9Xt=1%VJP7Yuwd;aiHXPb~nJ~O~&8G zeiGnL>rWE*f4%q%^v4>T-(~}B!?9nhG;j#9kxIv<*>O0y@nDDpN)T`V=V|uMX@8fL zI$osyWlkNs5wG~WJAxAUa01>D+}zf$b>zQ)LPZi^%Y%0VCAK0zcwGHor&I7H44{PA z40jU(@ti%kAQt(25wIN(!tW!6CY^j!NvH%cK~d@ z365mjXFr0;Z@pgmogoA+<4IUTaTdroH*!JAmIyGB#Bld7H|P3e%@1)r*yA||f7xRj z`2Y0o75+UuKmYqf?|7?e8$0|+t)G?n|7++M5|Du5YMr;izc4V`{<#9!==^=56~5eS zL$lyZy>J8l!!obUJ^Vg;9Vq_g5nl-{UY| zt>CBpU#pRSH`a>&Z(}R}TDbm)cGXWMXww?|Yst_?Ch+esL|d5gix6!kIPo0)`wNjc z9?0=i;{6l+lz(3#64_GezurFl|J6`{<$oyw-clzU^7Icy;jiG%{AM+@(edvtLgM)9 z_AP!E75c;L!@d_GQM@Yr`-{+zG0)Aw5)iHJUzzaNxUst>?tkd_p-AFsi-${mP-dG2 zk8j!@ImBlN@htHdFyMcT-TXfO9PU=me~CW_6@>|iej82}!`r>v7~*f@(uw_Vtxh*! zBm7TpZU3Sbx5f1gx5xZ`y$$fgU78L3`-=gJdkgQ}{rVD49pmH^_sJ$G{4NgN=H2SQ zyMh!I5*Lu%${K8e7z__;cy#*x%}k(%zn@hMZ+YTp{o7iaVm~Hfew(HFKNcLk0s4&q z{BslZcNYK=p-n;o0RvZFc`5zq_7u)i%wuM4BZzliOTFHP9ez1%1Xur)a!pqZN+fWw!4TY@V zCNclTRr6meI&i@Km-_OlUl48!enjvQ)@?)+#yi;GPz?{2cxe3h`!{&z+9s9io8Q1A zB4n}g?JXPmz`vj$`=@_xt0+E!xkYv{ytlfY?08MIo$Pq__#>&l5sUu^-|dW#)BpM{ z+S_f8jZg94+`ak1)(apz2)>93_f^rcfgITc1RMk^C**a#O{bDbyy;td8_%(wy=~sW z9%5`PkjL!hcCS8t$Kaf@XupEj%t->-<1j|75!!qIGVBmr9 zYy>+G0*$zJXV0M@NNdC+hjO62^?;fX8S*35pFIRCOz_2}nC*--z=QUgw-R-F$u`;;ULPlH zQG>c(^|IPusyT)~RN>0b+S=Ov&FxfsM9GV+I~o@zd!uCo0|wlW8uP8M46oDd-hK6D zm0xJjTBX@MQ0~e_g_1UZOrAWww)zE1x@&LPPWGeD4j9LwX0uQ4x-yP&$^@>R zmynXO=qp4Rlsd8>^_pp2m$mIYcsfo9LPE*(I9Bi++H2NyC+0|YGM6e78aaEDR|oAm zeb%wmw90o-pe0!&`54PSne%c(qy%6Fg{Fxo$|NJFe-Jl z=}M>W7+;#|qX)-xJaVtJx2!>WLajtsf#1?hrb#7ikGNy2H3iz!U@d{LlcihczXH3?t=3Gow7g(1At`xJ zJMEN2z$*Cn`2rxl8$p%(yX&-QL&KP9g!9upDX*DV-P!s)CKaM&CrM)tso$1szb*NC za9X2@3r63C3-26ZZ3zGrXXMk%At2Ht4LV}vKle(0=iMA?QY@7@wc)h)hY^?E*3Nrn1J3XK zLe<1FXWbNa!MfJtD0>GF6m3Pr&c@bS`$9%u0Fkq&@H6wTzH_f2PSCtG4FXTrl;y zGfnl2yPr-?o^g3$pRXE7qcoFPC&U+W~-YtRlCA}A9?ZR>hz#2xw>0JFS8w@np!bmhn-?rDSHM5Oik zI+E5?DfKPe5W)2`H6b%IGiC&dB~M{S7A%vY%z_`OU@Njg{-pCc)ev-F-Y}A1v?Zk| z?5I&?TVoW=;jBzmlA2WD+Nz{_^0BAA=1?iBS8C9@9O|BPo>e~cqyRlf`y62lx5F=7 zfEnpH5Fqs;awn|1>ia}pg3_tbWINfKdXXzDE2HCWiN_S$-WTP_bYU6L)U4U@sj*gs zLf0?Q!0r1gn^{;P7}xXi()+h*F*ztj93d=mXPZbJDb`Y`Dcz$8dY2_m#0H^L$s!a? zhKyOTV)ZQ$YoOv_F@|a)&@Ff45e_Wp3oA(FN{G?ab5Hx>iQxreEl4+!ef#z$gWlH) z)VkJ|Ck3mSZZBLxj2dEY=hj;#UK2K$=}eN(Z)hzic)pcfe)j+j)-f8U)wul95| zL6IO8mYJNYu}{|xwlL@(B9XY3#HqZ~G^Wp*HNPMf0yAQ6Uw*pWWLgv^y+VgtC^4%K zc6N7v)AgiJ*AvG0(zr^L9z~2j>zS7F1aPs4#jU@I4>api!{fL2bqG%A_J; zVH+#v#E;d1>j67QsG1B}kEA-4VH2G6Vsckt=Ur+V{im?PMGM;2#qquN&G5tw`df7A zz(y4MnhDzuO7~F&Bv>mr@~g||)A9?lqCYmDcU@gW)55|y5B~g=I85-VS zK5~g5njd@3!;GhvkO}3s137IByxMbZk53Dd_8$pN<~M>>x36e0vv$>A+4beQdEg$Y zqS|H=zvv|P_QGMLCqYZ~m2(>H>35(**qOIebUK3j&$gV2B`^{go6a^NM6P@+J(!9Z z<|uN?NUc~a*5S@pSWuCb996!)>oRpX>7(iks7fQ$Ia%7(#RzI6rhMKjmXfdH21%~c zlBr83T(!oYR(0Q%;um#$iKBe9qcw5|BPWUIJ+No)!OYL{G;qF`iZA3h1pB~Yn$_i_ zgfl|uwTPQ6r#=$yR1wM4&U)pgL9;p|Oat9Z1C?2yxS1M%_{s4k?oCn9%ma2>^Fnxx zFJK)mr%nUFIyC|ewLE6S)4T@>-T-mKclm_=|Q_&B@5&~;r%h@Z+B*XSdh zU!u9;kjQa?kxVJi2a;ukr4>UDt?qIg=&P7QWyD55cymHU`ou2dtuY#^z8Qmf(?Eu6 zP-IH0J}vD1%tW5?Nl|T(se8m;fp+21`djW6^U`S1P_J&C4CRQv z(`%zXHSLlmg{AU2wX3t$#W;qIFI@^s>0^hCIl|rj-8xdKU=64+K;agiYQ2PX+0L^c z?qiZTc!EQUDwRue_|sWpq>5LTX<96eq0RC`ynqn z1ANYux8Lw8a2==KilxwPv^V9UWP*k|WFRY3$C}|SdoR3ztP2boijp~J~EXaZ*e8UmyBUBjU_=j9U;=Je1zrV*p;@gVcpILqL*5!+h zU**`jeljCP%V-Vs-6XW^Q>?t+xQf{Nj`p_GqStmzl6c;OG$ zavm~4Rvzb?%sup@c|d0^=QuH9TzT_aXMEbd1PYS#a}z`UEAL3oCwq^~PIYTZdJ&0H zUs&FAjnH*y_9@F^8aNUEFNsR36xdusR|IbZo$)TLe}hQIVX2Cuc`lP6{$3(x2qvqJ zh8SC8*YWQ2;wY=DBy-n6-F4mrvk%Ltu1M_6eBXJ}0}-lufw^6jFE2cPRxD>=zNB7agAkvZOJ zKs}TuqfMGryK4Hq zw?Ms%<;_~|8m1*jc#>Ey1XEtram;a6UbD&Q&1s_-M_-E?On#b9%DkSjzc9`H`K!0Z z(gy*I*bGSZv#>)jp67W7PQJC`O8oQ!O#(zpol~8*6h3!Y@Dxf7m3xV%XAe9vo7c!M zNz6mOa^S_3#xnZI;@4RH=l4E68gD*1#*l*QzeB=sfYXC>4-S-0d<#lqR%q(8wq8$5 z0xofncKPt%1Q1Xjf2^SXCQOpvNXvG*j}KnyqppB?ahV*gRu>9ctcultR`5yFHYg$| zG!HlS3Fb>wr&0hX9W3mR)v7S;i+$96^lZtK2S6l^az9eOFP+ILM)7C55gw+8Q_^Sz zXDw1-i`R~Rgb&O}-{~=HnUiGbzYhkw6mjSMfD!f(KBnb-!+8#8Bcf9(1UI87A_F3Gj2BO6@XwoDr`%bAef`le7(@@qunW{ zM%iFhCCysWses>ecEgt(4116ot;62^-$+m#%0cfCqj3{CcCqxDgu6BHr)t%~CJP?Ck-;C5K#B-HuF@alAYs(t~6_ zu+akFK`O1|Mzd*VB=~Zx0qV&j)0d7~3DQAM{-Ywo_7oj#2&&boaRM z`+P01bq^9E+yGjnenmnMMuWukt9;LR1K)zsN5v|ahtm#pnx4U)6=H1X+f#b=U~)Cu zduIVzyT^_aBF)w)+TfH$sg3 zZ`&%$G{6UT?$`D}F&gL;CY6({@%w9-xQq9q{m7vyonQrA*R9ZPZEvpe?^B~?#&HrY zTs1qtKSzhu$YF2S(m&oi=oecXGkGDDzzAzy*Hf|s?T}kmIKWw>hY3u1p9Db0BTKOg zqX$mQ{#L4QAy^21CFPO#s6iCJeoW_q=rzqaBG0EYs1I>@yKC~#ap%)I(z{GQ%%4Ad z*p}2Nk4%6NSs=rXDNsQ1N3^fs%#3KSlDemo$gLDqEmLiyUBcV`Iaz6b5C7b!=UpdR zv$eA+BBo`T-uVSJQe9MCJNEX)eYdzJKHY4T=TLu&_q&l1s#m3qgh;Ki;&|cmnI#x2 zhuD2`KorK;r0*|@0cbZ$O*rrIh7?({uGMXgt{iwLY|^Pt8_NeHM~_wRGwc$9V8pT% zbLt5yb@C%@`Z2b|H8TG5q5)r5ukDi!m|Hr`lyk|YsPUfhSmCP2ltjUYuLE9d>(O78 z4mea4zy0duw06`iHYedrw^x6Nme2Eu3n>JI zH$-_I>`vybRH>1vgg?y#JTN7o8A$e~nTigc&F4vuzWk*nt`-(2Y*{${s$_-@fsRky z&J4v*4c`beu)_~&M7J6UJ0+Ztd*r+KaBn7CtwBDfDq2`oiHdzG|ATbCjwu`3KiOmg z)g~Z&PF6JY1&x%(l$?6(?GzFi1Y@J!|AGdBQ5(YyGy>zIXJLfeI4_YAg7KCqz6|1z zW{0m>p)dDoSvVvd$XiNCLuDwP<16ood9Q(D7lT`O++x2R$}Ga<{b7m=R^dS00uO~? zUoHta*96QjQ|s{?U*=qis2fcnGjHfrDBTTo*zUvqFN2LcaxnXNOL#JXMt9B1kH(Bu z7}A1mqj1|+y;D2G-Fo$u{bW0Tsu-3STd17BDLv-coaojjua%`ho zd!3VA`cUstK|&;xbPQoK`f6(bYeuwgV45|$Z2`@I`kEnzHMd0X@@0SZM7Th#xBXpG z8+(@G7&)5%x~lNyF<+}QVFWK|6LFzw`uLKetH$Dd06ZGN0dAmAwh-sx++3+r<4&p# zI3Lq#;kFK@&M%;-E&1F|wQl$QRR*{o?RRG3>~vqbY!& zAKbjvlLt<$Ga@91PYp<0n$L;LU!y0jKM&wQP7AWF0MO!IZsh zepEn}+J+G=ee9U#z2ld8#xyNR2i$K!Rlgz~eE^Q#($wDp~zQe$z(&d2L}(UsTE!2kfN$hcuEeWoO&p9CskWa)rbk&%l_> zP4)^7`Z@1X8xVVi9?a2d;aSnEl1F@c_i|s!+5=8rc#%xO+Un{Tdbe}|xyDcN&g-?( zR4md2vH@$4^TJoq@V;Y%<)(fj8UxU?mR9v+F`a_ zN){fpp!RO`>JDV8*umqFp4bq^g~Y?*>{77KH7o_1?^garQmpQ*XcK&?+d{}Ak<)WrLr3B;ZCRZ(0 zZhUC{*z}?P^GaWl<&!wGQLSQ4VdI7Ji;VU$2do#3vBe(J+V`F79&71nzRf>i_Ck7M z`m)tDYn}fvi)GvOw$I7KLjzbef<1WW?h9wub6=sP-A34o-w!g(TOWDtZ{ct{K{z7N zRxY7@|>(09(pfj+Mx*u@Jh6Hs4FgWl(D?W)+n#_ z?jXv)w}V;1x1?U}4xf?+W_pmU-Z+EP^R|SR@6yzR(cL}y^y(%)nbG7zPP>V*}4f4o*suV&!Mw>AW2md+Q-P!!;&Hxo@zVSEG|@2Dw8RD-*!>%xM1RA*k%7b z#K4;@_cJ4=i`q`vn_h?wdtk5#&1{sc9?e1l3Vf#iq59_vPw`IN~@ad%|AD3aIK z$8F~`1Sy~bs&07u_NciQJTTP|20pryK zSoP|U9_02CW9{a5FZ5=jZlkJf7Ed5eOX&^?4DPs*E8}UiYv#+B6r*9YGB4#T1qyzn zjSDSJ3DV3;5qHAt8rDf$;Je=Uk%k+2K6Zq8bnw;G3>f0Us1y?e*ws~6HsPNgDNW(-QaVNCgbJD*&-CS?19;I^zEUHLpyO_Y|->+6p=^3Ht- z9=P7ul-xpM)RRB5Xp$jtqV@pS+$f+=vJ?wz?-9kqH|jQh)vRZ|rW(Y;b4-LY3V^x4 zGUr+&aPLlki(tKriRau|uK>g)Do@Lry8F&vUZn;$7`^`MEp*Oi2&i>?YsSmqN2H?c z;cI!)ZI1ogH5S2r+}4*VI-^)T@wEU9N{LLX#N_KI6Wh6|M8ey*K-)DlGn; zs(#gT^cj<56l%lSydjW;m@@&Dwzmle9F23UE;R4JeyzmFtrG8y zJL+`iejUijlAJm9=6L(DFV9kkoo_EnTrA-mAH|jrlub_tEvl`{cPA`7FHoO+D16h+*^jtot!NSIULer}N2#=Fo#Da_9$d*%Qtvj~rBap4Lm_ zog{MsL7Dz~WLj>p%=4+uqcbae<7C~~%p0#>3$K)(d$b;7(f|2gT#-A`OOmRFsF%~9 zV~Lk$Uq%!Uy$n(cWs)Gq*4=+$jaDNtijzFk-IGK~g6t}3GnWgMJ z9b-#kWR0%RuZ2yw^vzp8E7u^SdMio(02W;*`1&QY+ z=9Z>${%HEi7^f2_Pm+^HPDQ#M5^(d=f?#GA)pVTGwYAYRh7YoQ29=T6V$t*HmoBe3FP@M9aZ#{U36?>+Kp|&-gRH6R{lMg$bO{ zxKg0Qd_ZgD;NV)fpZxhQv7XoflSH?b@T}Mq4Ot~{f(bSI*bk?dvKopGI4oVw$hWBm zCLDJf@VcMMc+r+}1Ua-78$DQzhI{rg6Q)-x7Pa+K#VZ=A(XP|O1J^&Wlbjl>&Cu8< z>@j${*k&+<%r2L^`&pq1Yq4EFcb&ipX^rVIdzL%qGOlyGMeSb@TDK=3?~osI9#XyH z#@wDjg3aNFc??dBdmM=?C-!lzc-0@CZ8MxIM!NKcnSH=rc;+EI#e!FYNucw$6Lr^fv>D7gX7!Jf0K>nJ;!Aj<1GGsxl))7L3;R{JG` zXq{k*P`FM(51g!udSr5r{*Ve%MV!$n27&fEEz52-Q~|;C#X3K4v@TdW@s`vYNsJtY!Vpq=tp=Z+#*VW|cnnFP$30r^ z`A#b}#Y^qGb700}lqGPs(?_E?Q-5!OV0G$QTjpaACzzF}NZ&F$kvP}Lc=ulk`dV8x z@GM_-o;)Qy5knqdXt~Q`y|9!yp_jL18rpQ{m^F#r3tP5yN8M==9_GmdHB9N2Z&;8R zBA3Tv)%W;qNBz5~JiQs~gF?kvl~opM8VY-<@~Hco=XMRAB0#2ROuo@N&smh6Tz#I| z(X+s#1?m53q_4$gr9D@urkP+RIDc16yUDe6SZ?FnYn-2p-ALa-xm(A(DmuKAL|D;8 z=g8o1<7y?wT^_%ExieSF0*i{i3LMFs6L5{&GE96u0;3+<2fga*;#KFou8GiA)4*A# z>}B+Q0@a>0L~s9&FIL?;b*JH2JG+mINaO)->bcD@+r$ zwTABp0p`P|{N)i#ry0T~p;qR!0wfcDMijPLD}a1>{qT z3mC=1Q>F%4D^+jUGe~uD@yDz|#U!yaa_TiN(Z0fs)U%;QGHcGK;`_1uurw)9Hjz1y z$yI^LYso;ermHixN3&(a*lQ-DUI|rRO(cXFS(`l6_4%|%G-TlXzE4aLU;;l$BOsen zuy*9IUmTPebzxvn?0q%OD)MG&)!kYB4sEHU=`M?52DDL6HY(6g{6yuj@*-Q`qZ_>} zvMEmYr^op;4T|sHU`3}jYlm=aiUuEtVlj4FlGAV7^Or{jMshOJ?>f9w$Yt1N96VKG z%PsUU1w(*zd)Nfy_hXqObW1-%u5;|I3C(bB;!ypXd)4~f-i~v}6dI}Gb7bx$^aZxe zcwNYk+V8JaH2pAOeh<|%sCDZ_P$LizHI9?Yn`?YbfDBS&+E?j0J<|1fj8I5nP}ys5 zDNW59+ea3~LWYLB!a>hc{M{Z>`d$flOhntgKI}at!*>d{^3Edg{K_(;VWG8f%epzE zIm!fPMmg$op0qN(Ykllvu7Smi6n5U3rL!-tH6A<-+&QylEe96HD0;NQMcLPPI(@#3 zn_F;qjCN%&P4|aI6IGf|XgR%UPTJM*@qXPi;jU-*5z5}lM;kLP6rC1raZcRZg`mK? zWt5N9BZu0-hdu48Cj~{7eWbFy(=b0y~_OpM%ny5T;lNTuaQ}_XzIkozULZ4Pa z48}*2l6m!!P1IPbu-Lq_kD=XU81^w`=jV(igyw{pd$;CT!~3)Y-9}Bx;UHwPTkKT# zEuqgxyy)530p5NUfLB zQzf614!i~m1Yf_dx)LYqbf9pexqbSyTT(=EyX~tup8bU9NK%Z}^X9sSYCqo_eLXRG z<>Q&=dmYbYn&P-P0|T2gdCQ!8Sj*?f>uR)!q7Mb`m3L(&82IE3C4JF@Eau7N)a^S! z(i-j;8m4Wg&2by&!qEX2jt|On2o8OYwK6C2!|Z^j`E}+XY&t`7Z;((KI+8ZukqLOE zQ`DDk^}3@LMl!WDd+}MN4*hMZ)ZrbHfwd_HXgf;n49{_tKX#tG(6XgrSPJb&x$I?c z`)T+tE&Z37vDedyR_JqN)yF58NxvQ5vwe%@!Vss=n&U*dx8j+Z4Z{0wz z)APGN{x^Aa2o+Ts#jNtqkv&u@Umbg%r2`^mI*g8#LvtVM53QE^@yE28M10YiNOf=M zd^=c4&%N``rK92Ysj9;QGOt|w)Nqt6~HQ65ykPR{P<@wmKeLh{v}%IV7|R6hCLH*jN#cz0MgljaV$BbGne z-}YlhJYP?-qQ_&Mn!z&We8ob`ND!18NbObJTLj`J<&eauO)y>GFLnFwC>)R4Z^3j) zwkPDmWaz|NQxq>H-9cv|RUfPQzAk0BRJ3(_!i`yPmhJ|TcN%+TPrNT~KY9JcBhSv; zlMVA3$0qk4Qa846QSBGY2%kfMII9+aaRe>8-a3z3F{RnY{*^x=vWk9tI4wQ#Hm{bN z+qx$u1POB_eO_5QQ~T1v&&Mj>#G$gJfu^_C;E^j)&k@6*(b&ijk30a=o|x_|arsz5 zhH)Cau)x_tm$M1Cvgvw4Hm zQ2c(M?5NRgN&ei#=tt+j5XLlkI==|4ZB4gx8bGsCrt{a-l~8_ev}PTGDa}m_L?%hN zG_fzO&X_Ha*sl85yU)|>M_A0fDL)y1l#w)I59%_M$)!d>H;1N2U}e{e^wQ-5FYj41 zswokk z%^FN`8efdV`A)v7x&)$zvBxMQvq81|v&UM*IF zO8I%xx6d*;^JJ@n4GJt?cZcdrRIKv=DZMDrRylIBLq&qy{mrycrEQlKrw_%vb-?A` z7EtH66rG!Ps0k)shcr?L`xuD0e)fhD*hjha-3yCbQA=-JJTa8W^TSRvjWLQqiddy{ zs!6pl@f(L)gJ|trsz{BdKG+QHguEj`uTc)d@$%Zp!Z=4 z7BpN^*~4+JcGaM&iJDd4>Ee?!UOC2PhY8_D$ff(VH=-qttSr*|39zHqk;?CmAwH8@ zEhKi=e=6wuAo+$eIE+0^L;xY|-3l+B=?t-M^TW(s8#?60B7VB)yd{bku6*VW$G{#$ zS}rHZ$`q#_m^`He(8hMJmRaCdT-0D=AL&<}n7Ho}+S9}F84r&&bHgcNq9?ogGSn0I z@E;YONW4{Gzl>!usC4IF4|L6V6Ibp|I!xv8Ex zPCUR^>bqgR$~NUNSz7BcXRx+%o@D;RFrp=%n#^lo-7CkSaL?t)x7Ntv3axlDY%5G2 zQ$nGtqXCZGR!Kqc7A|G1EuESY@YJKIkPH5Z(l@9E6?<~>He_eSjx%rA=PQ@zCp1=< zzd-Ah9L9}4yQ!2zjBt6X<#$l*v_cz;RS0W9>g(PLO8bK#n;H{&qy*G%C{342<@@43ukr!CTK@CKV~`zsf+h zVE})H2lR>_3UaWy#9@RUOks(dj6YKhiQ0b#m&l1T9ux6`7Zu3S`@y^tya-*?oF$gpSvgRMUlF;##6Ig!ii z!!<0OeO^=)eDxNWaLigMWgw$K#O!eOYwT<3-Qqcs4PQej(1xtai?fgOawlK;eB3SY zrAjH~vC5GdkSJ`pe)+-O4tOcO)rqtc59S__aV9WYe9Ki6Gei>d9+ieti^`>10;PoY zyUxyn%EBJ$oYx;zsIj3|8jr_4_*_Qn*dIsGKgoOpVlU1e9n497J7I*L&jv~js^NRc zOgv4hok5(Xqv7H$qq&(Oqa$&`dsn<1lt6%F_c1#W*YWmHPqnq$8_G)`G^T?evb9)g zuuBnxn9e<9gWYc70H*8Qsu!l$je;^F_l=CX1nq7wrt}4q%|6D-pJyY+e)PZli3LfDT`UWgK%foZ7*cqDRLq7MVRQqV;r7s-RH=oe z=pBV)kvj;HU;N>_q=64iCTQ;-rcLCzp2trkdx0pu-H7qQMcL{=aY7i;E3Kf4(HFO_ zJj{7gKolobDBGc)fA$!qwZ^_DsMqu^%a8ZB#vdhRb|OO|!g`O~iM*A#$my}idNoOw zz2E(P_LpFh8rhRrP2Tra=j!%jM6;=3MtJ&g~z_F_gmQ#A{;KeN$=rILA? zy?8Yv;oVO*W!#=HMll!Wm%H31q4_9q-zBw5jggue{)fiH`}008PV-r*vt7s%n^cxm z9Mte4D&b?gPlY*s=MX4K$@M1$xlW%KVB;c}v#b(JKn8{gm!QAI)|J)=M}0}Wt(Xo` zV~!OR0javKm&s>A>A)c;dt?qcfh$(s(frDL!xAKvX+YIrzrJ8neKCoB6lW}DhhrrJ z4{?6ip)j4tV54NETyD-EKN3}AR)F95Im6x5+cYr5fputZxcTzxy0AqfRTr}Y>A7QipZPzm6FD-C;w7Hw~8Q>*YTXx5I(7!`{@jJ&aL%&?Z+F>kzX= zSib}ALC_wFfzMN`eK%}gR;b=&LBhh%hfKngYXd&4@&cRlY3)&k^qjZPa`%BiAW4@P zc2xOOy>F=^?Ze8B(LE9S`ok*&j<%kCC{VXTT9XXIO4xC$DOdH_=Mrg2h}Sw9YFA#5 zT76uiN=%uSaD_PR(QCX}&Zkx7$+@<-rTAR+TabKu)?vT{=PM4P^HlYEEvwQCPfUDD z?{~F0vQO>uwY}F=O4DJpUcJw}X zd*Q2*ChPt|>|w3hrQpk6u}v zdoYSzUQ8uh3S*fn?=p7j|0>iod#rY{sI7Tsu;SY4a%7RKs_3 zKS3H?GwSkEnLoTAgHp{7vsmZJ1oyIdxg$-3dadJaNV{3}1YghcpOYA}tFd|#TH>g= zgHL=)A(}4ji#P!_PSEQW|SfnH0 z4ralI5d*?z2da&h9EPZOVkeGCOFS$bF3PqV;*Jyblf=#pE@Uw%rW{mpdECXU)3~$t zecJNEORDj{;@Rtn!{+ZE@|Jl`Fj*ZjX07cG<{j2Tpg};I$gg-@Dz0d8^oa%vG3l8q zch8*M`^)WF%l7n#McK!1C0u2etri|&uNIEEmT&S}(71Y@bO${bMq=uXOuFiHpzaQ| zxDlJ%xLy>l0HAFlN~Bqf6x6?Io)UaajJ${(@Hqq0^V3Qv`FU}bbX?x@WB$NHw4toA z$}@{v{&-nd=E@}wg~lVRnK_wxD1E^wQA5|q*XYGh(_%Qw)^8{`#YytfNl5G&wuoxy z=pz_*G9}{>dc`P$cp3}>4WUddRO5z=xBY41{eeAeqsmKBhf9hIcBP2zX$Kj3A%rBH z*`E5bkYswOgPj#wK5tNNB)cj^!{D*j^c;mj)+0R;tYdOl6u-y*`=@V4TpTGSgzQ^& znH@v8ACW$-t43vC$a{l7E5|Gh6mn$n8}2*&@WJyJZ%5=jprnH*UZN$MS`o}oQFs^FutR{JkGL;I^%ROf z_r9ex9pD7sBcPTE#cy!DQV9HNZCj@D%zkE3R#rYoklWUKbar8*2BuC%?!XINm6X)V zlqpIuV$$>O={(W{nVHn|0Ud$|R8|jW##L4yBs$O(b3)Hi7g&DplupFl!d1u6RizZl z0Lw3h3R6LN8Mw%gKxJZ2U07x@W`+;~b4vnc3EEzKn1Pu0`JZ@4V#wRt>jYveX%Pj1 zq1=nhlTGL4qa0K&C7ja(qt5%C>=bo60P} zwEG1Cv2cU>nAgsAJbn)S zMi-GT{!em1;lbgxlT9htK~h1l&`Y;dyxdWb7<+g})bP+jT)N<&fm{@R5G3N0)x7d@GlvJQt*K6`9 z{@9N_CHAu@2h`M?9bE^M6Y6@dWp
    Oe_*<8|H?@*1h(NH+Xh2xeMJU+P&q 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

    uD`3<~qRh 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 A)&Kwi literal 0 HcmV?d00001 diff --git a/examples/910_polyglot_demo/js/.dockerignore b/examples/910_polyglot_demo/js/.dockerignore new file mode 100644 index 000000000..93f136199 --- /dev/null +++ b/examples/910_polyglot_demo/js/.dockerignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/examples/910_polyglot_demo/js/Dockerfile b/examples/910_polyglot_demo/js/Dockerfile new file mode 100644 index 000000000..2f746c6f2 --- /dev/null +++ b/examples/910_polyglot_demo/js/Dockerfile @@ -0,0 +1,21 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# tag::dockerfile[] + +FROM node:18-alpine + +WORKDIR /usr/src/app + +COPY package*.json ./ +COPY main.js ./ + +RUN npm install + +EXPOSE 8080 + +CMD [ "node", "main.js" ] + +# end::dockerfile[] \ No newline at end of file diff --git a/examples/910_polyglot_demo/js/main.js b/examples/910_polyglot_demo/js/main.js new file mode 100644 index 000000000..7514bbaaa --- /dev/null +++ b/examples/910_polyglot_demo/js/main.js @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025 Oracle and/or its affiliates. + * Licensed under the Universal Permissive License v 1.0 as shown at + * http://oss.oracle.com/licenses/upl. + */ + +const coh = require('@oracle/coherence') + +const Session = coh.Session + +const express = require('express'); +const port = process.env.PORT || 8080 +const api = express(); + +api.use(express.json()); // to parse JSON request bodies + +// setup session to Coherence +const session = new Session() +const people = session.getCache('people') + +// ----- REST API ----------------------------------------------------------- + +/** + * Returns all people. + */ +api.get('/api/people', (req, res, next) => { + const toSend = [] + people.values() + .then(async values => { + // copy values to array to be sent via express + for await (let value of values) { + toSend.push(value) + } + res.send(toSend) + }) + .catch(err => next(err)) +}) + + +/** + * Create a person. + */ +api.post('/api/people', (req, res, next) => { + const id = req.body.id + const person = { + id: req.body.id, + name: req.body.name, + age: req.body.age + } + + people.set(id, person) + .then(() => { + res.send(JSON.stringify(person)) + }) + .catch(err => next(err)) +}) + + +/** + * Get a single person. + */ +api.get('/api/people/:id', (req, res, next) => { + const id = Number(req.params.id); + people.get(id) + .then(person => { + if (person) { + res.status(200).json(person); + } else { + res.sendStatus(404); + } + }) + .catch(err => next(err)); +}); + +/** + * Delete a person. + */ +api.delete('/api/people/:id', (req, res, next) => { + const id = Number(req.params.id); + people.delete(id) + .then(oldValue => { + res.sendStatus(oldValue ? 200 : 404) + }) + .catch(err => next(err)) +}) + +api.listen(port, () => console.log(`Listening on port ${port}`)) diff --git a/examples/910_polyglot_demo/js/package-lock.json b/examples/910_polyglot_demo/js/package-lock.json new file mode 100644 index 000000000..e35d49726 --- /dev/null +++ b/examples/910_polyglot_demo/js/package-lock.json @@ -0,0 +1,1266 @@ +{ + "name": "person-js", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "person-js", + "version": "1.0.0", + "dependencies": { + "@oracle/coherence": "^1.2.3", + "classnames": "^2.2.6", + "express": "^4.21.2", + "prop-types": "^15.7.2", + "typescript": "^3.9.7", + "uuid": "^8.3.0" + } + }, + "node_modules/@grpc/grpc-js": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.13.2.tgz", + "integrity": "sha512-nnR5nmL6lxF8YBqb6gWvEgLdLh/Fn+kvAdX5hUOnt48sNSb0riz/93ASd2E5gvanPA41X6Yp25bIfGRp1SMb2g==", + "license": "Apache-2.0", + "dependencies": { + "@grpc/proto-loader": "^0.7.13", + "@js-sdsl/ordered-map": "^4.4.2" + }, + "engines": { + "node": ">=12.10.0" + } + }, + "node_modules/@grpc/proto-loader": { + "version": "0.7.13", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz", + "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==", + "license": "Apache-2.0", + "dependencies": { + "lodash.camelcase": "^4.3.0", + "long": "^5.0.0", + "protobufjs": "^7.2.5", + "yargs": "^17.7.2" + }, + "bin": { + "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@js-sdsl/ordered-map": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz", + "integrity": "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/@oracle/coherence": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@oracle/coherence/-/coherence-1.2.3.tgz", + "integrity": "sha512-eOAFg15rTkSfHEjrmXLOVpTrs1RHs8OwWeN1StjYh5WcAWW+s7DrKIiZMUUIlvkvOXqQdmn4s7QXhg9bqtq6rQ==", + "license": "UPL-1.0", + "dependencies": { + "@grpc/grpc-js": "^1.12", + "@grpc/proto-loader": "^0.7", + "decimal.js": "^10.5", + "google-protobuf": "^3.21" + } + }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==", + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause" + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==", + "license": "BSD-3-Clause" + }, + "node_modules/@types/node": { + "version": "22.14.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.14.0.tgz", + "integrity": "sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/classnames": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", + "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==", + "license": "MIT" + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.5.0.tgz", + "integrity": "sha512-8vDa8Qxvr/+d94hSh5P3IJwI5t8/c0KsMp+g8bNw9cY2icONa5aPfvKeieW1WlG0WQYwwhJ7mjui2xtiePQSXw==", + "license": "MIT" + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/google-protobuf": { + "version": "3.21.4", + "resolved": "https://registry.npmjs.org/google-protobuf/-/google-protobuf-3.21.4.tgz", + "integrity": "sha512-MnG7N936zcKTco4Jd2PX2U96Kf9PxygAPKBug+74LHzmHXmceN16MmRcdgZv+DGef/S9YvQAfRsNCn4cjf9yyQ==", + "license": "(BSD-3-Clause AND Apache-2.0)" + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz", + "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng==", + "license": "Apache-2.0" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typescript": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.10.tgz", + "integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "node": ">=12" + } + } + } +} diff --git a/examples/910_polyglot_demo/js/package.json b/examples/910_polyglot_demo/js/package.json new file mode 100644 index 000000000..ae6cad0b4 --- /dev/null +++ b/examples/910_polyglot_demo/js/package.json @@ -0,0 +1,13 @@ +{ + "name": "person-js", + "version": "1.0.0", + "private": true, + "dependencies": { + "@oracle/coherence": "^1.2.3", + "express": "^4.21.2", + "classnames": "^2.2.6", + "prop-types": "^15.7.2", + "typescript": "^3.9.7", + "uuid": "^8.3.0" + } +} \ No newline at end of file diff --git a/examples/910_polyglot_demo/py/Dockerfile b/examples/910_polyglot_demo/py/Dockerfile new file mode 100644 index 000000000..6dc8dcdfc --- /dev/null +++ b/examples/910_polyglot_demo/py/Dockerfile @@ -0,0 +1,15 @@ +# ---------------------------------------------------------------------------------------------------------------------- +# Copyright (c) 2025, Oracle and/or its affiliates. +# +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# tag::dockerfile[] + +FROM python:3.11-slim + +ADD main.py . +RUN pip install coherence-client==2.0.0 Quart + +CMD ["python3", "./main.py"] + +# end::dockerfile[] \ No newline at end of file diff --git a/examples/910_polyglot_demo/py/main.py b/examples/910_polyglot_demo/py/main.py new file mode 100644 index 000000000..0d97c7a65 --- /dev/null +++ b/examples/910_polyglot_demo/py/main.py @@ -0,0 +1,100 @@ +# +# Copyright (c) 2025, Oracle and/or its affiliates. +# Licensed under the Universal Permissive License v 1.0 as shown at +# http://oss.oracle.com/licenses/upl. +# + +from typing import List +import jsonpickle +import quart +from coherence import NamedMap, Session, Filters, Processors +from dataclasses import dataclass +from coherence.serialization import proxy +from quart import Quart, request, redirect + + +@dataclass +class Person: + id: int + name: str + age: int + + +# ---- init ------------ + +# the Quart application. Quart was chosen over Flask due to better +# handling of asyncio which is required to use the Coherence client +# library +app: Quart = Quart(__name__, + static_url_path='', + static_folder='./') + + +# the Session with the gRPC proxy +session: Session + +people: NamedMap[int, Person] + + +@app.before_serving +async def init(): + + # initialize the session using the default localhost:1408 or the value of COHERENCE_SERVER_ADDRESS + global session + session = await Session.create() + + global people + people = await session.get_map('people') + +# ----- routes -------------------------------------------------------------- + +# Get all people +@app.route('/api/people', methods=['GET']) +async def get_people(): + people_list: List[People] = [] + async for person in await people.values(): + people_list.append(person) + + return quart.Response(jsonpickle.encode(people_list, unpicklable=False), mimetype="application/json") + +# Create a person with JSON as body +@app.route('/api/people', methods=['POST']) +async def create_person(): + data = await request.get_json(force=True) + name: str = data['name'] + id: int = data['id'] + age: int = data['age'] + person: Person = Person(id, name, age) + await people.put(person.id, person) + + return quart.Response( + jsonpickle.encode(person, unpicklable=False), + status=201, + mimetype='application/json' + ) + +# Get a single person +@app.route('/api/people/', methods=['GET']) +async def get_person(id: str): + existing: Person = await people.get(int(id)) + if existing == None: + return "", 404 + + return jsonpickle.encode(existing, unpicklable=False), 200 + +# Delete a person +@app.route('/api/people/', methods=['DELETE']) +async def delete_person(id: str): + """ + This route will delete the person with the given id. + + :param id: the id of the person to delete + """ + existing: Person = await people.remove(int(id)) + return "", 404 if existing is None else 200 + +# ----- main ---------------------------------------------------------------- + +if __name__ == '__main__': + # run the application on port 8080 + app.run(host='0.0.0.0', port=8080) \ No newline at end of file diff --git a/examples/910_polyglot_demo/py/requirements.txt b/examples/910_polyglot_demo/py/requirements.txt new file mode 100644 index 000000000..8a53fa7bf --- /dev/null +++ b/examples/910_polyglot_demo/py/requirements.txt @@ -0,0 +1,2 @@ +coherence-client==2.0.0 +Quart==0.20.0 diff --git a/examples/910_polyglot_demo/yaml/coherence-cluster.yaml b/examples/910_polyglot_demo/yaml/coherence-cluster.yaml new file mode 100644 index 000000000..fbda75a36 --- /dev/null +++ b/examples/910_polyglot_demo/yaml/coherence-cluster.yaml @@ -0,0 +1,14 @@ +apiVersion: coherence.oracle.com/v1 +kind: Coherence +metadata: + name: demo-cluster # <1> +spec: + replicas: 3 # <2> + image: "ghcr.io/oracle/coherence-ce:14.1.2-0-1-java17" # <3> + coherence: + management: # <4> + enabled: true + ports: + - name: grpc # <5> + port: 1408 + - name: management \ No newline at end of file diff --git a/examples/910_polyglot_demo/yaml/go-client.yaml b/examples/910_polyglot_demo/yaml/go-client.yaml new file mode 100644 index 000000000..fc7bb2163 --- /dev/null +++ b/examples/910_polyglot_demo/yaml/go-client.yaml @@ -0,0 +1,52 @@ +kind: Service +apiVersion: v1 +metadata: + name: go-client-http + labels: + app: go-client + coherence-language: go +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + selector: + app: go-client +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: go-client +spec: + replicas: 1 + selector: + matchLabels: + app: go-client + template: + metadata: + labels: + app: go-client + spec: + containers: + - name: go-client + image: polyglot-client-go:1.0.0 + imagePullPolicy: IfNotPresent + env: + - name: COHERENCE_SERVER_ADDRESS + value: "demo-cluster-grpc:1408" + - name: COHERENCE_READY_TIMEOUT + value: "60000" + resources: + requests: + memory: "2Gi" + limits: + memory: "2Gi" + ports: + - containerPort: 8080 + securityContext: + runAsNonRoot: true + runAsUser: 10001 + capabilities: + drop: + - all + readOnlyRootFilesystem: true \ No newline at end of file diff --git a/examples/910_polyglot_demo/yaml/js-client.yaml b/examples/910_polyglot_demo/yaml/js-client.yaml new file mode 100644 index 000000000..18a8dbc98 --- /dev/null +++ b/examples/910_polyglot_demo/yaml/js-client.yaml @@ -0,0 +1,52 @@ +kind: Service +apiVersion: v1 +metadata: + name: js-client-http + labels: + app: js-client + coherence-language: go +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + selector: + app: js-client +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: js-client +spec: + replicas: 1 + selector: + matchLabels: + app: js-client + template: + metadata: + labels: + app: js-client + spec: + containers: + - name: js-client + image: polyglot-client-js:1.0.0 + imagePullPolicy: IfNotPresent + env: + - name: COHERENCE_SERVER_ADDRESS + value: "demo-cluster-grpc:1408" + - name: COHERENCE_READY_TIMEOUT + value: "60000" + resources: + requests: + memory: "2Gi" + limits: + memory: "2Gi" + ports: + - containerPort: 8080 + securityContext: + runAsNonRoot: true + runAsUser: 10001 + capabilities: + drop: + - all + readOnlyRootFilesystem: true \ No newline at end of file diff --git a/examples/910_polyglot_demo/yaml/py-client.yaml b/examples/910_polyglot_demo/yaml/py-client.yaml new file mode 100644 index 000000000..5a34a1dfa --- /dev/null +++ b/examples/910_polyglot_demo/yaml/py-client.yaml @@ -0,0 +1,53 @@ +kind: Service +apiVersion: v1 +metadata: + name: py-client-http + labels: + app: py-client + coherence-language: go +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: 8080 + selector: + app: py-client +--- +kind: Deployment +apiVersion: apps/v1 +metadata: + name: py-client +spec: + replicas: 1 + selector: + matchLabels: + app: py-client + template: + metadata: + labels: + app: py-client + spec: + containers: # tag::yaml[] + - name: py-client + image: polyglot-client-py:1.0.0 + imagePullPolicy: IfNotPresent + env: + - name: COHERENCE_SERVER_ADDRESS + value: "demo-cluster-grpc:1408" + - name: COHERENCE_READY_TIMEOUT + value: "60000" + resources: + requests: + memory: "2Gi" + limits: + memory: "2Gi" + ports: + - containerPort: 8080 + securityContext: + runAsNonRoot: true + runAsUser: 10001 + capabilities: + drop: + - all + readOnlyRootFilesystem: true + # end::yaml[] \ No newline at end of file diff --git a/examples/README.adoc b/examples/README.adoc index af5bf915e..de72cb083 100644 --- a/examples/README.adoc +++ b/examples/README.adoc @@ -108,3 +108,13 @@ Istio Support Deploying the Coherence demo application. -- ==== + +[PILLARS] +==== +[CARD] +.Polyglot Client Demo +[link=examples/910_polyglot_demo/README.adoc] +-- +Deploy Python, JavaScript or Go applications using the Operator. +-- +==== From 33c12598a5b30342668a7213c9541ce10b531e4d Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 7 Apr 2025 12:52:54 +0800 Subject: [PATCH 2/4] Update to use non-root user for images --- examples/910_polyglot_demo/go/Dockerfile | 7 +++++-- examples/910_polyglot_demo/js/Dockerfile | 10 +++++++--- examples/910_polyglot_demo/py/Dockerfile | 12 ++++++++++-- 3 files changed, 22 insertions(+), 7 deletions(-) diff --git a/examples/910_polyglot_demo/go/Dockerfile b/examples/910_polyglot_demo/go/Dockerfile index eaab49a45..8a5db8f4d 100644 --- a/examples/910_polyglot_demo/go/Dockerfile +++ b/examples/910_polyglot_demo/go/Dockerfile @@ -7,19 +7,22 @@ FROM golang:1.24 as builder -# Set working dir inside the container WORKDIR /app -# Copy your Go module files and source code COPY go.mod ./ COPY go.sum ./ COPY main.go ./ +ENV APP_USER_UID=1000 +ENV APP_USER_GID=1000 + RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o runner . +RUN chown ${APP_USER_UID}:${APP_USER_GID} /app/runner FROM scratch COPY --from=builder /app/runner /files/runner +USER 1000:1000 EXPOSE 8080 ENTRYPOINT ["/files/runner"] diff --git a/examples/910_polyglot_demo/js/Dockerfile b/examples/910_polyglot_demo/js/Dockerfile index 2f746c6f2..034043b7a 100644 --- a/examples/910_polyglot_demo/js/Dockerfile +++ b/examples/910_polyglot_demo/js/Dockerfile @@ -7,13 +7,17 @@ FROM node:18-alpine +RUN addgroup -S appgroup && adduser -S appuser -G appgroup + WORKDIR /usr/src/app -COPY package*.json ./ -COPY main.js ./ +COPY --chown=appuser:appgroup package*.json ./ +COPY --chown=appuser:appgroup main.js ./ -RUN npm install +RUN npm install --ignore-scripts +RUN chown -R appuser:appgroup /usr/src/app +USER appuser EXPOSE 8080 CMD [ "node", "main.js" ] diff --git a/examples/910_polyglot_demo/py/Dockerfile b/examples/910_polyglot_demo/py/Dockerfile index 6dc8dcdfc..a8ab441b2 100644 --- a/examples/910_polyglot_demo/py/Dockerfile +++ b/examples/910_polyglot_demo/py/Dockerfile @@ -7,8 +7,16 @@ FROM python:3.11-slim -ADD main.py . -RUN pip install coherence-client==2.0.0 Quart +RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser + +WORKDIR /app +COPY --chown=appuser:appgroup main.py . + +RUN pip install --no-cache-dir coherence-client==2.0.0 Quart + +RUN chown -R appuser:appgroup /app + +USER appuser CMD ["python3", "./main.py"] From 780e188312570fbe07bd6655fad13a59d488f897 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Mon, 7 Apr 2025 13:00:00 +0800 Subject: [PATCH 3/4] Further fix permissions in Dockerfiles --- examples/910_polyglot_demo/js/Dockerfile | 2 ++ examples/910_polyglot_demo/py/Dockerfile | 1 + 2 files changed, 3 insertions(+) diff --git a/examples/910_polyglot_demo/js/Dockerfile b/examples/910_polyglot_demo/js/Dockerfile index 034043b7a..3f10a4651 100644 --- a/examples/910_polyglot_demo/js/Dockerfile +++ b/examples/910_polyglot_demo/js/Dockerfile @@ -14,6 +14,8 @@ WORKDIR /usr/src/app COPY --chown=appuser:appgroup package*.json ./ COPY --chown=appuser:appgroup main.js ./ +RUN chmod 444 package*.json main.js + RUN npm install --ignore-scripts RUN chown -R appuser:appgroup /usr/src/app diff --git a/examples/910_polyglot_demo/py/Dockerfile b/examples/910_polyglot_demo/py/Dockerfile index a8ab441b2..8082ab5cf 100644 --- a/examples/910_polyglot_demo/py/Dockerfile +++ b/examples/910_polyglot_demo/py/Dockerfile @@ -11,6 +11,7 @@ RUN addgroup --system appgroup && adduser --system --ingroup appgroup appuser WORKDIR /app COPY --chown=appuser:appgroup main.py . +RUN chmod 444 main.py RUN pip install --no-cache-dir coherence-client==2.0.0 Quart From c0071e1da64c5eccbd3a5ff6841a50544be97d10 Mon Sep 17 00:00:00 2001 From: Tim Middleton Date: Thu, 10 Apr 2025 14:44:36 +0800 Subject: [PATCH 4/4] Reduce memory usage of containers --- examples/910_polyglot_demo/yaml/coherence-cluster.yaml | 4 ++++ examples/910_polyglot_demo/yaml/go-client.yaml | 4 ++-- examples/910_polyglot_demo/yaml/js-client.yaml | 4 ++-- examples/910_polyglot_demo/yaml/py-client.yaml | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/910_polyglot_demo/yaml/coherence-cluster.yaml b/examples/910_polyglot_demo/yaml/coherence-cluster.yaml index fbda75a36..4646cec65 100644 --- a/examples/910_polyglot_demo/yaml/coherence-cluster.yaml +++ b/examples/910_polyglot_demo/yaml/coherence-cluster.yaml @@ -3,6 +3,10 @@ kind: Coherence metadata: name: demo-cluster # <1> spec: + jvm: + memory: + initialHeapSize: 1g + maxHeapSize: 1g replicas: 3 # <2> image: "ghcr.io/oracle/coherence-ce:14.1.2-0-1-java17" # <3> coherence: diff --git a/examples/910_polyglot_demo/yaml/go-client.yaml b/examples/910_polyglot_demo/yaml/go-client.yaml index fc7bb2163..303baf717 100644 --- a/examples/910_polyglot_demo/yaml/go-client.yaml +++ b/examples/910_polyglot_demo/yaml/go-client.yaml @@ -38,9 +38,9 @@ spec: value: "60000" resources: requests: - memory: "2Gi" + memory: "512Mi" limits: - memory: "2Gi" + memory: "512Mi" ports: - containerPort: 8080 securityContext: diff --git a/examples/910_polyglot_demo/yaml/js-client.yaml b/examples/910_polyglot_demo/yaml/js-client.yaml index 18a8dbc98..e7a9ae10f 100644 --- a/examples/910_polyglot_demo/yaml/js-client.yaml +++ b/examples/910_polyglot_demo/yaml/js-client.yaml @@ -38,9 +38,9 @@ spec: value: "60000" resources: requests: - memory: "2Gi" + memory: "512Mi" limits: - memory: "2Gi" + memory: "512Mi" ports: - containerPort: 8080 securityContext: diff --git a/examples/910_polyglot_demo/yaml/py-client.yaml b/examples/910_polyglot_demo/yaml/py-client.yaml index 5a34a1dfa..9c248ba34 100644 --- a/examples/910_polyglot_demo/yaml/py-client.yaml +++ b/examples/910_polyglot_demo/yaml/py-client.yaml @@ -38,9 +38,9 @@ spec: value: "60000" resources: requests: - memory: "2Gi" + memory: "512Mi" limits: - memory: "2Gi" + memory: "512Mi" ports: - containerPort: 8080 securityContext: