From 96359ce27d9c82996f51cedb25e683123c58d0e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Komor?= Date: Tue, 13 Oct 2026 14:10:11 +0200 Subject: [PATCH] Update README and plugin documentation for improved clarity and client configuration examples - Enhanced the README.md to provide clearer local setup instructions for each plugin, including links to sample directories. - Updated client configuration sections across multiple plugin READMEs to reflect the use of the Hypertube SDK, removing unnecessary runtime and host keys. - Added detailed examples for client configuration in .NET, emphasizing the structure and requirements for plugin integration. - Removed outdated sample configuration files and scripts for Kafka, Pub/Sub, and SQS, streamlining the documentation for better usability. - Improved health checks and dependencies in Docker Compose files for Kafka and Pub/Sub services to ensure proper initialization and reliability. --- README.md | 42 +++++++-- kafka/Readme.md | 31 ++++--- pubsub/Readme.md | 59 +++++++----- rabbitmq/Readme.md | 34 +++---- samples/README.md | 60 +++++++++++++ samples/kafka/README.md | 29 ------ samples/kafka/docker-compose.redpanda.yml | 10 --- samples/kafka/docker-compose.yml | 54 ++++++++++- samples/kafka/graftConfig.kafka.example.json | 16 ---- samples/kafka/pluginConfig.gateway.json | 8 -- samples/kafka/pluginConfig.json | 8 -- samples/kafka/run.ps1 | 2 + samples/kafka/scripts/create-topics.sh | 53 ----------- samples/kafka/server/Dockerfile | 43 +++++++++ samples/kafka/server/netapp.dll | Bin 0 -> 4096 bytes .../pluginconfig.json} | 5 +- samples/pubsub/README.md | 61 ------------- samples/pubsub/docker-compose.yml | 45 +++++++++- .../pubsub/graftConfig.pubsub.example.json | 20 ----- samples/pubsub/pluginConfig.client.json | 11 --- samples/pubsub/pluginConfig.json | 9 -- samples/pubsub/pluginConfig.oneway.json | 10 --- samples/pubsub/run.ps1 | 2 + samples/pubsub/scripts/create-resources.sh | 38 -------- samples/pubsub/server/Dockerfile | 43 +++++++++ samples/pubsub/server/netapp.dll | Bin 0 -> 4096 bytes .../pluginconfig.json} | 2 +- samples/rabbitmq/docker-compose.yml | 49 ++++++++++ samples/rabbitmq/middleware/definitions.json | 40 +++++++++ samples/rabbitmq/run.ps1 | 2 + samples/rabbitmq/server/Dockerfile | 43 +++++++++ samples/rabbitmq/server/netapp.dll | Bin 0 -> 4096 bytes samples/rabbitmq/server/pluginconfig.json | 9 ++ samples/servicebus/client/client_config.json | 13 +++ samples/servicebus/docker-compose.yml | 85 ++++++++++++++++++ samples/servicebus/middleware/Config.json | 43 +++++++++ samples/servicebus/run.ps1 | 2 + samples/servicebus/server/Dockerfile | 43 +++++++++ samples/servicebus/server/netapp.dll | Bin 0 -> 4096 bytes samples/servicebus/server/pluginconfig.json | 5 ++ samples/sqs/README.md | 67 -------------- samples/sqs/docker-compose.yml | 61 ++++++++++++- samples/sqs/graftConfig.sqs.example.json | 22 ----- samples/sqs/pluginConfig.client.json | 13 --- samples/sqs/pluginConfig.json | 11 --- samples/sqs/pluginConfig.oneway.json | 12 --- samples/sqs/run.ps1 | 2 + samples/sqs/scripts/create-queues.sh | 22 ----- samples/sqs/scripts/init-queues.sh | 7 -- samples/sqs/server/Dockerfile | 43 +++++++++ samples/sqs/server/netapp.dll | Bin 0 -> 4096 bytes .../pluginconfig.json} | 2 +- servicebus/Readme.md | 29 +++--- .../ServiceBusPlugin/ServiceBusServer.cpp | 58 ++++++++++-- sqs/Readme.md | 52 +++++++---- 55 files changed, 898 insertions(+), 532 deletions(-) create mode 100644 samples/README.md delete mode 100644 samples/kafka/README.md delete mode 100644 samples/kafka/docker-compose.redpanda.yml delete mode 100644 samples/kafka/graftConfig.kafka.example.json delete mode 100644 samples/kafka/pluginConfig.gateway.json delete mode 100644 samples/kafka/pluginConfig.json create mode 100644 samples/kafka/run.ps1 delete mode 100644 samples/kafka/scripts/create-topics.sh create mode 100644 samples/kafka/server/Dockerfile create mode 100644 samples/kafka/server/netapp.dll rename samples/kafka/{pluginConfig.client.json => server/pluginconfig.json} (53%) delete mode 100644 samples/pubsub/README.md delete mode 100644 samples/pubsub/graftConfig.pubsub.example.json delete mode 100644 samples/pubsub/pluginConfig.client.json delete mode 100644 samples/pubsub/pluginConfig.json delete mode 100644 samples/pubsub/pluginConfig.oneway.json create mode 100644 samples/pubsub/run.ps1 delete mode 100755 samples/pubsub/scripts/create-resources.sh create mode 100644 samples/pubsub/server/Dockerfile create mode 100644 samples/pubsub/server/netapp.dll rename samples/pubsub/{pluginConfig.gateway.json => server/pluginconfig.json} (82%) create mode 100644 samples/rabbitmq/docker-compose.yml create mode 100644 samples/rabbitmq/middleware/definitions.json create mode 100644 samples/rabbitmq/run.ps1 create mode 100644 samples/rabbitmq/server/Dockerfile create mode 100644 samples/rabbitmq/server/netapp.dll create mode 100644 samples/rabbitmq/server/pluginconfig.json create mode 100644 samples/servicebus/client/client_config.json create mode 100644 samples/servicebus/docker-compose.yml create mode 100644 samples/servicebus/middleware/Config.json create mode 100644 samples/servicebus/run.ps1 create mode 100644 samples/servicebus/server/Dockerfile create mode 100644 samples/servicebus/server/netapp.dll create mode 100644 samples/servicebus/server/pluginconfig.json delete mode 100644 samples/sqs/README.md delete mode 100644 samples/sqs/graftConfig.sqs.example.json delete mode 100644 samples/sqs/pluginConfig.client.json delete mode 100644 samples/sqs/pluginConfig.json delete mode 100644 samples/sqs/pluginConfig.oneway.json create mode 100644 samples/sqs/run.ps1 delete mode 100755 samples/sqs/scripts/create-queues.sh delete mode 100755 samples/sqs/scripts/init-queues.sh create mode 100644 samples/sqs/server/Dockerfile create mode 100644 samples/sqs/server/netapp.dll rename samples/sqs/{pluginConfig.gateway.json => server/pluginconfig.json} (85%) diff --git a/README.md b/README.md index e306f1e..fa0cbd8 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,15 @@ Official open-source Graftcode Gateway plugins. Each plugin carries Graft calls | Plugin | Channel | Modes | Local setup | Docs | |--------|---------|-------|-------------|------| -| [rabbitmq](rabbitmq/Readme.md) | RabbitMQ (AMQP 0-9-1) | request/reply | Docker image in `rabbitmq/` | [Readme](rabbitmq/Readme.md) | -| [servicebus](servicebus/Readme.md) | Azure Service Bus (AMQP 1.0) | request/reply and one-way | Service Bus emulator | [Readme](servicebus/Readme.md) | -| [kafka](kafka/Readme.md) | Apache Kafka | request/reply | [samples/kafka](samples/kafka/README.md) | [Readme](kafka/Readme.md) | -| [sqs](sqs/Readme.md) | Amazon SQS | request/reply and one-way | [samples/sqs](samples/sqs/README.md) (LocalStack) | [Readme](sqs/Readme.md) | -| [pubsub](pubsub/Readme.md) | Google Cloud Pub/Sub | request/reply and one-way | [samples/pubsub](samples/pubsub/README.md) (Pub/Sub emulator) | [Readme](pubsub/Readme.md) | +| [rabbitmq](rabbitmq/Readme.md) | RabbitMQ (AMQP 0-9-1) | request/reply | [samples/rabbitmq](samples/rabbitmq) | [Readme](rabbitmq/Readme.md) | +| [servicebus](servicebus/Readme.md) | Azure Service Bus (AMQP 1.0) | request/reply and one-way | [samples/servicebus](samples/servicebus) | [Readme](servicebus/Readme.md) | +| [kafka](kafka/Readme.md) | Apache Kafka | request/reply | [samples/kafka](samples/kafka) | [Readme](kafka/Readme.md) | +| [sqs](sqs/Readme.md) | Amazon SQS | request/reply and one-way | [samples/sqs](samples/sqs) (LocalStack) | [Readme](sqs/Readme.md) | +| [pubsub](pubsub/Readme.md) | Google Cloud Pub/Sub | request/reply and one-way | [samples/pubsub](samples/pubsub) (Pub/Sub emulator) | [Readme](pubsub/Readme.md) | + +All local Docker setups, ports, and startup instructions are documented in +[samples/README.md](samples/README.md). Each setup starts its middleware and +three Gateway servers consuming from the same request destination. OpenTelemetry / Application Insights lives under [observability/opentelemetry](observability/opentelemetry/dotnet/Graft.Netcore.Telemetry.AppInsightsConnector/README.md). It is not a transport plugin. @@ -30,8 +34,8 @@ Each release archive is flat and contains the plugin shared library. Third-party 1. Download the release archive for the platform, or build the plugin (see its Readme). 2. Put `gg`, the hosted module, and the plugin library in one folder. For Linux Kafka, also put `libsasl2.so.2` and `libsasl2.so.2.0.25` from the same archive there. -3. Start the Gateway with `--config` and one of the JSON files below. A verified SQS layout is in [samples/sqs](samples/sqs/README.md): `gg netapp.dll --config sqsplugin.json`. -4. Open `http://localhost:81/GV`, install the generated package, and call `GraftConfig.SetConfig` with the same channel settings inside `plugin`. `host` next to `runtime` is the Gateway HTTP address, not the broker. +3. Start the Gateway with `--config` and one of the JSON files below. Verified local Docker layouts are available under [samples](samples/README.md). +4. Call the Gateway from .NET with the Hypertube SDK (see [Client configuration](#client-configuration)), or open `http://localhost:81/GV` and install the generated package. How the Gateway loads a plugin is described in the "Plugin server config" section of the [Graftcode Gateway](https://github.com/grft-dev/graftcode-gateway) README. @@ -110,4 +114,28 @@ Pub/Sub (`pubsub/Readme.md`, emulator): } ``` +## Client configuration + +A .NET client can call the gateway directly through the Hypertube SDK. The client config wraps the plugin object in `configurations` → package name → `plugin`, with no `runtime` or `host` keys; the plugin carries the transport. + +```csharp +using Hypertube.Netcore.Sdk; +using Hypertube.Netcore.Sdk.Configuration; + +RuntimeBridge.AddConfig(ConfigPriority.User, configJsonOrPathToFile); +var rtmCtx = RuntimeBridge.InitializeRC("graft.nuget.netapp"); +var result = rtmCtx.GetType("netapp.Calculator").CreateInstance() + .InvokeInstanceMethod("Add", 1, 2).Execute().GetValue(); +``` + +`AddConfig` accepts a JSON string, a path to a JSON file, or the name of an environment variable holding either. The key under `configurations` must match the name passed to `InitializeRC`. Load one plugin config per process. + +| Plugin | Client config | `name` | +|---|---|---| +| RabbitMQ | [rabbitmq/Readme.md](rabbitmq/Readme.md#client-configuration) | `RabbitmqPlugin` | +| Kafka | [kafka/Readme.md](kafka/Readme.md#client-configuration) | `KafkaPlugin` | +| Service Bus | [servicebus/Readme.md](servicebus/Readme.md#client-configuration) | `ServiceBusPlugin` | +| SQS | [sqs/Readme.md](sqs/Readme.md#client-configuration) | `SqsPlugin` | +| Pub/Sub | [pubsub/Readme.md](pubsub/Readme.md#client-configuration) | `PubSubPlugin` | + Part of the [Graftcode](https://github.com/grft-dev/graftcode) project. diff --git a/kafka/Readme.md b/kafka/Readme.md index 519c3a0..827e372 100644 --- a/kafka/Readme.md +++ b/kafka/Readme.md @@ -36,7 +36,7 @@ Download `gg` from https://github.com/grft-dev/graftcode-gateway/releases/. ## Local broker -Compose files are in [`samples/kafka`](../samples/kafka/README.md), not in this directory. +Compose files are in [`samples/kafka`](../samples/kafka) (see [samples/README.md](../samples/README.md)), not in this directory. Apache Kafka (KRaft), broker `localhost:9092`, UI `http://localhost:8080`: @@ -56,7 +56,7 @@ Set `"brokers": "localhost:19092"` when using Redpanda. ## Gateway -`pluginConfig.json` (also [`samples/kafka/pluginConfig.gateway.json`](../samples/kafka/pluginConfig.gateway.json)): +`pluginConfig.json`: ```json { @@ -73,20 +73,16 @@ Set `"brokers": "localhost:19092"` when using Redpanda. ./gg .\PhysicsCalculator.dll --config .\pluginConfig.json ``` -Open `http://localhost:81/GV` and install the generated package. +Open `http://localhost:81/GV` to install the generated package, or call the Gateway from .NET with the Hypertube SDK (see [Client configuration](#client-configuration)). -## Client +## Client configuration -A full example is [`samples/kafka/graftConfig.kafka.example.json`](../samples/kafka/graftConfig.kafka.example.json). Use a client `groupId` such as `graft-client`; the plugin still adds a unique suffix. Kafka bootstrap servers belong in `plugin.brokers`. When the runtime also needs the Gateway HTTP address, set `"host": "localhost:80"` next to `runtime`, as in the [SQS client config](../samples/sqs/graftConfig.sqs.example.json). +A .NET client can call the gateway through the Hypertube SDK. The client config has no `runtime` or `host` keys; the plugin carries the transport. Use a `groupId` different from the gateway's (`graft-gateway`). -```csharp -string configSource = -""" +```json { "configurations": { - "graft.nuget.PhysicsCalculator": { - "runtime": "netcore", - "stateless": true, + "graft.nuget.netapp": { "plugin": { "name": "KafkaPlugin", "brokers": "localhost:9092", @@ -98,11 +94,20 @@ string configSource = } } } -"""; +``` -graft.nuget.PhysicsCalculator.GraftConfig.SetConfig(configSource); +```csharp +using Hypertube.Netcore.Sdk; +using Hypertube.Netcore.Sdk.Configuration; + +RuntimeBridge.AddConfig(ConfigPriority.User, configJsonOrPathToFile); +var rtmCtx = RuntimeBridge.InitializeRC("graft.nuget.netapp"); +var result = rtmCtx.GetType("netapp.Calculator").CreateInstance() + .InvokeInstanceMethod("Add", 1, 2).Execute().GetValue(); ``` +`AddConfig` accepts a JSON string, a path to a JSON file, or the name of an environment variable holding either. The key under `configurations` must match the name passed to `InitializeRC`. Load one plugin config per process. + ## Configuration reference | Field | Required | Description | diff --git a/pubsub/Readme.md b/pubsub/Readme.md index 8204a53..50dbf74 100644 --- a/pubsub/Readme.md +++ b/pubsub/Readme.md @@ -62,7 +62,7 @@ Download `gg` from https://github.com/grft-dev/graftcode-gateway/releases/. ## Emulator -Sample files and compose live in [`samples/pubsub`](../samples/pubsub/README.md). From `pubsub/` you can start the same emulator: +Sample files and compose live in [`samples/pubsub`](../samples/pubsub) (see [samples/README.md](../samples/README.md)). From `pubsub/` you can start the same emulator: ```bash docker compose up -d @@ -84,7 +84,7 @@ This layout is the same shape as the SQS sample. Put these files in one folder: - the hosted module, for example `netapp.dll` - `PubSubPlugin.dll` -`pubsubplugin.json` (same contents as [`samples/pubsub/pluginConfig.gateway.json`](../samples/pubsub/pluginConfig.gateway.json)): +`pubsubplugin.json`: ```json { @@ -104,25 +104,42 @@ From that folder, with the emulator running: .\gg.exe netapp.dll --config pubsubplugin.json ``` -Open `http://localhost:81/GV` and install the generated package. +Open `http://localhost:81/GV` to install the generated package, or call the Gateway from .NET with the Hypertube SDK (see [Client configuration](#client-configuration)). -## Client +## Google Cloud credentials and permissions -`host` is the Gateway (`localhost:80`), not Pub/Sub. `clientconfig.json` matches [`samples/pubsub/graftConfig.pubsub.example.json`](../samples/pubsub/graftConfig.pubsub.example.json): +On Google Cloud, omit `emulatorHost` and `verifySsl`. Provide credentials with one of: -```csharp -graft.nuget.netapp.GraftConfig.SetConfig("clientconfig.json"); -var calculator = new graft.nuget.netapp.Calculator(); -calculator.Add(1, 2); +- `accessToken`, or `PUBSUB_ACCESS_TOKEN` +- `credentialsFile`, or `GOOGLE_APPLICATION_CREDENTIALS`, pointing at a service-account JSON key +- the GCE metadata server, when neither of the above is set + +The gateway identity needs `pubsub.subscriptions.consume` on the request subscription and `pubsub.topics.publish` on reply topics. A client needs `pubsub.topics.publish` on the request topic and `pubsub.subscriptions.consume` on its reply subscription. `autoCreate` also needs permission to create topics and subscriptions. Set `autoCreate` to false in production and create the resources ahead of time when the runtime identity should not administer Pub/Sub. + +## One-way calls + +Set `"oneWay": true` on both sides. The client omits `replyTo` and does not wait. The gateway processes the request and acks it without publishing a reply. A reply topic is not required. Client `plugin` object: + +```json +{ + "name": "PubSubPlugin", + "projectId": "graftcode-local", + "emulatorHost": "localhost:8085", + "requestTopic": "graft-requests", + "oneWay": true, + "autoCreate": true, + "verifySsl": false +} ``` +## Client configuration + +A .NET client can call the gateway through the Hypertube SDK. The client config has no `runtime` or `host` keys; the plugin carries the transport. Emulator values. Each client process needs its own `replyTopic`. + ```json { "configurations": { "graft.nuget.netapp": { - "runtime": "netcore", - "host": "localhost:80", - "stateless": true, "plugin": { "name": "PubSubPlugin", "projectId": "graftcode-local", @@ -139,17 +156,17 @@ calculator.Add(1, 2); } ``` -On Google Cloud, omit `emulatorHost` and `verifySsl`. Provide credentials with one of: - -- `accessToken`, or `PUBSUB_ACCESS_TOKEN` -- `credentialsFile`, or `GOOGLE_APPLICATION_CREDENTIALS`, pointing at a service-account JSON key -- the GCE metadata server, when neither of the above is set - -The gateway identity needs `pubsub.subscriptions.consume` on the request subscription and `pubsub.topics.publish` on reply topics. A client needs `pubsub.topics.publish` on the request topic and `pubsub.subscriptions.consume` on its reply subscription. `autoCreate` also needs permission to create topics and subscriptions. Set `autoCreate` to false in production and create the resources ahead of time when the runtime identity should not administer Pub/Sub. +```csharp +using Hypertube.Netcore.Sdk; +using Hypertube.Netcore.Sdk.Configuration; -## One-way calls +RuntimeBridge.AddConfig(ConfigPriority.User, configJsonOrPathToFile); +var rtmCtx = RuntimeBridge.InitializeRC("graft.nuget.netapp"); +var result = rtmCtx.GetType("netapp.Calculator").CreateInstance() + .InvokeInstanceMethod("Add", 1, 2).Execute().GetValue(); +``` -Set `"oneWay": true` on both sides. The client omits `replyTo` and does not wait. The gateway processes the request and acks it without publishing a reply. A reply topic is not required. See [`samples/pubsub/pluginConfig.oneway.json`](../samples/pubsub/pluginConfig.oneway.json). +`AddConfig` accepts a JSON string, a path to a JSON file, or the name of an environment variable holding either. The key under `configurations` must match the name passed to `InitializeRC`. Load one plugin config per process. ## Configuration reference diff --git a/rabbitmq/Readme.md b/rabbitmq/Readme.md index 2060974..2786e2b 100644 --- a/rabbitmq/Readme.md +++ b/rabbitmq/Readme.md @@ -73,40 +73,44 @@ docker exec -it graftcode-rabbitmq rabbitmqadmin declare queue name=myqueue.repl ./gg .\PhysicsCalculator.dll --config .\pluginConfig.json ``` -Open `http://localhost:81/GV` and install the generated package. +Open `http://localhost:81/GV` to install the generated package, or call the Gateway from .NET with the Hypertube SDK (see [Client configuration](#client-configuration)). -## Client +## Client configuration -`host` inside `configurations` is the Gateway address. Broker host and port stay in `plugin`. +A .NET client can call the gateway through the Hypertube SDK. The client config has no `runtime` or `host` keys; the plugin carries the transport. Port `5673` and user `graftcode` match [samples/rabbitmq](../samples/rabbitmq). The standalone broker above uses `5672` and `guest`. -```csharp -string configSource = -""" +```json { "configurations": { - "graft.nuget.PhysicsCalculator": { - "runtime": "netcore", - "host": "localhost:80", - "stateless": true, + "graft.nuget.netapp": { "plugin": { "name": "RabbitmqPlugin", "host": "localhost", - "port": 5672, + "port": 5673, "queue": "myqueue", "replyQueue": "myqueue.reply", - "user": "guest", - "password": "guest", + "user": "graftcode", + "password": "graftcode", "vhost": "/", "rpcTimeoutMs": 30000 } } } } -"""; +``` -graft.nuget.PhysicsCalculator.GraftConfig.SetConfig(configSource); +```csharp +using Hypertube.Netcore.Sdk; +using Hypertube.Netcore.Sdk.Configuration; + +RuntimeBridge.AddConfig(ConfigPriority.User, configJsonOrPathToFile); +var rtmCtx = RuntimeBridge.InitializeRC("graft.nuget.netapp"); +var result = rtmCtx.GetType("netapp.Calculator").CreateInstance() + .InvokeInstanceMethod("Add", 1, 2).Execute().GetValue(); ``` +`AddConfig` accepts a JSON string, a path to a JSON file, or the name of an environment variable holding either. The key under `configurations` must match the name passed to `InitializeRC`. Load one plugin config per process. + ## Configuration reference | Field | Required | Description | diff --git a/samples/README.md b/samples/README.md new file mode 100644 index 0000000..60ed441 --- /dev/null +++ b/samples/README.md @@ -0,0 +1,60 @@ +# Local Docker samples + +These samples run each messaging service locally together with three Graftcode +Gateway (`gg`) servers consuming from the same request queue or topic. + +Available samples: + +- `servicebus` - Azure Service Bus emulator on port `5672`; GG on `10301` +- `rabbitmq` - RabbitMQ on port `5673`, management UI on `15672`; GG on `10302` +- `kafka` - Kafka on port `9092`, Kafka UI on `8080`; GG on `10303` +- `sqs` - LocalStack SQS on port `4566`; GG on `10304` +- `pubsub` - Google Pub/Sub emulator on port `8085`; GG on `10305` + +The corresponding Graft Vision ports are `10401` through `10405`. + +## Prerequisites + +- Docker Desktop with Docker Compose +- Internet access during the first image build + +The server image downloads the latest Linux Graftcode Gateway and the latest +matching plugin release. Each sample includes `server/netapp.dll` as the hosted +test module. + +## Run a sample + +Open PowerShell in a sample directory and run: + +```powershell +.\run.ps1 +``` + +This executes: + +```powershell +docker compose up -d --build +``` + +Only the first GG server publishes its WebSocket and Vision ports to the host. +The other two servers are internal consumers, avoiding host-port conflicts +while demonstrating competing consumers. + +Stop a sample with: + +```powershell +docker compose down +``` + +Use `docker compose down -v` for SQS if you also want to remove persisted +LocalStack data. + +## Configuration + +The gateway-side plugin configuration is in `server/pluginconfig.json`. +Client applications must use the matching broker address, request destination, +reply destination, and credentials. + +`kafka-init` and `sqs-init` are one-shot initialization services. Their expected +state after startup is `Exited (0)`: Kafka topics or SQS queues have been +created successfully. diff --git a/samples/kafka/README.md b/samples/kafka/README.md deleted file mode 100644 index 0baa9c9..0000000 --- a/samples/kafka/README.md +++ /dev/null @@ -1,29 +0,0 @@ -# Kafka sample configs - -Local broker + UI and Graftcode plugin connection files. Plugin build, headers, and the configuration table are in [kafka/Readme.md](../../kafka/Readme.md). - -## Start Kafka - -```bash -cd samples/kafka -docker compose up -d -``` - -- Broker: `localhost:9092` -- UI: http://localhost:8080 - -Create topics (if needed): - -```bash -docker exec graftcode-kafka /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --if-not-exists --topic graft.requests --partitions 1 --replication-factor 1 -docker exec graftcode-kafka /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 --create --if-not-exists --topic graft.replies --partitions 1 --replication-factor 1 -``` - -## Config files - -| File | Use | -|------|-----| -| `pluginConfig.gateway.json` | `gg YourModule.dll --config pluginConfig.gateway.json` | -| `pluginConfig.client.json` | Client-side plugin block | -| `graftConfig.kafka.example.json` | Full GraftConfig example for .NET | -| `pluginConfig.json` | Same as gateway (default name) | diff --git a/samples/kafka/docker-compose.redpanda.yml b/samples/kafka/docker-compose.redpanda.yml deleted file mode 100644 index eff7477..0000000 --- a/samples/kafka/docker-compose.redpanda.yml +++ /dev/null @@ -1,10 +0,0 @@ -services: - redpanda: - image: redpandadata/redpanda:v24.2.4 - command: - - redpanda start - - --kafka-addr internal://0.0.0.0:9092,external://0.0.0.0:19092 - - --advertise-kafka-addr internal://redpanda:9092,external://localhost:19092 - - --mode dev-container - ports: - - "19092:19092" diff --git a/samples/kafka/docker-compose.yml b/samples/kafka/docker-compose.yml index 4e4f838..345de0b 100644 --- a/samples/kafka/docker-compose.yml +++ b/samples/kafka/docker-compose.yml @@ -1,4 +1,5 @@ -# Lokalny Kafka (KRaft) + UI — testy Graftcode KafkaPlugin. +name: graftcode-kafka + services: kafka: image: apache/kafka:3.8.1 @@ -19,14 +20,63 @@ services: KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 CLUSTER_ID: MkU3OEVBNTcwNTJENDM2Qk + healthcheck: + test: ["CMD", "/opt/kafka/bin/kafka-broker-api-versions.sh", "--bootstrap-server", "localhost:9092"] + interval: 5s + timeout: 5s + retries: 20 + + kafka-init: + image: apache/kafka:3.8.1 + depends_on: + kafka: + condition: service_healthy + command: + - /bin/sh + - -c + - > + /opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9094 + --create --if-not-exists --topic graft.requests --partitions 1 --replication-factor 1 && + /opt/kafka/bin/kafka-topics.sh --bootstrap-server kafka:9094 + --create --if-not-exists --topic graft.replies --partitions 1 --replication-factor 1 kafka-ui: image: provectuslabs/kafka-ui:v0.7.2 container_name: graftcode-kafka-ui depends_on: - - kafka + kafka: + condition: service_healthy ports: - "8080:8080" environment: KAFKA_CLUSTERS_0_NAME: local KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka:9094 + + server: + container_name: graftcode-kafka-server + image: graftcode-kafka-server + build: + context: . + dockerfile: server/Dockerfile + args: + PLUGIN_NAME: kafka + depends_on: + kafka-init: + condition: service_completed_successfully + ports: + - "10303:10301" + - "10403:10401" + + server-2: + container_name: graftcode-kafka-server-2 + image: graftcode-kafka-server + depends_on: + kafka-init: + condition: service_completed_successfully + + server-3: + container_name: graftcode-kafka-server-3 + image: graftcode-kafka-server + depends_on: + kafka-init: + condition: service_completed_successfully diff --git a/samples/kafka/graftConfig.kafka.example.json b/samples/kafka/graftConfig.kafka.example.json deleted file mode 100644 index 120c636..0000000 --- a/samples/kafka/graftConfig.kafka.example.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "configurations": { - "graft.nuget.PhysicsCalculator": { - "runtime": "netcore", - "stateless": true, - "plugin": { - "name": "KafkaPlugin", - "brokers": "localhost:9092", - "requestTopic": "graft.requests", - "replyTopic": "graft.replies", - "groupId": "graft-client", - "rpcTimeoutMs": 30000 - } - } - } -} diff --git a/samples/kafka/pluginConfig.gateway.json b/samples/kafka/pluginConfig.gateway.json deleted file mode 100644 index e607326..0000000 --- a/samples/kafka/pluginConfig.gateway.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "KafkaPlugin", - "brokers": "localhost:9092", - "requestTopic": "graft.requests", - "replyTopic": "graft.replies", - "groupId": "graft-gateway", - "rpcTimeoutMs": 30000 -} diff --git a/samples/kafka/pluginConfig.json b/samples/kafka/pluginConfig.json deleted file mode 100644 index e607326..0000000 --- a/samples/kafka/pluginConfig.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "name": "KafkaPlugin", - "brokers": "localhost:9092", - "requestTopic": "graft.requests", - "replyTopic": "graft.replies", - "groupId": "graft-gateway", - "rpcTimeoutMs": 30000 -} diff --git a/samples/kafka/run.ps1 b/samples/kafka/run.ps1 new file mode 100644 index 0000000..d701893 --- /dev/null +++ b/samples/kafka/run.ps1 @@ -0,0 +1,2 @@ +Set-Location $PSScriptRoot +docker compose up -d --build diff --git a/samples/kafka/scripts/create-topics.sh b/samples/kafka/scripts/create-topics.sh deleted file mode 100644 index efbb103..0000000 --- a/samples/kafka/scripts/create-topics.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/usr/bin/env bash -# Create the default Graftcode Kafka request/reply topics. -set -euo pipefail - -BROKERS="${BROKERS:-localhost:9092}" -REQUEST_TOPIC="${REQUEST_TOPIC:-graft.requests}" -REPLY_TOPIC="${REPLY_TOPIC:-graft.replies}" -CONTAINER="${KAFKA_CONTAINER:-}" - -create_with_kafka_topics() { - local bin="$1" - "$bin" --bootstrap-server "$BROKERS" --create --if-not-exists --topic "$REQUEST_TOPIC" --partitions 1 --replication-factor 1 - "$bin" --bootstrap-server "$BROKERS" --create --if-not-exists --topic "$REPLY_TOPIC" --partitions 1 --replication-factor 1 -} - -if [[ -n "$CONTAINER" ]]; then - echo "Creating topics via docker exec on container '$CONTAINER' (bootstrap $BROKERS)..." - # apache/kafka image path - if docker exec "$CONTAINER" test -x /opt/kafka/bin/kafka-topics.sh; then - docker exec "$CONTAINER" /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \ - --create --if-not-exists --topic "$REQUEST_TOPIC" --partitions 1 --replication-factor 1 - docker exec "$CONTAINER" /opt/kafka/bin/kafka-topics.sh --bootstrap-server localhost:9092 \ - --create --if-not-exists --topic "$REPLY_TOPIC" --partitions 1 --replication-factor 1 - # bitnami / other layouts - elif docker exec "$CONTAINER" sh -c 'command -v kafka-topics.sh' >/dev/null 2>&1; then - docker exec "$CONTAINER" kafka-topics.sh --bootstrap-server localhost:9092 \ - --create --if-not-exists --topic "$REQUEST_TOPIC" --partitions 1 --replication-factor 1 - docker exec "$CONTAINER" kafka-topics.sh --bootstrap-server localhost:9092 \ - --create --if-not-exists --topic "$REPLY_TOPIC" --partitions 1 --replication-factor 1 - else - echo "Could not find kafka-topics.sh inside $CONTAINER" >&2 - exit 1 - fi -elif command -v kafka-topics.sh >/dev/null 2>&1; then - create_with_kafka_topics kafka-topics.sh -elif command -v rpk >/dev/null 2>&1; then - echo "Creating topics via rpk (brokers $BROKERS)..." - rpk topic create "$REQUEST_TOPIC" -X brokers="$BROKERS" || true - rpk topic create "$REPLY_TOPIC" -X brokers="$BROKERS" || true -else - # Auto-detect a running compose kafka container - CONTAINER="$(docker ps --format '{{.Names}}' | grep -E 'kafka|redpanda' | head -n1 || true)" - if [[ -z "$CONTAINER" ]]; then - echo "No kafka-topics.sh/rpk on PATH and no kafka/redpanda container running." >&2 - echo "Start the broker first: docker compose up -d" >&2 - echo "Or set KAFKA_CONTAINER=." >&2 - exit 1 - fi - KAFKA_CONTAINER="$CONTAINER" "$0" - exit $? -fi - -echo "Topics ready: $REQUEST_TOPIC, $REPLY_TOPIC" diff --git a/samples/kafka/server/Dockerfile b/samples/kafka/server/Dockerfile new file mode 100644 index 0000000..d1e7c09 --- /dev/null +++ b/samples/kafka/server/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:bookworm-slim AS downloader + +ARG TARGETARCH +ARG PLUGIN_NAME + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) gateway_arch="amd64" ;; \ + arm64) gateway_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/gateway /out; \ + curl -fsSL "https://github.com/grft-dev/graftcode-gateway/releases/latest/download/gg_linux_${gateway_arch}.tar.gz" \ + | tar -xz -C /downloads/gateway; \ + find /downloads/gateway -type f -name gg -exec cp {} /out/gg \;; \ + test -f /out/gg; \ + chmod +x /out/gg + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) plugin_arch="x86_64" ;; \ + arm64) plugin_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/plugin; \ + curl -fsSL "https://github.com/grft-dev/graftcode-extensions/releases/latest/download/${PLUGIN_NAME}-linux-${plugin_arch}.tar.gz" \ + | tar -xz -C /downloads/plugin; \ + cp -a /downloads/plugin/. /out/ + +FROM mcr.microsoft.com/dotnet/runtime:10.0 + +WORKDIR /app +COPY --from=downloader /out/ ./ +COPY server/netapp.dll ./ +COPY server/pluginconfig.json ./ + +EXPOSE 10301 10401 + +ENTRYPOINT ["./gg", "netapp.dll", "--port", "10301", "--httpport", "10401", "--config", "pluginconfig.json"] diff --git a/samples/kafka/server/netapp.dll b/samples/kafka/server/netapp.dll new file mode 100644 index 0000000000000000000000000000000000000000..c53b4cae1039e3fc21ec07f093413d2d9bc506d6 GIT binary patch literal 4096 zcmeHJU2GIp6h5~<+drrjgg{JiutgAcvZc@nD&1|jg=+uoZVPH-JG*;#8JwNj%*+BC zqc#M6Fd^YVA3zi01I7oUB>F%!`Y6$ahz~{|Of-g|i9~tT80&ZL%yze}@ZggPp56Q1 zdw%Y@=bSrp?~Fckn(B$D0c~M{=n{ISn8Mq`Jk;&?e$-AMwp`hANsL_Ck~(a9TG4g# zu2Il3hHX2(mX?}ZvNhAz65~m&;AEw~dUb18=z3z1XhhW0i!Hg|E35rL_i1e+O4JNV zB#=ILKY9%G8R8MoSuqpqmeaNy1)CRxfJcwaAF2r)0BU%Mn zbSqJ6x$btn7KC3hX$F6w!l2iEdE7^z<@_`>&NYl1BSjj~obGyV2Aqm5h5;wqidI80 zM6bKja$v}@(i|H5YDcS~7*T%>tA>4CR}jZmM~yV65naU^f`$Kr*48&1+OfHAJK|{F zf%*N(C;CO`jbl5yOONV3(Vp%-?7WdI;At$@`4Z8e=x_2k>AR+#_t@eF_JmXJoJ!J7 zjCm_N_fHKcSP8{d=KC!t%{&Nci~Z|~TA2Am^w5SNm(_tzm4!>gv9zIap4^B`5%dxI zo-T<9bqmHVbY9#=-$E6k5gLbNqj&-ME-^}lCloFy+^z7k^6-=JmCL{{S3w~D*TpHZ zTa70)UiVdO(`N8f0k`oS{320Ec$@1GX$zf0Y#qP|Z34E^1Hd)Zt#FURxWZwDPbqv_ zp#c)Y?hK`&zA*#vSj;aK~h4dOZmK!wq)GLmZwX; zq`2qFLfR^)Outr~GTglMhj6aQV~+d$O8X(xlGD=lOvhfS9JX_gTQGbm4eORx1CE_D z^Cef=Rh>-AT++x%zdVUtysCV_DHKgh8Ba>fIIc!D`bs7fE~=36s}m~+Emm2h0?D*9s>Jzj{9~(>!IIfHriw}jRc>HL0RFA?#``wEVO2YJtmQfzVbJN08G&X4y z6bWjEgFsLdwoe+K447^lQ261o7SyO1oW4RG7GaKKUp1X2F*?D)%%Ppc-EgugRHEbe zeI431v}@2^tI8e{(A88jo04VAA9Fh_r!N!p8uSE4D6jr3{KCaB(}xQ z&+Lru?2Yc}i5gzn&ftd4`7=QhGfw)*Oqc_UIK@z<7qe+f9*FPU-AmyTr>}=ga2j9y z7`Z_ooH?|8q_L|fyb3s~Q66hNT1k~FBhbuZ6B3p+YM3?^ybN|kss~YN7q-HQx_wu= z`Cszis{r3mL>r0bm&O*{;CLri$k~p+9S+5aE(C8Ui!j~=oTenr0taalqhWk$je#Eq z4h3Ib^hv{Se+6$0Kk-aK-xo0I?yqsHq)u`sr|2OWB0Y(4- literal 0 HcmV?d00001 diff --git a/samples/kafka/pluginConfig.client.json b/samples/kafka/server/pluginconfig.json similarity index 53% rename from samples/kafka/pluginConfig.client.json rename to samples/kafka/server/pluginconfig.json index 8cac248..1e2316b 100644 --- a/samples/kafka/pluginConfig.client.json +++ b/samples/kafka/server/pluginconfig.json @@ -1,8 +1,7 @@ { "name": "KafkaPlugin", - "brokers": "localhost:9092", + "brokers": "kafka:9094", "requestTopic": "graft.requests", "replyTopic": "graft.replies", - "groupId": "graft-client", - "rpcTimeoutMs": 30000 + "groupId": "graft-gateway" } diff --git a/samples/pubsub/README.md b/samples/pubsub/README.md deleted file mode 100644 index 64b48f3..0000000 --- a/samples/pubsub/README.md +++ /dev/null @@ -1,61 +0,0 @@ -# Pub/Sub sample configs - -Local Google Cloud Pub/Sub emulator and the Graftcode plugin files used for a Windows smoke test. - -## Start the emulator - -```bash -cd samples/pubsub -docker compose up -d -``` - -- Emulator (`emulatorHost` / `PUBSUB_EMULATOR_HOST`): `localhost:8085` -- Project id: `graftcode-local` -- Request topic / subscription: `graft-requests` / `graft-requests-sub` -- Reply topic / subscription: `graft-replies` / `graft-replies-sub` - -The emulator does not create topics on startup. Sample configs set `"autoCreate": true`, so the plugin creates those four resources when `gg.exe` and the client start. To create them from the host instead: - -```bash -./scripts/create-resources.sh -``` - -`create-resources.sh` talks to `http://localhost:8085`. Give each runtime its own reply topic and subscription. Pub/Sub delivers every message on a topic to every subscription, and this plugin acks replies that do not match the in-flight correlation id. Delivery is at-least-once; set a dead-letter policy on `graft-requests-sub` when a failed call should stop being retried. Field list: [pubsub/Readme.md](../../pubsub/Readme.md). - -## Windows smoke test - -Colocate these next to each other: - -- `gg.exe` -- `netapp.dll` (the hosted module) -- `PubSubPlugin.dll` (curl, OpenSSL, zlib, and the VC++ runtime are linked in) - -Copy `pluginConfig.gateway.json` to that folder as `pubsubplugin.json` and run: - -```powershell -.\gg.exe netapp.dll --config pubsubplugin.json -``` - -Install `graft.nuget.netapp` from `http://localhost:81/GV`. In the client project: - -```csharp -graft.nuget.netapp.GraftConfig.SetConfig("clientconfig.json"); -var calculator = new graft.nuget.netapp.Calculator(); -calculator.Add(1, 2); -``` - -`clientconfig.json` is `graftConfig.pubsub.example.json`. `host` is `localhost:80` (the Gateway). `emulatorHost` stays `localhost:8085`. - -No credentials are required for the emulator. On Google Cloud, omit `emulatorHost` and `verifySsl`, and set `credentialsFile` (or `GOOGLE_APPLICATION_CREDENTIALS`) to a service-account JSON key. Set `autoCreate` to false if that identity cannot create topics. - -If the built library is `libPubSubPlugin.so` or `libPubSubPlugin.dylib`, set `"name": "libPubSubPlugin"`. - -## Config files - -| File | Use | -|------|-----| -| `pluginConfig.gateway.json` | Gateway config. Save as `pubsubplugin.json` next to `gg.exe`. | -| `pluginConfig.json` | Same as the gateway file. | -| `pluginConfig.client.json` | Plugin object only (no `configurations` wrapper). | -| `graftConfig.pubsub.example.json` | Client `GraftConfig` JSON. Save as `clientconfig.json`. | -| `pluginConfig.oneway.json` | Fire-and-forget (`oneWay: true`) for both sides. No reply topic. | diff --git a/samples/pubsub/docker-compose.yml b/samples/pubsub/docker-compose.yml index 43ca610..e294724 100644 --- a/samples/pubsub/docker-compose.yml +++ b/samples/pubsub/docker-compose.yml @@ -1,6 +1,7 @@ -# Local Google Cloud Pub/Sub emulator for Graftcode PubSubPlugin samples. +name: graftcode-pubsub + services: - pubsub-emulator: + pubsub: image: gcr.io/google.com/cloudsdktool/google-cloud-cli:emulators container_name: graftcode-pubsub command: @@ -12,3 +13,43 @@ services: - --host-port=0.0.0.0:8085 ports: - "8085:8085" + healthcheck: + test: + - CMD + - curl + - --fail + - --silent + - http://localhost:8085/v1/projects/graftcode-local/topics + interval: 2s + timeout: 5s + retries: 30 + start_period: 5s + + server: + container_name: graftcode-pubsub-server + image: graftcode-pubsub-server + build: + context: . + dockerfile: server/Dockerfile + args: + PLUGIN_NAME: pubsub + depends_on: + pubsub: + condition: service_healthy + ports: + - "10305:10301" + - "10405:10401" + + server-2: + container_name: graftcode-pubsub-server-2 + image: graftcode-pubsub-server + depends_on: + pubsub: + condition: service_healthy + + server-3: + container_name: graftcode-pubsub-server-3 + image: graftcode-pubsub-server + depends_on: + pubsub: + condition: service_healthy diff --git a/samples/pubsub/graftConfig.pubsub.example.json b/samples/pubsub/graftConfig.pubsub.example.json deleted file mode 100644 index 92e83c6..0000000 --- a/samples/pubsub/graftConfig.pubsub.example.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "configurations": { - "graft.nuget.netapp": { - "runtime": "netcore", - "host": "localhost:80", - "stateless": true, - "plugin": { - "name": "PubSubPlugin", - "projectId": "graftcode-local", - "emulatorHost": "localhost:8085", - "requestTopic": "graft-requests", - "replyTopic": "graft-replies", - "rpcTimeoutMs": 30000, - "ackDeadlineSeconds": 60, - "autoCreate": true, - "verifySsl": false - } - } - } -} diff --git a/samples/pubsub/pluginConfig.client.json b/samples/pubsub/pluginConfig.client.json deleted file mode 100644 index e454a0e..0000000 --- a/samples/pubsub/pluginConfig.client.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "PubSubPlugin", - "projectId": "graftcode-local", - "emulatorHost": "localhost:8085", - "requestTopic": "graft-requests", - "replyTopic": "graft-replies", - "rpcTimeoutMs": 30000, - "ackDeadlineSeconds": 60, - "autoCreate": true, - "verifySsl": false -} diff --git a/samples/pubsub/pluginConfig.json b/samples/pubsub/pluginConfig.json deleted file mode 100644 index 0b88cc9..0000000 --- a/samples/pubsub/pluginConfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "name": "PubSubPlugin", - "projectId": "graftcode-local", - "emulatorHost": "localhost:8085", - "requestTopic": "graft-requests", - "ackDeadlineSeconds": 60, - "autoCreate": true, - "verifySsl": false -} diff --git a/samples/pubsub/pluginConfig.oneway.json b/samples/pubsub/pluginConfig.oneway.json deleted file mode 100644 index d405d81..0000000 --- a/samples/pubsub/pluginConfig.oneway.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "name": "PubSubPlugin", - "projectId": "graftcode-local", - "emulatorHost": "localhost:8085", - "requestTopic": "graft-requests", - "oneWay": true, - "ackDeadlineSeconds": 60, - "autoCreate": true, - "verifySsl": false -} diff --git a/samples/pubsub/run.ps1 b/samples/pubsub/run.ps1 new file mode 100644 index 0000000..d701893 --- /dev/null +++ b/samples/pubsub/run.ps1 @@ -0,0 +1,2 @@ +Set-Location $PSScriptRoot +docker compose up -d --build diff --git a/samples/pubsub/scripts/create-resources.sh b/samples/pubsub/scripts/create-resources.sh deleted file mode 100755 index 27d58d5..0000000 --- a/samples/pubsub/scripts/create-resources.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/usr/bin/env bash -# Create the sample topic and subscription against an emulator already listening. -set -euo pipefail - -HOST="${PUBSUB_EMULATOR_HOST:-localhost:8085}" -HOST="${HOST#http://}" -HOST="${HOST#https://}" -HOST="${HOST%/}" -PROJECT="${PUBSUB_PROJECT_ID:-graftcode-local}" -BASE="http://${HOST}/v1/projects/${PROJECT}" - -put() { - local url="$1" - local body="$2" - local status - status="$(curl -sS -o /tmp/graftcode-pubsub-create.txt -w "%{http_code}" \ - -X PUT "$url" \ - -H "Content-Type: application/json" \ - -d "$body")" - if [[ "$status" != "200" && "$status" != "409" ]]; then - echo "PUT $url failed: HTTP $status" >&2 - cat /tmp/graftcode-pubsub-create.txt >&2 || true - exit 1 - fi -} - -put "${BASE}/topics/graft-requests" \ - "{\"name\":\"projects/${PROJECT}/topics/graft-requests\"}" -put "${BASE}/topics/graft-replies" \ - "{\"name\":\"projects/${PROJECT}/topics/graft-replies\"}" -put "${BASE}/subscriptions/graft-requests-sub" \ - "{\"name\":\"projects/${PROJECT}/subscriptions/graft-requests-sub\",\"topic\":\"projects/${PROJECT}/topics/graft-requests\",\"ackDeadlineSeconds\":60}" -put "${BASE}/subscriptions/graft-replies-sub" \ - "{\"name\":\"projects/${PROJECT}/subscriptions/graft-replies-sub\",\"topic\":\"projects/${PROJECT}/topics/graft-replies\",\"ackDeadlineSeconds\":60}" - -echo "Pub/Sub resources ready on ${HOST} in project ${PROJECT}" -echo " topic graft-requests subscription graft-requests-sub" -echo " topic graft-replies subscription graft-replies-sub" diff --git a/samples/pubsub/server/Dockerfile b/samples/pubsub/server/Dockerfile new file mode 100644 index 0000000..d1e7c09 --- /dev/null +++ b/samples/pubsub/server/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:bookworm-slim AS downloader + +ARG TARGETARCH +ARG PLUGIN_NAME + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) gateway_arch="amd64" ;; \ + arm64) gateway_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/gateway /out; \ + curl -fsSL "https://github.com/grft-dev/graftcode-gateway/releases/latest/download/gg_linux_${gateway_arch}.tar.gz" \ + | tar -xz -C /downloads/gateway; \ + find /downloads/gateway -type f -name gg -exec cp {} /out/gg \;; \ + test -f /out/gg; \ + chmod +x /out/gg + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) plugin_arch="x86_64" ;; \ + arm64) plugin_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/plugin; \ + curl -fsSL "https://github.com/grft-dev/graftcode-extensions/releases/latest/download/${PLUGIN_NAME}-linux-${plugin_arch}.tar.gz" \ + | tar -xz -C /downloads/plugin; \ + cp -a /downloads/plugin/. /out/ + +FROM mcr.microsoft.com/dotnet/runtime:10.0 + +WORKDIR /app +COPY --from=downloader /out/ ./ +COPY server/netapp.dll ./ +COPY server/pluginconfig.json ./ + +EXPOSE 10301 10401 + +ENTRYPOINT ["./gg", "netapp.dll", "--port", "10301", "--httpport", "10401", "--config", "pluginconfig.json"] diff --git a/samples/pubsub/server/netapp.dll b/samples/pubsub/server/netapp.dll new file mode 100644 index 0000000000000000000000000000000000000000..c53b4cae1039e3fc21ec07f093413d2d9bc506d6 GIT binary patch literal 4096 zcmeHJU2GIp6h5~<+drrjgg{JiutgAcvZc@nD&1|jg=+uoZVPH-JG*;#8JwNj%*+BC zqc#M6Fd^YVA3zi01I7oUB>F%!`Y6$ahz~{|Of-g|i9~tT80&ZL%yze}@ZggPp56Q1 zdw%Y@=bSrp?~Fckn(B$D0c~M{=n{ISn8Mq`Jk;&?e$-AMwp`hANsL_Ck~(a9TG4g# zu2Il3hHX2(mX?}ZvNhAz65~m&;AEw~dUb18=z3z1XhhW0i!Hg|E35rL_i1e+O4JNV zB#=ILKY9%G8R8MoSuqpqmeaNy1)CRxfJcwaAF2r)0BU%Mn zbSqJ6x$btn7KC3hX$F6w!l2iEdE7^z<@_`>&NYl1BSjj~obGyV2Aqm5h5;wqidI80 zM6bKja$v}@(i|H5YDcS~7*T%>tA>4CR}jZmM~yV65naU^f`$Kr*48&1+OfHAJK|{F zf%*N(C;CO`jbl5yOONV3(Vp%-?7WdI;At$@`4Z8e=x_2k>AR+#_t@eF_JmXJoJ!J7 zjCm_N_fHKcSP8{d=KC!t%{&Nci~Z|~TA2Am^w5SNm(_tzm4!>gv9zIap4^B`5%dxI zo-T<9bqmHVbY9#=-$E6k5gLbNqj&-ME-^}lCloFy+^z7k^6-=JmCL{{S3w~D*TpHZ zTa70)UiVdO(`N8f0k`oS{320Ec$@1GX$zf0Y#qP|Z34E^1Hd)Zt#FURxWZwDPbqv_ zp#c)Y?hK`&zA*#vSj;aK~h4dOZmK!wq)GLmZwX; zq`2qFLfR^)Outr~GTglMhj6aQV~+d$O8X(xlGD=lOvhfS9JX_gTQGbm4eORx1CE_D z^Cef=Rh>-AT++x%zdVUtysCV_DHKgh8Ba>fIIc!D`bs7fE~=36s}m~+Emm2h0?D*9s>Jzj{9~(>!IIfHriw}jRc>HL0RFA?#``wEVO2YJtmQfzVbJN08G&X4y z6bWjEgFsLdwoe+K447^lQ261o7SyO1oW4RG7GaKKUp1X2F*?D)%%Ppc-EgugRHEbe zeI431v}@2^tI8e{(A88jo04VAA9Fh_r!N!p8uSE4D6jr3{KCaB(}xQ z&+Lru?2Yc}i5gzn&ftd4`7=QhGfw)*Oqc_UIK@z<7qe+f9*FPU-AmyTr>}=ga2j9y z7`Z_ooH?|8q_L|fyb3s~Q66hNT1k~FBhbuZ6B3p+YM3?^ybN|kss~YN7q-HQx_wu= z`Cszis{r3mL>r0bm&O*{;CLri$k~p+9S+5aE(C8Ui!j~=oTenr0taalqhWk$je#Eq z4h3Ib^hv{Se+6$0Kk-aK-xo0I?yqsHq)u`sr|2OWB0Y(4- literal 0 HcmV?d00001 diff --git a/samples/pubsub/pluginConfig.gateway.json b/samples/pubsub/server/pluginconfig.json similarity index 82% rename from samples/pubsub/pluginConfig.gateway.json rename to samples/pubsub/server/pluginconfig.json index 0b88cc9..90bea10 100644 --- a/samples/pubsub/pluginConfig.gateway.json +++ b/samples/pubsub/server/pluginconfig.json @@ -1,7 +1,7 @@ { "name": "PubSubPlugin", "projectId": "graftcode-local", - "emulatorHost": "localhost:8085", + "emulatorHost": "pubsub:8085", "requestTopic": "graft-requests", "ackDeadlineSeconds": 60, "autoCreate": true, diff --git a/samples/rabbitmq/docker-compose.yml b/samples/rabbitmq/docker-compose.yml new file mode 100644 index 0000000..3fcb26e --- /dev/null +++ b/samples/rabbitmq/docker-compose.yml @@ -0,0 +1,49 @@ +name: graftcode-rabbitmq + +services: + rabbitmq: + container_name: graftcode-rabbitmq + image: rabbitmq:4-management + ports: + - "5673:5672" + - "15672:15672" + volumes: + - "./middleware/definitions.json:/etc/rabbitmq/definitions.json:ro" + environment: + RABBITMQ_DEFAULT_USER: graftcode + RABBITMQ_DEFAULT_PASS: graftcode + RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: '-rabbitmq_management load_definitions "/etc/rabbitmq/definitions.json"' + healthcheck: + test: ["CMD", "rabbitmq-diagnostics", "-q", "ping"] + interval: 2s + timeout: 5s + retries: 30 + + server: + container_name: graftcode-rabbitmq-server + image: graftcode-rabbitmq-server + build: + context: . + dockerfile: server/Dockerfile + args: + PLUGIN_NAME: rabbitmq + depends_on: + rabbitmq: + condition: service_healthy + ports: + - "10302:10301" + - "10402:10401" + + server-2: + container_name: graftcode-rabbitmq-server-2 + image: graftcode-rabbitmq-server + depends_on: + rabbitmq: + condition: service_healthy + + server-3: + container_name: graftcode-rabbitmq-server-3 + image: graftcode-rabbitmq-server + depends_on: + rabbitmq: + condition: service_healthy diff --git a/samples/rabbitmq/middleware/definitions.json b/samples/rabbitmq/middleware/definitions.json new file mode 100644 index 0000000..c056251 --- /dev/null +++ b/samples/rabbitmq/middleware/definitions.json @@ -0,0 +1,40 @@ +{ + "users": [ + { + "name": "graftcode", + "password_hash": "fr75dvjAdO7zcXfvmlnECmBe+yhVndelMcf/wvwT7rwBT43u", + "hashing_algorithm": "rabbit_password_hashing_sha256", + "tags": ["administrator"] + } + ], + "vhosts": [ + { + "name": "/" + } + ], + "permissions": [ + { + "user": "graftcode", + "vhost": "/", + "configure": ".*", + "write": ".*", + "read": ".*" + } + ], + "queues": [ + { + "name": "myqueue", + "vhost": "/", + "durable": true, + "auto_delete": false, + "arguments": {} + }, + { + "name": "myqueue.reply", + "vhost": "/", + "durable": true, + "auto_delete": false, + "arguments": {} + } + ] +} diff --git a/samples/rabbitmq/run.ps1 b/samples/rabbitmq/run.ps1 new file mode 100644 index 0000000..d701893 --- /dev/null +++ b/samples/rabbitmq/run.ps1 @@ -0,0 +1,2 @@ +Set-Location $PSScriptRoot +docker compose up -d --build diff --git a/samples/rabbitmq/server/Dockerfile b/samples/rabbitmq/server/Dockerfile new file mode 100644 index 0000000..d1e7c09 --- /dev/null +++ b/samples/rabbitmq/server/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:bookworm-slim AS downloader + +ARG TARGETARCH +ARG PLUGIN_NAME + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) gateway_arch="amd64" ;; \ + arm64) gateway_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/gateway /out; \ + curl -fsSL "https://github.com/grft-dev/graftcode-gateway/releases/latest/download/gg_linux_${gateway_arch}.tar.gz" \ + | tar -xz -C /downloads/gateway; \ + find /downloads/gateway -type f -name gg -exec cp {} /out/gg \;; \ + test -f /out/gg; \ + chmod +x /out/gg + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) plugin_arch="x86_64" ;; \ + arm64) plugin_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/plugin; \ + curl -fsSL "https://github.com/grft-dev/graftcode-extensions/releases/latest/download/${PLUGIN_NAME}-linux-${plugin_arch}.tar.gz" \ + | tar -xz -C /downloads/plugin; \ + cp -a /downloads/plugin/. /out/ + +FROM mcr.microsoft.com/dotnet/runtime:10.0 + +WORKDIR /app +COPY --from=downloader /out/ ./ +COPY server/netapp.dll ./ +COPY server/pluginconfig.json ./ + +EXPOSE 10301 10401 + +ENTRYPOINT ["./gg", "netapp.dll", "--port", "10301", "--httpport", "10401", "--config", "pluginconfig.json"] diff --git a/samples/rabbitmq/server/netapp.dll b/samples/rabbitmq/server/netapp.dll new file mode 100644 index 0000000000000000000000000000000000000000..c53b4cae1039e3fc21ec07f093413d2d9bc506d6 GIT binary patch literal 4096 zcmeHJU2GIp6h5~<+drrjgg{JiutgAcvZc@nD&1|jg=+uoZVPH-JG*;#8JwNj%*+BC zqc#M6Fd^YVA3zi01I7oUB>F%!`Y6$ahz~{|Of-g|i9~tT80&ZL%yze}@ZggPp56Q1 zdw%Y@=bSrp?~Fckn(B$D0c~M{=n{ISn8Mq`Jk;&?e$-AMwp`hANsL_Ck~(a9TG4g# zu2Il3hHX2(mX?}ZvNhAz65~m&;AEw~dUb18=z3z1XhhW0i!Hg|E35rL_i1e+O4JNV zB#=ILKY9%G8R8MoSuqpqmeaNy1)CRxfJcwaAF2r)0BU%Mn zbSqJ6x$btn7KC3hX$F6w!l2iEdE7^z<@_`>&NYl1BSjj~obGyV2Aqm5h5;wqidI80 zM6bKja$v}@(i|H5YDcS~7*T%>tA>4CR}jZmM~yV65naU^f`$Kr*48&1+OfHAJK|{F zf%*N(C;CO`jbl5yOONV3(Vp%-?7WdI;At$@`4Z8e=x_2k>AR+#_t@eF_JmXJoJ!J7 zjCm_N_fHKcSP8{d=KC!t%{&Nci~Z|~TA2Am^w5SNm(_tzm4!>gv9zIap4^B`5%dxI zo-T<9bqmHVbY9#=-$E6k5gLbNqj&-ME-^}lCloFy+^z7k^6-=JmCL{{S3w~D*TpHZ zTa70)UiVdO(`N8f0k`oS{320Ec$@1GX$zf0Y#qP|Z34E^1Hd)Zt#FURxWZwDPbqv_ zp#c)Y?hK`&zA*#vSj;aK~h4dOZmK!wq)GLmZwX; zq`2qFLfR^)Outr~GTglMhj6aQV~+d$O8X(xlGD=lOvhfS9JX_gTQGbm4eORx1CE_D z^Cef=Rh>-AT++x%zdVUtysCV_DHKgh8Ba>fIIc!D`bs7fE~=36s}m~+Emm2h0?D*9s>Jzj{9~(>!IIfHriw}jRc>HL0RFA?#``wEVO2YJtmQfzVbJN08G&X4y z6bWjEgFsLdwoe+K447^lQ261o7SyO1oW4RG7GaKKUp1X2F*?D)%%Ppc-EgugRHEbe zeI431v}@2^tI8e{(A88jo04VAA9Fh_r!N!p8uSE4D6jr3{KCaB(}xQ z&+Lru?2Yc}i5gzn&ftd4`7=QhGfw)*Oqc_UIK@z<7qe+f9*FPU-AmyTr>}=ga2j9y z7`Z_ooH?|8q_L|fyb3s~Q66hNT1k~FBhbuZ6B3p+YM3?^ybN|kss~YN7q-HQx_wu= z`Cszis{r3mL>r0bm&O*{;CLri$k~p+9S+5aE(C8Ui!j~=oTenr0taalqhWk$je#Eq z4h3Ib^hv{Se+6$0Kk-aK-xo0I?yqsHq)u`sr|2OWB0Y(4- literal 0 HcmV?d00001 diff --git a/samples/rabbitmq/server/pluginconfig.json b/samples/rabbitmq/server/pluginconfig.json new file mode 100644 index 0000000..02ce2b4 --- /dev/null +++ b/samples/rabbitmq/server/pluginconfig.json @@ -0,0 +1,9 @@ +{ + "name": "RabbitmqPlugin", + "host": "rabbitmq", + "port": 5672, + "queue": "myqueue", + "user": "graftcode", + "password": "graftcode", + "vhost": "/" +} diff --git a/samples/servicebus/client/client_config.json b/samples/servicebus/client/client_config.json new file mode 100644 index 0000000..f00bdad --- /dev/null +++ b/samples/servicebus/client/client_config.json @@ -0,0 +1,13 @@ +{ + "configurations": { + "graft.nuget.netapp": { + "plugin": { + "name": "ServiceBusPlugin", + "connectionString": "Endpoint=sb://localhost:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", + "queue": "myqueue", + "replyQueue": "myqueue.reply", + "rpcTimeoutMs": 30000 + } + } + } +} diff --git a/samples/servicebus/docker-compose.yml b/samples/servicebus/docker-compose.yml new file mode 100644 index 0000000..c297fc4 --- /dev/null +++ b/samples/servicebus/docker-compose.yml @@ -0,0 +1,85 @@ +name: graftcode-servicebus + +services: + servicebus: + container_name: servicebus-emulator + image: mcr.microsoft.com/azure-messaging/servicebus-emulator:latest + volumes: + - "./middleware/Config.json:/ServiceBus_Emulator/ConfigFiles/Config.json:ro" + ports: + - "5672:5672" + - "5300:5300" + environment: + SQL_SERVER: mssql + MSSQL_SA_PASSWORD: "Local123!" + ACCEPT_EULA: "Y" + EMULATOR_HTTP_PORT: "5300" + SQL_WAIT_INTERVAL: "30" + depends_on: + mssql: + condition: service_healthy + + mssql: + container_name: servicebus-mssql + image: mcr.microsoft.com/mssql/server:2022-latest + environment: + ACCEPT_EULA: "Y" + MSSQL_SA_PASSWORD: "Local123!" + healthcheck: + test: + - CMD-SHELL + - >- + /opt/mssql-tools18/bin/sqlcmd -C -S localhost -U sa + -P "$$MSSQL_SA_PASSWORD" -b -Q + "IF EXISTS (SELECT 1 FROM sys.databases WHERE state_desc <> 'ONLINE') + THROW 50000, 'Database recovery incomplete', 1;" + > /dev/null + interval: 2s + timeout: 5s + retries: 60 + start_period: 10s + + servicebus-ready: + image: curlimages/curl:8.16.0 + depends_on: + servicebus: + condition: service_started + command: + - --fail + - --silent + - --show-error + - --retry + - "30" + - --retry-delay + - "2" + - --retry-all-errors + - http://servicebus:5300/health + + server: + container_name: graftcode-servicebus-server + image: graftcode-servicebus-server + build: + context: . + dockerfile: server/Dockerfile + args: + PLUGIN_NAME: servicebus + depends_on: + servicebus-ready: + condition: service_completed_successfully + ports: + - "10301:10301" + - "10401:10401" + + server-2: + container_name: graftcode-servicebus-server-2 + image: graftcode-servicebus-server + depends_on: + servicebus-ready: + condition: service_completed_successfully + + server-3: + container_name: graftcode-servicebus-server-3 + image: graftcode-servicebus-server + depends_on: + servicebus-ready: + condition: service_completed_successfully diff --git a/samples/servicebus/middleware/Config.json b/samples/servicebus/middleware/Config.json new file mode 100644 index 0000000..72bf067 --- /dev/null +++ b/samples/servicebus/middleware/Config.json @@ -0,0 +1,43 @@ +{ + "UserConfig": { + "Namespaces": [ + { + "Name": "sbemulatorns", + "Queues": [ + { + "Name": "myqueue", + "Properties": { + "DeadLetteringOnMessageExpiration": false, + "DefaultMessageTimeToLive": "PT1H", + "DuplicateDetectionHistoryTimeWindow": "PT20S", + "ForwardDeadLetteredMessagesTo": "", + "ForwardTo": "", + "LockDuration": "PT1M", + "MaxDeliveryCount": 10, + "RequiresDuplicateDetection": false, + "RequiresSession": false + } + }, + { + "Name": "myqueue.reply", + "Properties": { + "DeadLetteringOnMessageExpiration": false, + "DefaultMessageTimeToLive": "PT1H", + "DuplicateDetectionHistoryTimeWindow": "PT20S", + "ForwardDeadLetteredMessagesTo": "", + "ForwardTo": "", + "LockDuration": "PT1M", + "MaxDeliveryCount": 10, + "RequiresDuplicateDetection": false, + "RequiresSession": true + } + } + ], + "Topics": [] + } + ], + "Logging": { + "Type": "Console" + } + } +} diff --git a/samples/servicebus/run.ps1 b/samples/servicebus/run.ps1 new file mode 100644 index 0000000..44530aa --- /dev/null +++ b/samples/servicebus/run.ps1 @@ -0,0 +1,2 @@ +Set-Location $PSScriptRoot +docker compose up -d --build \ No newline at end of file diff --git a/samples/servicebus/server/Dockerfile b/samples/servicebus/server/Dockerfile new file mode 100644 index 0000000..d1e7c09 --- /dev/null +++ b/samples/servicebus/server/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:bookworm-slim AS downloader + +ARG TARGETARCH +ARG PLUGIN_NAME + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) gateway_arch="amd64" ;; \ + arm64) gateway_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/gateway /out; \ + curl -fsSL "https://github.com/grft-dev/graftcode-gateway/releases/latest/download/gg_linux_${gateway_arch}.tar.gz" \ + | tar -xz -C /downloads/gateway; \ + find /downloads/gateway -type f -name gg -exec cp {} /out/gg \;; \ + test -f /out/gg; \ + chmod +x /out/gg + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) plugin_arch="x86_64" ;; \ + arm64) plugin_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/plugin; \ + curl -fsSL "https://github.com/grft-dev/graftcode-extensions/releases/latest/download/${PLUGIN_NAME}-linux-${plugin_arch}.tar.gz" \ + | tar -xz -C /downloads/plugin; \ + cp -a /downloads/plugin/. /out/ + +FROM mcr.microsoft.com/dotnet/runtime:10.0 + +WORKDIR /app +COPY --from=downloader /out/ ./ +COPY server/netapp.dll ./ +COPY server/pluginconfig.json ./ + +EXPOSE 10301 10401 + +ENTRYPOINT ["./gg", "netapp.dll", "--port", "10301", "--httpport", "10401", "--config", "pluginconfig.json"] diff --git a/samples/servicebus/server/netapp.dll b/samples/servicebus/server/netapp.dll new file mode 100644 index 0000000000000000000000000000000000000000..c53b4cae1039e3fc21ec07f093413d2d9bc506d6 GIT binary patch literal 4096 zcmeHJU2GIp6h5~<+drrjgg{JiutgAcvZc@nD&1|jg=+uoZVPH-JG*;#8JwNj%*+BC zqc#M6Fd^YVA3zi01I7oUB>F%!`Y6$ahz~{|Of-g|i9~tT80&ZL%yze}@ZggPp56Q1 zdw%Y@=bSrp?~Fckn(B$D0c~M{=n{ISn8Mq`Jk;&?e$-AMwp`hANsL_Ck~(a9TG4g# zu2Il3hHX2(mX?}ZvNhAz65~m&;AEw~dUb18=z3z1XhhW0i!Hg|E35rL_i1e+O4JNV zB#=ILKY9%G8R8MoSuqpqmeaNy1)CRxfJcwaAF2r)0BU%Mn zbSqJ6x$btn7KC3hX$F6w!l2iEdE7^z<@_`>&NYl1BSjj~obGyV2Aqm5h5;wqidI80 zM6bKja$v}@(i|H5YDcS~7*T%>tA>4CR}jZmM~yV65naU^f`$Kr*48&1+OfHAJK|{F zf%*N(C;CO`jbl5yOONV3(Vp%-?7WdI;At$@`4Z8e=x_2k>AR+#_t@eF_JmXJoJ!J7 zjCm_N_fHKcSP8{d=KC!t%{&Nci~Z|~TA2Am^w5SNm(_tzm4!>gv9zIap4^B`5%dxI zo-T<9bqmHVbY9#=-$E6k5gLbNqj&-ME-^}lCloFy+^z7k^6-=JmCL{{S3w~D*TpHZ zTa70)UiVdO(`N8f0k`oS{320Ec$@1GX$zf0Y#qP|Z34E^1Hd)Zt#FURxWZwDPbqv_ zp#c)Y?hK`&zA*#vSj;aK~h4dOZmK!wq)GLmZwX; zq`2qFLfR^)Outr~GTglMhj6aQV~+d$O8X(xlGD=lOvhfS9JX_gTQGbm4eORx1CE_D z^Cef=Rh>-AT++x%zdVUtysCV_DHKgh8Ba>fIIc!D`bs7fE~=36s}m~+Emm2h0?D*9s>Jzj{9~(>!IIfHriw}jRc>HL0RFA?#``wEVO2YJtmQfzVbJN08G&X4y z6bWjEgFsLdwoe+K447^lQ261o7SyO1oW4RG7GaKKUp1X2F*?D)%%Ppc-EgugRHEbe zeI431v}@2^tI8e{(A88jo04VAA9Fh_r!N!p8uSE4D6jr3{KCaB(}xQ z&+Lru?2Yc}i5gzn&ftd4`7=QhGfw)*Oqc_UIK@z<7qe+f9*FPU-AmyTr>}=ga2j9y z7`Z_ooH?|8q_L|fyb3s~Q66hNT1k~FBhbuZ6B3p+YM3?^ybN|kss~YN7q-HQx_wu= z`Cszis{r3mL>r0bm&O*{;CLri$k~p+9S+5aE(C8Ui!j~=oTenr0taalqhWk$je#Eq z4h3Ib^hv{Se+6$0Kk-aK-xo0I?yqsHq)u`sr|2OWB0Y(4- literal 0 HcmV?d00001 diff --git a/samples/servicebus/server/pluginconfig.json b/samples/servicebus/server/pluginconfig.json new file mode 100644 index 0000000..faf7393 --- /dev/null +++ b/samples/servicebus/server/pluginconfig.json @@ -0,0 +1,5 @@ +{ + "name": "ServiceBusPlugin", + "connectionString": "Endpoint=sb://servicebus:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", + "queue": "myqueue" +} diff --git a/samples/sqs/README.md b/samples/sqs/README.md deleted file mode 100644 index 4880878..0000000 --- a/samples/sqs/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# SQS sample configs - -Local Amazon SQS (LocalStack) and the Graftcode plugin files used for a working Windows smoke test. - -## Start LocalStack - -```bash -cd samples/sqs -docker compose up -d -``` - -- SDK endpoint (`endpointOverride`): `http://localhost:4566` -- Region: `us-east-1` -- Account id in LocalStack queue URLs: `000000000000` - -`docker compose up` runs `scripts/init-queues.sh` and creates `graft-requests` and `graft-replies`. To create them again from the host: - -```bash -./scripts/create-queues.sh -``` - -The AWS CLI talks to `http://localhost:4566`. Put the queue URL that `get-queue-url` prints into the plugin config. LocalStack returns: - -- `http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-requests` -- `http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-replies` - -`localhost.localstack.cloud` resolves to 127.0.0.1. Do not replace that host with `localhost` in the queue URL. The SDK endpoint and the queue URL are different settings. - -Give each runtime its own reply queue. SQS cannot filter a receive by correlation id, so independent clients must not share one reply queue. Delivery is at-least-once; pair the request queue with a dead-letter queue when a failed call should stop being retried. Field list: [sqs/Readme.md](../../sqs/Readme.md). - -## Windows smoke test - -Colocate these next to each other: - -- `gg.exe` -- `netapp.dll` (the hosted module) -- `SqsPlugin.dll` (the AWS SDK and the VC++ runtime are linked in) - -Copy `pluginConfig.gateway.json` to that folder as `sqsplugin.json` and run: - -```powershell -.\gg.exe netapp.dll --config sqsplugin.json -``` - -Install `graft.nuget.netapp` from `http://localhost:81/GV`. In the client project: - -```csharp -graft.nuget.netapp.GraftConfig.SetConfig("clientconfig.json"); -var calculator = new graft.nuget.netapp.Calculator(); -calculator.Add(1, 2); -``` - -`clientconfig.json` is `graftConfig.sqs.example.json`. `host` is `localhost:80` (the Gateway). `endpointOverride` stays `http://localhost:4566`. - -Static `test` / `test` credentials are for LocalStack only. On AWS, omit `endpointOverride`, `accessKeyId`, `secretAccessKey`, and `verifySsl`. - -If the built library is `libSqsPlugin.so` or `libSqsPlugin.dylib`, set `"name": "libSqsPlugin"`. - -## Config files - -| File | Use | -|------|-----| -| `pluginConfig.gateway.json` | Gateway config. Save as `sqsplugin.json` next to `gg.exe`. | -| `pluginConfig.json` | Same as the gateway file. | -| `pluginConfig.client.json` | Plugin object only (no `configurations` wrapper). | -| `graftConfig.sqs.example.json` | Client `GraftConfig` JSON. Save as `clientconfig.json`. | -| `pluginConfig.oneway.json` | Fire-and-forget (`oneWay: true`) for both sides. No reply queue. | diff --git a/samples/sqs/docker-compose.yml b/samples/sqs/docker-compose.yml index 19c143b..eb3c2b2 100644 --- a/samples/sqs/docker-compose.yml +++ b/samples/sqs/docker-compose.yml @@ -1,4 +1,5 @@ -# Local Amazon SQS (LocalStack) for Graftcode SqsPlugin samples. +name: graftcode-sqs + services: localstack: image: localstack/localstack:4.8 @@ -9,9 +10,65 @@ services: SERVICES: sqs AWS_DEFAULT_REGION: us-east-1 DEBUG: 0 + SQS_ENDPOINT_STRATEGY: standard volumes: - localstack-data:/var/lib/localstack - - ./scripts/init-queues.sh:/etc/localstack/init/ready.d/init-queues.sh + healthcheck: + test: ["CMD", "awslocal", "sqs", "list-queues"] + interval: 2s + timeout: 5s + retries: 30 + networks: + default: + aliases: + - sqs.us-east-1.localhost.localstack.cloud + + sqs-init: + image: localstack/localstack:4.8 + depends_on: + localstack: + condition: service_healthy + environment: + AWS_DEFAULT_REGION: us-east-1 + AWS_ACCESS_KEY_ID: test + AWS_SECRET_ACCESS_KEY: test + entrypoint: /bin/sh + command: + - -c + - > + awslocal --endpoint-url=http://localstack:4566 sqs create-queue --queue-name graft-requests && + awslocal --endpoint-url=http://localstack:4566 sqs create-queue --queue-name graft-replies && + awslocal --endpoint-url=http://localstack:4566 sqs get-queue-url --queue-name graft-requests && + awslocal --endpoint-url=http://localstack:4566 sqs get-queue-url --queue-name graft-replies + + server: + container_name: graftcode-sqs-server + image: graftcode-sqs-server + build: + context: . + dockerfile: server/Dockerfile + args: + PLUGIN_NAME: sqs + depends_on: + sqs-init: + condition: service_completed_successfully + ports: + - "10304:10301" + - "10404:10401" + + server-2: + container_name: graftcode-sqs-server-2 + image: graftcode-sqs-server + depends_on: + sqs-init: + condition: service_completed_successfully + + server-3: + container_name: graftcode-sqs-server-3 + image: graftcode-sqs-server + depends_on: + sqs-init: + condition: service_completed_successfully volumes: localstack-data: diff --git a/samples/sqs/graftConfig.sqs.example.json b/samples/sqs/graftConfig.sqs.example.json deleted file mode 100644 index 7738a10..0000000 --- a/samples/sqs/graftConfig.sqs.example.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "configurations": { - "graft.nuget.netapp": { - "runtime": "netcore", - "host": "localhost:80", - "stateless": true, - "plugin": { - "name": "SqsPlugin", - "region": "us-east-1", - "endpointOverride": "http://localhost:4566", - "accessKeyId": "test", - "secretAccessKey": "test", - "requestQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-requests", - "replyQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-replies", - "rpcTimeoutMs": 30000, - "waitTimeSeconds": 10, - "visibilityTimeoutSeconds": 60, - "verifySsl": false - } - } - } -} diff --git a/samples/sqs/pluginConfig.client.json b/samples/sqs/pluginConfig.client.json deleted file mode 100644 index 4668284..0000000 --- a/samples/sqs/pluginConfig.client.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "name": "SqsPlugin", - "region": "us-east-1", - "endpointOverride": "http://localhost:4566", - "accessKeyId": "test", - "secretAccessKey": "test", - "requestQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-requests", - "replyQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-replies", - "rpcTimeoutMs": 30000, - "waitTimeSeconds": 10, - "visibilityTimeoutSeconds": 60, - "verifySsl": false -} diff --git a/samples/sqs/pluginConfig.json b/samples/sqs/pluginConfig.json deleted file mode 100644 index 4e3fc9f..0000000 --- a/samples/sqs/pluginConfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "SqsPlugin", - "region": "us-east-1", - "endpointOverride": "http://localhost:4566", - "accessKeyId": "test", - "secretAccessKey": "test", - "requestQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-requests", - "waitTimeSeconds": 10, - "visibilityTimeoutSeconds": 60, - "verifySsl": false -} diff --git a/samples/sqs/pluginConfig.oneway.json b/samples/sqs/pluginConfig.oneway.json deleted file mode 100644 index 21f8477..0000000 --- a/samples/sqs/pluginConfig.oneway.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "SqsPlugin", - "region": "us-east-1", - "endpointOverride": "http://localhost:4566", - "accessKeyId": "test", - "secretAccessKey": "test", - "requestQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-requests", - "oneWay": true, - "waitTimeSeconds": 10, - "visibilityTimeoutSeconds": 60, - "verifySsl": false -} diff --git a/samples/sqs/run.ps1 b/samples/sqs/run.ps1 new file mode 100644 index 0000000..d701893 --- /dev/null +++ b/samples/sqs/run.ps1 @@ -0,0 +1,2 @@ +Set-Location $PSScriptRoot +docker compose up -d --build diff --git a/samples/sqs/scripts/create-queues.sh b/samples/sqs/scripts/create-queues.sh deleted file mode 100755 index efe78fd..0000000 --- a/samples/sqs/scripts/create-queues.sh +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/env bash -# Create the sample queues against a LocalStack already listening on localhost. -set -euo pipefail - -ENDPOINT="${SQS_ENDPOINT_OVERRIDE:-http://localhost:4566}" -REGION="${AWS_REGION:-us-east-1}" -export AWS_ACCESS_KEY_ID="${AWS_ACCESS_KEY_ID:-test}" -export AWS_SECRET_ACCESS_KEY="${AWS_SECRET_ACCESS_KEY:-test}" -export AWS_DEFAULT_REGION="$REGION" - -if ! command -v aws >/dev/null 2>&1; then - echo "aws CLI is required (https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)" >&2 - exit 1 -fi - -aws --endpoint-url "$ENDPOINT" sqs create-queue --queue-name graft-requests --region "$REGION" -aws --endpoint-url "$ENDPOINT" sqs create-queue --queue-name graft-replies --region "$REGION" - -echo "Request queue:" -aws --endpoint-url "$ENDPOINT" sqs get-queue-url --queue-name graft-requests --region "$REGION" --output text -echo "Reply queue:" -aws --endpoint-url "$ENDPOINT" sqs get-queue-url --queue-name graft-replies --region "$REGION" --output text diff --git a/samples/sqs/scripts/init-queues.sh b/samples/sqs/scripts/init-queues.sh deleted file mode 100755 index 4e034c5..0000000 --- a/samples/sqs/scripts/init-queues.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh -# Runs inside LocalStack once the edge port is ready. -set -eu - -awslocal sqs create-queue --queue-name graft-requests -awslocal sqs create-queue --queue-name graft-replies -echo "SQS queues ready: graft-requests, graft-replies" diff --git a/samples/sqs/server/Dockerfile b/samples/sqs/server/Dockerfile new file mode 100644 index 0000000..d1e7c09 --- /dev/null +++ b/samples/sqs/server/Dockerfile @@ -0,0 +1,43 @@ +FROM debian:bookworm-slim AS downloader + +ARG TARGETARCH +ARG PLUGIN_NAME + +RUN apt-get update \ + && apt-get install -y --no-install-recommends ca-certificates curl \ + && rm -rf /var/lib/apt/lists/* + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) gateway_arch="amd64" ;; \ + arm64) gateway_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/gateway /out; \ + curl -fsSL "https://github.com/grft-dev/graftcode-gateway/releases/latest/download/gg_linux_${gateway_arch}.tar.gz" \ + | tar -xz -C /downloads/gateway; \ + find /downloads/gateway -type f -name gg -exec cp {} /out/gg \;; \ + test -f /out/gg; \ + chmod +x /out/gg + +RUN set -eux; \ + case "${TARGETARCH}" in \ + amd64) plugin_arch="x86_64" ;; \ + arm64) plugin_arch="arm64" ;; \ + *) echo "Unsupported architecture: ${TARGETARCH}" >&2; exit 1 ;; \ + esac; \ + mkdir -p /downloads/plugin; \ + curl -fsSL "https://github.com/grft-dev/graftcode-extensions/releases/latest/download/${PLUGIN_NAME}-linux-${plugin_arch}.tar.gz" \ + | tar -xz -C /downloads/plugin; \ + cp -a /downloads/plugin/. /out/ + +FROM mcr.microsoft.com/dotnet/runtime:10.0 + +WORKDIR /app +COPY --from=downloader /out/ ./ +COPY server/netapp.dll ./ +COPY server/pluginconfig.json ./ + +EXPOSE 10301 10401 + +ENTRYPOINT ["./gg", "netapp.dll", "--port", "10301", "--httpport", "10401", "--config", "pluginconfig.json"] diff --git a/samples/sqs/server/netapp.dll b/samples/sqs/server/netapp.dll new file mode 100644 index 0000000000000000000000000000000000000000..c53b4cae1039e3fc21ec07f093413d2d9bc506d6 GIT binary patch literal 4096 zcmeHJU2GIp6h5~<+drrjgg{JiutgAcvZc@nD&1|jg=+uoZVPH-JG*;#8JwNj%*+BC zqc#M6Fd^YVA3zi01I7oUB>F%!`Y6$ahz~{|Of-g|i9~tT80&ZL%yze}@ZggPp56Q1 zdw%Y@=bSrp?~Fckn(B$D0c~M{=n{ISn8Mq`Jk;&?e$-AMwp`hANsL_Ck~(a9TG4g# zu2Il3hHX2(mX?}ZvNhAz65~m&;AEw~dUb18=z3z1XhhW0i!Hg|E35rL_i1e+O4JNV zB#=ILKY9%G8R8MoSuqpqmeaNy1)CRxfJcwaAF2r)0BU%Mn zbSqJ6x$btn7KC3hX$F6w!l2iEdE7^z<@_`>&NYl1BSjj~obGyV2Aqm5h5;wqidI80 zM6bKja$v}@(i|H5YDcS~7*T%>tA>4CR}jZmM~yV65naU^f`$Kr*48&1+OfHAJK|{F zf%*N(C;CO`jbl5yOONV3(Vp%-?7WdI;At$@`4Z8e=x_2k>AR+#_t@eF_JmXJoJ!J7 zjCm_N_fHKcSP8{d=KC!t%{&Nci~Z|~TA2Am^w5SNm(_tzm4!>gv9zIap4^B`5%dxI zo-T<9bqmHVbY9#=-$E6k5gLbNqj&-ME-^}lCloFy+^z7k^6-=JmCL{{S3w~D*TpHZ zTa70)UiVdO(`N8f0k`oS{320Ec$@1GX$zf0Y#qP|Z34E^1Hd)Zt#FURxWZwDPbqv_ zp#c)Y?hK`&zA*#vSj;aK~h4dOZmK!wq)GLmZwX; zq`2qFLfR^)Outr~GTglMhj6aQV~+d$O8X(xlGD=lOvhfS9JX_gTQGbm4eORx1CE_D z^Cef=Rh>-AT++x%zdVUtysCV_DHKgh8Ba>fIIc!D`bs7fE~=36s}m~+Emm2h0?D*9s>Jzj{9~(>!IIfHriw}jRc>HL0RFA?#``wEVO2YJtmQfzVbJN08G&X4y z6bWjEgFsLdwoe+K447^lQ261o7SyO1oW4RG7GaKKUp1X2F*?D)%%Ppc-EgugRHEbe zeI431v}@2^tI8e{(A88jo04VAA9Fh_r!N!p8uSE4D6jr3{KCaB(}xQ z&+Lru?2Yc}i5gzn&ftd4`7=QhGfw)*Oqc_UIK@z<7qe+f9*FPU-AmyTr>}=ga2j9y z7`Z_ooH?|8q_L|fyb3s~Q66hNT1k~FBhbuZ6B3p+YM3?^ybN|kss~YN7q-HQx_wu= z`Cszis{r3mL>r0bm&O*{;CLri$k~p+9S+5aE(C8Ui!j~=oTenr0taalqhWk$je#Eq z4h3Ib^hv{Se+6$0Kk-aK-xo0I?yqsHq)u`sr|2OWB0Y(4- literal 0 HcmV?d00001 diff --git a/samples/sqs/pluginConfig.gateway.json b/samples/sqs/server/pluginconfig.json similarity index 85% rename from samples/sqs/pluginConfig.gateway.json rename to samples/sqs/server/pluginconfig.json index 4e3fc9f..5573382 100644 --- a/samples/sqs/pluginConfig.gateway.json +++ b/samples/sqs/server/pluginconfig.json @@ -1,7 +1,7 @@ { "name": "SqsPlugin", "region": "us-east-1", - "endpointOverride": "http://localhost:4566", + "endpointOverride": "http://localstack:4566", "accessKeyId": "test", "secretAccessKey": "test", "requestQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-requests", diff --git a/servicebus/Readme.md b/servicebus/Readme.md index f1e397f..2d1cd80 100644 --- a/servicebus/Readme.md +++ b/servicebus/Readme.md @@ -115,7 +115,7 @@ Then run: ## 8) Get installation command Visit `http://localhost:81/GV`, select your package manager, and copy the generated -installation command. +installation command. To call the Gateway without the generated package, see [Client configuration](#client-configuration). Example for .NET: @@ -124,21 +124,17 @@ dotnet new console dotnet add package -s https://grft.dev/019cf6aa-e2e0-74e7-a2b0-be30db97ccb5__graftcode graft.nuget.physicscalculator --version 1.0.0 ``` -## 9) Configure Graft after installation +## Client configuration -Use this configuration: +A .NET client can call the gateway through the Hypertube SDK. The client config has no `runtime` or `host` keys; the plugin carries the transport. The connection string targets the local emulator ([samples/servicebus](../samples/servicebus)). Use your namespace connection string in Azure. -```csharp -string configSource = -""" +```json { "configurations": { - "graft.nuget.PhysicsCalculator": { - "runtime": "netcore", - "stateless": true, + "graft.nuget.netapp": { "plugin": { "name": "ServiceBusPlugin", - "connectionString": "Endpoint=sb://.servicebus.windows.net/;SharedAccessKeyName=;SharedAccessKey=", + "connectionString": "Endpoint=sb://localhost:5672;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=SAS_KEY_VALUE;UseDevelopmentEmulator=true;", "queue": "myqueue", "replyQueue": "myqueue.reply", "rpcTimeoutMs": 30000 @@ -146,11 +142,20 @@ string configSource = } } } -"""; +``` -graft.nuget.PhysicsCalculator.GraftConfig.SetConfig(configSource); +```csharp +using Hypertube.Netcore.Sdk; +using Hypertube.Netcore.Sdk.Configuration; + +RuntimeBridge.AddConfig(ConfigPriority.User, configJsonOrPathToFile); +var rtmCtx = RuntimeBridge.InitializeRC("graft.nuget.netapp"); +var result = rtmCtx.GetType("netapp.Calculator").CreateInstance() + .InvokeInstanceMethod("Add", 1, 2).Execute().GetValue(); ``` +`AddConfig` accepts a JSON string, a path to a JSON file, or the name of an environment variable holding either. The key under `configurations` must match the name passed to `InitializeRC`. Load one plugin config per process. + ## Configuration reference | Field | Required | Description | diff --git a/servicebus/ServiceBusPlugin/ServiceBusServer.cpp b/servicebus/ServiceBusPlugin/ServiceBusServer.cpp index f884db1..00c9660 100644 --- a/servicebus/ServiceBusPlugin/ServiceBusServer.cpp +++ b/servicebus/ServiceBusPlugin/ServiceBusServer.cpp @@ -41,6 +41,9 @@ namespace { constexpr std::uint64_t kMaxMessageSize = 1024ULL * 1024ULL; constexpr std::uint32_t kDefaultRpcTimeoutMs = 30000; constexpr int kReceivePollMs = 1000; + // Service Bus (and the emulator) force-detach links idle for 10 min; sending on a + // detached cached link segfaults in azure-core-amqp, so recycle reply senders well before. + constexpr auto kReplySenderMaxIdle = std::chrono::minutes(5); std::atomic_bool g_stopRequested{ false }; @@ -263,11 +266,6 @@ namespace { const std::vector& responseBytes, const AmqpModels::AmqpValue& correlationId, const std::optional& replySessionId) { - Amqp::MessageSender* sender = acquire(replyEntity); - if (sender == nullptr) { - return false; - } - AmqpModels::AmqpMessage response; std::vector body(responseBytes.begin(), responseBytes.end()); response.SetBody(AmqpModels::AmqpBinaryData(body)); @@ -280,12 +278,31 @@ namespace { } response.Properties.ContentType = std::string("application/octet-stream"); + for (int attempt = 0; attempt < 2; ++attempt) { + Amqp::MessageSender* sender = acquire(replyEntity); + if (sender == nullptr) { + return false; + } + bool ok = false; + try { #if defined(ENABLE_RUST_AMQP) && ENABLE_RUST_AMQP - return !static_cast(sender->Send(response)); + ok = !static_cast(sender->Send(response)); #else - const auto sendResult = sender->Send(response); - return std::get<0>(sendResult) == Amqp::MessageSendStatus::Ok; + const auto sendResult = sender->Send(response); + ok = std::get<0>(sendResult) == Amqp::MessageSendStatus::Ok; #endif + } + catch (...) { + ok = false; + } + if (ok) { + lastUsed_[replyEntity] = std::chrono::steady_clock::now(); + return true; + } + // Link likely detached by the broker; drop it and retry once with a fresh link. + evict(replyEntity); + } + return false; } void closeAll() { @@ -297,13 +314,34 @@ namespace { } } senders_.clear(); + lastUsed_.clear(); } private: + void evict(const std::string& replyEntity) { + const auto it = senders_.find(replyEntity); + if (it != senders_.end()) { + try { + it->second->Close(); + } + catch (...) { + } + senders_.erase(it); + } + lastUsed_.erase(replyEntity); + } + Amqp::MessageSender* acquire(const std::string& replyEntity) { const auto it = senders_.find(replyEntity); if (it != senders_.end()) { - return it->second.get(); + const auto used = lastUsed_.find(replyEntity); + const bool stale = used == lastUsed_.end() + || std::chrono::steady_clock::now() - used->second > kReplySenderMaxIdle; + if (!stale) { + return it->second.get(); + } + // Proactively replace before the broker's 10-min idle detach. + evict(replyEntity); } Amqp::MessageSenderOptions senderOptions; @@ -321,11 +359,13 @@ namespace { Amqp::MessageSender* raw = sender.get(); senders_.emplace(replyEntity, std::move(sender)); + lastUsed_[replyEntity] = std::chrono::steady_clock::now(); return raw; } Amqp::Session& session_; std::map> senders_; + std::map lastUsed_; }; } diff --git a/sqs/Readme.md b/sqs/Readme.md index f205cf9..45726dc 100644 --- a/sqs/Readme.md +++ b/sqs/Readme.md @@ -46,7 +46,7 @@ Download `gg` from https://github.com/grft-dev/graftcode-gateway/releases/. ## LocalStack -Sample files and compose live in [`samples/sqs`](../samples/sqs/README.md). From `sqs/` you can also start the same LocalStack; the compose file creates `graft-requests` and `graft-replies`: +Sample files and compose live in [`samples/sqs`](../samples/sqs) (see [samples/README.md](../samples/README.md)). From `sqs/` you can also start the same LocalStack; the compose file creates `graft-requests` and `graft-replies`: ```bash docker compose up -d @@ -75,7 +75,7 @@ This layout was run locally against LocalStack. Put these files in one folder: - the hosted module, for example `netapp.dll` - `SqsPlugin.dll` -`sqsplugin.json` (same contents as [`samples/sqs/pluginConfig.gateway.json`](../samples/sqs/pluginConfig.gateway.json)): +`sqsplugin.json`: ```json { @@ -97,25 +97,39 @@ From that folder: .\gg.exe netapp.dll --config sqsplugin.json ``` -Open `http://localhost:81/GV` and install the generated package (the verified client used `graft.nuget.netapp`). +Open `http://localhost:81/GV` to install the generated package, or call the Gateway from .NET with the Hypertube SDK (see [Client configuration](#client-configuration)). The verified client used `graft.nuget.netapp`. -## Client +## AWS credentials and permissions -`host` is the Gateway (`localhost:80`), not SQS. `clientconfig.json` matches [`samples/sqs/graftConfig.sqs.example.json`](../samples/sqs/graftConfig.sqs.example.json): +On AWS, omit `endpointOverride`, `accessKeyId`, `secretAccessKey`, and `verifySsl`. The SDK default credential chain (IAM role, environment, or shared config) is used. `accessKeyId` and `secretAccessKey` must be set together; `sessionToken` is optional. -```csharp -graft.nuget.netapp.GraftConfig.SetConfig("clientconfig.json"); -var calculator = new graft.nuget.netapp.Calculator(); -calculator.Add(1, 2); +The gateway identity needs `sqs:ReceiveMessage`, `sqs:DeleteMessage`, and `sqs:SendMessage` on the request queue (and `sqs:SendMessage` on reply queues). A client needs `sqs:SendMessage` on the request queue and `sqs:ReceiveMessage` plus `sqs:DeleteMessage` on its reply queue. + +## One-way calls + +Set `"oneWay": true` on both sides. The client omits `GraftcodeReplyTo` and does not wait. The gateway processes the request and deletes it without sending a reply. A reply queue is not required. Client `plugin` object: + +```json +{ + "name": "SqsPlugin", + "region": "us-east-1", + "endpointOverride": "http://localhost:4566", + "accessKeyId": "test", + "secretAccessKey": "test", + "requestQueueUrl": "http://sqs.us-east-1.localhost.localstack.cloud:4566/000000000000/graft-requests", + "oneWay": true, + "verifySsl": false +} ``` +## Client configuration + +A .NET client can call the gateway through the Hypertube SDK. The client config has no `runtime` or `host` keys; the plugin carries the transport. LocalStack values. Each client process needs its own `replyQueueUrl`. + ```json { "configurations": { "graft.nuget.netapp": { - "runtime": "netcore", - "host": "localhost:80", - "stateless": true, "plugin": { "name": "SqsPlugin", "region": "us-east-1", @@ -134,13 +148,17 @@ calculator.Add(1, 2); } ``` -On AWS, omit `endpointOverride`, `accessKeyId`, `secretAccessKey`, and `verifySsl`. The SDK default credential chain (IAM role, environment, or shared config) is used. `accessKeyId` and `secretAccessKey` must be set together; `sessionToken` is optional. - -The gateway identity needs `sqs:ReceiveMessage`, `sqs:DeleteMessage`, and `sqs:SendMessage` on the request queue (and `sqs:SendMessage` on reply queues). A client needs `sqs:SendMessage` on the request queue and `sqs:ReceiveMessage` plus `sqs:DeleteMessage` on its reply queue. +```csharp +using Hypertube.Netcore.Sdk; +using Hypertube.Netcore.Sdk.Configuration; -## One-way calls +RuntimeBridge.AddConfig(ConfigPriority.User, configJsonOrPathToFile); +var rtmCtx = RuntimeBridge.InitializeRC("graft.nuget.netapp"); +var result = rtmCtx.GetType("netapp.Calculator").CreateInstance() + .InvokeInstanceMethod("Add", 1, 2).Execute().GetValue(); +``` -Set `"oneWay": true` on both sides. The client omits `GraftcodeReplyTo` and does not wait. The gateway processes the request and deletes it without sending a reply. See [`samples/sqs/pluginConfig.oneway.json`](../samples/sqs/pluginConfig.oneway.json). A reply queue is not required. +`AddConfig` accepts a JSON string, a path to a JSON file, or the name of an environment variable holding either. The key under `configurations` must match the name passed to `InitializeRC`. Load one plugin config per process. ## Configuration reference